// Package sliceutil provides utilities for working with slices. package sliceutil import ( "slices" ) // AppendUnique appends toAdd to the vs slice if toAdd does not already // exist in the slice. It returns the new or unchanged slice. func AppendUnique[T comparable](vs []T, toAdd T) []T { if slices.Contains(vs, toAdd) { return vs } return append(vs, toAdd) } // AppendUniques appends a slice of values to the vs slice. It only // appends values that do not already exist in the slice. // It returns the new or unchanged slice. func AppendUniques[T comparable](vs []T, toAdd []T) []T { if len(toAdd) == 0 { return vs } // Extend the slice's capacity to avoid multiple re-allocations even in the worst case vs = slices.Grow(vs, len(toAdd)) for _, v := range toAdd { vs = AppendUnique(vs, v) } return vs } // Exclude returns a copy of the vs slice, excluding all values // that are also present in the toExclude slice. func Exclude[T comparable](vs []T, toExclude []T) []T { ret := make([]T, 0, len(vs)) for _, v := range vs { if !slices.Contains(toExclude, v) { ret = append(ret, v) } } return ret } // Unique returns a copy of the vs slice, with non-unique values removed. func Unique[T comparable](vs []T) []T { distinctValues := make(map[T]struct{}, len(vs)) ret := make([]T, 0, len(vs)) for _, v := range vs { if _, exists := distinctValues[v]; !exists { distinctValues[v] = struct{}{} ret = append(ret, v) } } return ret } // Delete returns a copy of the vs slice with toDel values removed. func Delete[T comparable](vs []T, toDel T) []T { ret := make([]T, 0, len(vs)) for _, v := range vs { if v != toDel { ret = append(ret, v) } } return ret } // Intersect returns a slice containing values that exist in both provided slices. func Intersect[T comparable](a []T, b []T) []T { var ret []T for _, v := range a { if slices.Contains(b, v) { ret = append(ret, v) } } return ret } // NotIntersect returns a slice containing values that do not exist in both provided slices. func NotIntersect[T comparable](a []T, b []T) []T { var ret []T for _, v := range a { if !slices.Contains(b, v) { ret = append(ret, v) } } for _, v := range b { if !slices.Contains(a, v) { ret = append(ret, v) } } return ret } // SliceSame returns true if the two provided slices have equal elements, // regardless of order. func SliceSame[T comparable](a []T, b []T) bool { if len(a) != len(b) { return false } visited := make(map[int]struct{}) for i := range a { found := false for j := range b { if _, exists := visited[j]; exists { continue } if a[i] == b[j] { found = true visited[j] = struct{}{} break } } if !found { return false } } return true } // Filter returns a slice containing the elements of the vs slice // that meet the condition specified by f. func Filter[T any](vs []T, f func(T) bool) []T { var ret []T for _, v := range vs { if f(v) { ret = append(ret, v) } } return ret } // Map returns the result of applying f to each element of the vs slice. func Map[T any, V any](vs []T, f func(T) V) []V { ret := make([]V, len(vs)) for i, v := range vs { ret[i] = f(v) } return ret } func PtrsToValues[T any](vs []*T) []T { ret := make([]T, len(vs)) for i, v := range vs { ret[i] = *v } return ret } func ValuesToPtrs[T any](vs []T) []*T { ret := make([]*T, len(vs)) for i, v := range vs { // We can do this safely because go.mod indicates Go 1.22 // See: https://go.dev/blog/loopvar-preview ret[i] = &v } return ret } // Flatten returns a single slice containing all elements of the provided // slice of slices. func Flatten[T any](vs [][]T) []T { var ret []T for _, v := range vs { ret = append(ret, v...) } return ret }