pkg/util/slices/slices.go (209 lines of code) (raw):
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package slices
import (
"cmp"
"slices"
"strings"
)
import (
"golang.org/x/exp/constraints"
)
// Equal reports whether two slices are equal: the same length and all
// elements equal. If the lengths are different, Equal returns false.
// Otherwise, the elements are compared in increasing index order, and the
// comparison stops at the first unequal pair.
// Floating point NaNs are not considered equal.
func Equal[E comparable](s1, s2 []E) bool {
return slices.Equal(s1, s2)
}
// EqualUnordered reports whether two slices are equal, ignoring order
func EqualUnordered[E comparable](s1, s2 []E) bool {
if len(s1) != len(s2) {
return false
}
first := make(map[E]struct{}, len(s1))
for _, c := range s1 {
first[c] = struct{}{}
}
for _, c := range s2 {
if _, f := first[c]; !f {
return false
}
}
return true
}
// EqualFunc reports whether two slices are equal using a comparison
// function on each pair of elements. If the lengths are different,
// EqualFunc returns false. Otherwise, the elements are compared in
// increasing index order, and the comparison stops at the first index
// for which eq returns false.
func EqualFunc[E1, E2 comparable](s1 []E1, s2 []E2, eq func(E1, E2) bool) bool {
return slices.EqualFunc(s1, s2, eq)
}
// SortFunc sorts the slice x in ascending order as determined by the less function.
// This sort is not guaranteed to be stable.
// The slice is modified in place but returned.
func SortFunc[E any](x []E, less func(a, b E) int) []E {
if len(x) <= 1 {
return x
}
slices.SortFunc(x, less)
return x
}
// SortStableFunc sorts the slice x while keeping the original order of equal element.
// The slice is modified in place but returned.
// Please refer to SortFunc for usage instructions.
func SortStableFunc[E any](x []E, less func(a, b E) int) []E {
if len(x) <= 1 {
return x
}
slices.SortStableFunc(x, less)
return x
}
// SortBy is a helper to sort a slice by some value. Typically, this would be sorting a struct
// by a single field. If you need to have multiple fields, see the ExampleSort.
func SortBy[E any, A constraints.Ordered](x []E, extract func(a E) A) []E {
if len(x) <= 1 {
return x
}
SortFunc(x, func(a, b E) int {
return cmp.Compare(extract(a), extract(b))
})
return x
}
// Sort sorts a slice of any ordered type in ascending order.
// The slice is modified in place but returned.
func Sort[E constraints.Ordered](x []E) []E {
if len(x) <= 1 {
return x
}
slices.Sort(x)
return x
}
// Clone returns a copy of the slice.
// The elements are copied using assignment, so this is a shallow clone.
func Clone[S ~[]E, E any](s S) S {
return slices.Clone(s)
}
// Delete removes the element i from s, returning the modified slice.
func Delete[S ~[]E, E any](s S, i int) S {
// Since Go 1.22, "slices.Delete zeroes the elements s[len(s)-(j-i):len(s)]"
// (no memory leak)
return slices.Delete(s, i, i+1)
}
// Contains reports whether v is present in s.
func Contains[E comparable](s []E, v E) bool {
return slices.Contains(s, v)
}
// FindFunc finds the first element matching the function, or nil if none do
func FindFunc[E any](s []E, f func(E) bool) *E {
idx := slices.IndexFunc(s, f)
if idx == -1 {
return nil
}
return &s[idx]
}
// First returns the first item in the slice, if there is one
func First[E any](s []E) *E {
if len(s) == 0 {
return nil
}
return &s[0]
}
// Reverse returns its argument array reversed
func Reverse[E any](r []E) []E {
for i, j := 0, len(r)-1; i < len(r)/2; i, j = i+1, j-1 {
r[i], r[j] = r[j], r[i]
}
return r
}
func BinarySearch[S ~[]E, E cmp.Ordered](x S, target E) (int, bool) {
return slices.BinarySearch(x, target)
}
// FilterInPlace retains all elements in []E that f(E) returns true for.
// The array is *mutated in place* and returned.
// Use Filter to avoid mutation
func FilterInPlace[E any](s []E, f func(E) bool) []E {
n := 0
for _, val := range s {
if f(val) {
s[n] = val
n++
}
}
// If those elements contain pointers you might consider zeroing those elements
// so that objects they reference can be garbage collected."
var empty E
for i := n; i < len(s); i++ {
s[i] = empty
}
s = s[:n]
return s
}
// FilterDuplicatesPresorted retains all unique elements in []E.
// The slices MUST be pre-sorted.
func FilterDuplicatesPresorted[E comparable](s []E) []E {
if len(s) <= 1 {
return s
}
n := 1
for i := 1; i < len(s); i++ {
val := s[i]
if val != s[i-1] {
s[n] = val
n++
}
}
// If those elements contain pointers you might consider zeroing those elements
// so that objects they reference can be garbage collected."
var empty E
for i := n; i < len(s); i++ {
s[i] = empty
}
s = s[:n]
return s
}
// Filter retains all elements in []E that f(E) returns true for.
// A new slice is created and returned. Use FilterInPlace to perform in-place
func Filter[E any](s []E, f func(E) bool) []E {
matched := []E{}
for _, v := range s {
if f(v) {
matched = append(matched, v)
}
}
return matched
}
// Map runs f() over all elements in s and returns the result
func Map[E any, O any](s []E, f func(E) O) []O {
n := make([]O, 0, len(s))
for _, e := range s {
n = append(n, f(e))
}
return n
}
// MapErr runs f() over all elements in s and returns the result, short circuiting if there is an error.
func MapErr[E any, O any](s []E, f func(E) (O, error)) ([]O, error) {
n := make([]O, 0, len(s))
for _, e := range s {
res, err := f(e)
if err != nil {
return nil, err
}
n = append(n, res)
}
return n, nil
}
// MapFilter runs f() over all elements in s and returns any non-nil results
func MapFilter[E any, O any](s []E, f func(E) *O) []O {
n := make([]O, 0, len(s))
for _, e := range s {
if res := f(e); res != nil {
n = append(n, *res)
}
}
return n
}
// Reference takes a pointer to all elements in the slice
func Reference[E any](s []E) []*E {
res := make([]*E, 0, len(s))
for _, v := range s {
v := v
res = append(res, &v)
}
return res
}
// Dereference returns all non-nil references, dereferenced
func Dereference[E any](s []*E) []E {
res := make([]E, 0, len(s))
for _, v := range s {
if v != nil {
res = append(res, *v)
}
}
return res
}
// Flatten merges a slice of slices into a single slice.
func Flatten[E any](s [][]E) []E {
if s == nil {
return nil
}
res := make([]E, 0)
for _, v := range s {
res = append(res, v...)
}
return res
}
// Group groups a slice by a key.
func Group[T any, K comparable](data []T, f func(T) K) map[K][]T {
res := make(map[K][]T, len(data))
for _, e := range data {
k := f(e)
res[k] = append(res[k], e)
}
return res
}
// GroupUnique groups a slice by a key. Each key must be unique or data will be lost. To allow multiple use Group.
func GroupUnique[T any, K comparable](data []T, f func(T) K) map[K]T {
res := make(map[K]T, len(data))
for _, e := range data {
res[f(e)] = e
}
return res
}
func Join(sep string, fields ...string) string {
return strings.Join(fields, sep)
}
// Insert inserts the values v... into s at index i,
// returning the modified slice.
// The elements at s[i:] are shifted up to make room.
// In the returned slice r, r[i] == v[0],
// and r[i+len(v)] == value originally at r[i].
// Insert panics if i is out of range.
// This function is O(len(s) + len(v)).
func Insert[S ~[]E, E any](s S, i int, v ...E) S {
return slices.Insert(s, i, v...)
}