diff --git a/find.go b/find.go index ece4c509..a9f43a4d 100644 --- a/find.go +++ b/find.go @@ -109,7 +109,7 @@ func FindKeyBy[K comparable, V any](object map[K]V, predicate func(key K, value // FindUniques returns a slice with all the unique elements of the collection. // The order of result values is determined by the order they occur in the collection. -func FindUniques[T comparable](collection []T) []T { +func FindUniques[T comparable, Slice ~[]T](collection Slice) Slice { isDupl := make(map[T]bool, len(collection)) for i := range collection { @@ -121,7 +121,7 @@ func FindUniques[T comparable](collection []T) []T { } } - result := make([]T, 0, len(collection)-len(isDupl)) + result := make(Slice, 0, len(collection)-len(isDupl)) for i := range collection { if duplicated := isDupl[collection[i]]; !duplicated { @@ -135,7 +135,7 @@ func FindUniques[T comparable](collection []T) []T { // FindUniquesBy returns a slice with all the unique elements of the collection. // The order of result values is determined by the order they occur in the array. It accepts `iteratee` which is // invoked for each element in array to generate the criterion by which uniqueness is computed. -func FindUniquesBy[T any, U comparable](collection []T, iteratee func(item T) U) []T { +func FindUniquesBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice { isDupl := make(map[U]bool, len(collection)) for i := range collection { @@ -149,7 +149,7 @@ func FindUniquesBy[T any, U comparable](collection []T, iteratee func(item T) U) } } - result := make([]T, 0, len(collection)-len(isDupl)) + result := make(Slice, 0, len(collection)-len(isDupl)) for i := range collection { key := iteratee(collection[i]) @@ -164,7 +164,7 @@ func FindUniquesBy[T any, U comparable](collection []T, iteratee func(item T) U) // FindDuplicates returns a slice with the first occurrence of each duplicated elements of the collection. // The order of result values is determined by the order they occur in the collection. -func FindDuplicates[T comparable](collection []T) []T { +func FindDuplicates[T comparable, Slice ~[]T](collection Slice) Slice { isDupl := make(map[T]bool, len(collection)) for i := range collection { @@ -176,7 +176,7 @@ func FindDuplicates[T comparable](collection []T) []T { } } - result := make([]T, 0, len(collection)-len(isDupl)) + result := make(Slice, 0, len(collection)-len(isDupl)) for i := range collection { if duplicated := isDupl[collection[i]]; duplicated { @@ -191,7 +191,7 @@ func FindDuplicates[T comparable](collection []T) []T { // FindDuplicatesBy returns a slice with the first occurrence of each duplicated elements of the collection. // The order of result values is determined by the order they occur in the array. It accepts `iteratee` which is // invoked for each element in array to generate the criterion by which uniqueness is computed. -func FindDuplicatesBy[T any, U comparable](collection []T, iteratee func(item T) U) []T { +func FindDuplicatesBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice { isDupl := make(map[U]bool, len(collection)) for i := range collection { @@ -205,7 +205,7 @@ func FindDuplicatesBy[T any, U comparable](collection []T, iteratee func(item T) } } - result := make([]T, 0, len(collection)-len(isDupl)) + result := make(Slice, 0, len(collection)-len(isDupl)) for i := range collection { key := iteratee(collection[i]) @@ -438,12 +438,12 @@ func Sample[T any](collection []T) T { } // Samples returns N random unique items from collection. -func Samples[T any](collection []T, count int) []T { +func Samples[T any, Slice ~[]T](collection Slice, count int) Slice { size := len(collection) - copy := append([]T{}, collection...) + copy := append(Slice{}, collection...) - results := []T{} + results := Slice{} for i := 0; i < size && i < count; i++ { copyLength := size - i diff --git a/find_test.go b/find_test.go index 907004ec..c5adafa4 100644 --- a/find_test.go +++ b/find_test.go @@ -184,6 +184,11 @@ func TestFindUniques(t *testing.T) { is.Equal(0, len(result4)) is.Equal([]int{}, result4) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := FindUniques(allStrings) + is.IsType(nonempty, allStrings, "type preserved") } func TestFindUniquesBy(t *testing.T) { @@ -217,6 +222,13 @@ func TestFindUniquesBy(t *testing.T) { is.Equal(0, len(result4)) is.Equal([]int{}, result4) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := FindUniquesBy(allStrings, func(i string) string { + return i + }) + is.IsType(nonempty, allStrings, "type preserved") } func TestFindDuplicates(t *testing.T) { @@ -237,6 +249,11 @@ func TestFindDuplicates(t *testing.T) { is.Equal(0, len(result3)) is.Equal([]int{}, result3) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := FindDuplicates(allStrings) + is.IsType(nonempty, allStrings, "type preserved") } func TestFindDuplicatesBy(t *testing.T) { @@ -263,6 +280,13 @@ func TestFindDuplicatesBy(t *testing.T) { is.Equal(0, len(result3)) is.Equal([]int{}, result3) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := FindDuplicatesBy(allStrings, func(i string) string { + return i + }) + is.IsType(nonempty, allStrings, "type preserved") } func TestMin(t *testing.T) { @@ -488,4 +512,9 @@ func TestSamples(t *testing.T) { is.Equal(result1, []string{"a", "b", "c"}) is.Equal(result2, []string{}) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Samples(allStrings, 2) + is.IsType(nonempty, allStrings, "type preserved") } diff --git a/intersect.go b/intersect.go index d8105b98..2df0e741 100644 --- a/intersect.go +++ b/intersect.go @@ -91,8 +91,8 @@ func NoneBy[T any](collection []T, predicate func(item T) bool) bool { } // Intersect returns the intersection between two collections. -func Intersect[T comparable](list1 []T, list2 []T) []T { - result := []T{} +func Intersect[T comparable, Slice ~[]T](list1 Slice, list2 Slice) Slice { + result := Slice{} seen := map[T]struct{}{} for i := range list1 { @@ -111,9 +111,9 @@ func Intersect[T comparable](list1 []T, list2 []T) []T { // Difference returns the difference between two collections. // The first value is the collection of element absent of list2. // The second value is the collection of element absent of list1. -func Difference[T comparable](list1 []T, list2 []T) ([]T, []T) { - left := []T{} - right := []T{} +func Difference[T comparable, Slice ~[]T](list1 Slice, list2 Slice) (Slice, Slice) { + left := Slice{} + right := Slice{} seenLeft := map[T]struct{}{} seenRight := map[T]struct{}{} @@ -143,14 +143,14 @@ func Difference[T comparable](list1 []T, list2 []T) ([]T, []T) { // Union returns all distinct elements from given collections. // result returns will not change the order of elements relatively. -func Union[T comparable](lists ...[]T) []T { +func Union[T comparable, Slice ~[]T](lists ...Slice) Slice { var capLen int for _, list := range lists { capLen += len(list) } - result := make([]T, 0, capLen) + result := make(Slice, 0, capLen) seen := make(map[T]struct{}, capLen) for i := range lists { @@ -166,8 +166,8 @@ func Union[T comparable](lists ...[]T) []T { } // Without returns slice excluding all given values. -func Without[T comparable](collection []T, exclude ...T) []T { - result := make([]T, 0, len(collection)) +func Without[T comparable, Slice ~[]T](collection Slice, exclude ...T) Slice { + result := make(Slice, 0, len(collection)) for i := range collection { if !Contains(exclude, collection[i]) { result = append(result, collection[i]) @@ -177,15 +177,8 @@ func Without[T comparable](collection []T, exclude ...T) []T { } // WithoutEmpty returns slice excluding empty values. -func WithoutEmpty[T comparable](collection []T) []T { - var empty T - - result := make([]T, 0, len(collection)) - for i := range collection { - if collection[i] != empty { - result = append(result, collection[i]) - } - } - - return result +// +// Deprecated: Use lo.Compact instead. +func WithoutEmpty[T comparable, Slice ~[]T](collection Slice) Slice { + return Compact(collection) } diff --git a/intersect_test.go b/intersect_test.go index 339dbcbb..53911599 100644 --- a/intersect_test.go +++ b/intersect_test.go @@ -187,6 +187,11 @@ func TestIntersect(t *testing.T) { is.Equal(result3, []int{}) is.Equal(result4, []int{0}) is.Equal(result5, []int{0}) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Intersect(allStrings, allStrings) + is.IsType(nonempty, allStrings, "type preserved") } func TestDifference(t *testing.T) { @@ -204,6 +209,12 @@ func TestDifference(t *testing.T) { left3, right3 := Difference([]int{0, 1, 2, 3, 4, 5}, []int{0, 1, 2, 3, 4, 5}) is.Equal(left3, []int{}) is.Equal(right3, []int{}) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + a, b := Difference(allStrings, allStrings) + is.IsType(a, allStrings, "type preserved") + is.IsType(b, allStrings, "type preserved") } func TestUnion(t *testing.T) { @@ -231,6 +242,11 @@ func TestUnion(t *testing.T) { is.Equal(result13, []int{0, 1, 2, 3, 4, 5}) is.Equal(result14, []int{0, 1, 2}) is.Equal(result15, []int{}) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Union(allStrings, allStrings) + is.IsType(nonempty, allStrings, "type preserved") } func TestWithout(t *testing.T) { @@ -247,6 +263,11 @@ func TestWithout(t *testing.T) { is.Equal(result3, []int{}) is.Equal(result4, []int{}) is.Equal(result5, []int{}) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Without(allStrings, "") + is.IsType(nonempty, allStrings, "type preserved") } func TestWithoutEmpty(t *testing.T) { @@ -259,4 +280,9 @@ func TestWithoutEmpty(t *testing.T) { is.Equal(result1, []int{1, 2}) is.Equal(result2, []int{1, 2}) is.Equal(result3, []int{}) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := WithoutEmpty(allStrings) + is.IsType(nonempty, allStrings, "type preserved") } diff --git a/map.go b/map.go index 885977fd..ce9d06e2 100644 --- a/map.go +++ b/map.go @@ -42,8 +42,8 @@ func ValueOr[K comparable, V any](in map[K]V, key K, fallback V) V { // PickBy returns same map type filtered by given predicate. // Play: https://go.dev/play/p/kdg8GR_QMmf -func PickBy[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) map[K]V { - r := map[K]V{} +func PickBy[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) bool) Map { + r := Map{} for k := range in { if predicate(k, in[k]) { r[k] = in[k] @@ -54,8 +54,8 @@ func PickBy[K comparable, V any](in map[K]V, predicate func(key K, value V) bool // PickByKeys returns same map type filtered by given keys. // Play: https://go.dev/play/p/R1imbuci9qU -func PickByKeys[K comparable, V any](in map[K]V, keys []K) map[K]V { - r := map[K]V{} +func PickByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map { + r := Map{} for i := range keys { if v, ok := in[keys[i]]; ok { r[keys[i]] = v @@ -66,8 +66,8 @@ func PickByKeys[K comparable, V any](in map[K]V, keys []K) map[K]V { // PickByValues returns same map type filtered by given values. // Play: https://go.dev/play/p/1zdzSvbfsJc -func PickByValues[K comparable, V comparable](in map[K]V, values []V) map[K]V { - r := map[K]V{} +func PickByValues[K comparable, V comparable, Map ~map[K]V](in Map, values []V) Map { + r := Map{} for k := range in { if Contains(values, in[k]) { r[k] = in[k] @@ -78,8 +78,8 @@ func PickByValues[K comparable, V comparable](in map[K]V, values []V) map[K]V { // OmitBy returns same map type filtered by given predicate. // Play: https://go.dev/play/p/EtBsR43bdsd -func OmitBy[K comparable, V any](in map[K]V, predicate func(key K, value V) bool) map[K]V { - r := map[K]V{} +func OmitBy[K comparable, V any, Map ~map[K]V](in Map, predicate func(key K, value V) bool) Map { + r := Map{} for k := range in { if !predicate(k, in[k]) { r[k] = in[k] @@ -90,8 +90,8 @@ func OmitBy[K comparable, V any](in map[K]V, predicate func(key K, value V) bool // OmitByKeys returns same map type filtered by given keys. // Play: https://go.dev/play/p/t1QjCrs-ysk -func OmitByKeys[K comparable, V any](in map[K]V, keys []K) map[K]V { - r := map[K]V{} +func OmitByKeys[K comparable, V any, Map ~map[K]V](in Map, keys []K) Map { + r := Map{} for k := range in { r[k] = in[k] } @@ -103,8 +103,8 @@ func OmitByKeys[K comparable, V any](in map[K]V, keys []K) map[K]V { // OmitByValues returns same map type filtered by given values. // Play: https://go.dev/play/p/9UYZi-hrs8j -func OmitByValues[K comparable, V comparable](in map[K]V, values []V) map[K]V { - r := map[K]V{} +func OmitByValues[K comparable, V comparable, Map ~map[K]V](in Map, values []V) Map { + r := Map{} for k := range in { if !Contains(values, in[k]) { r[k] = in[k] @@ -170,8 +170,8 @@ func Invert[K comparable, V comparable](in map[K]V) map[V]K { // Assign merges multiple maps from left to right. // Play: https://go.dev/play/p/VhwfJOyxf5o -func Assign[K comparable, V any](maps ...map[K]V) map[K]V { - out := map[K]V{} +func Assign[K comparable, V any, Map ~map[K]V](maps ...Map) Map { + out := Map{} for i := range maps { for k := range maps[i] { diff --git a/map_test.go b/map_test.go index bbbf38ef..e7a03f22 100644 --- a/map_test.go +++ b/map_test.go @@ -60,6 +60,11 @@ func TestPickBy(t *testing.T) { }) is.Equal(r1, map[string]int{"foo": 1, "baz": 3}) + + type myMap map[string]int + before := myMap{"": 0, "foobar": 6, "baz": 3} + after := PickBy(before, func(key string, value int) bool { return true }) + is.IsType(after, before, "type preserved") } func TestPickByKeys(t *testing.T) { @@ -69,6 +74,11 @@ func TestPickByKeys(t *testing.T) { r1 := PickByKeys(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []string{"foo", "baz", "qux"}) is.Equal(r1, map[string]int{"foo": 1, "baz": 3}) + + type myMap map[string]int + before := myMap{"": 0, "foobar": 6, "baz": 3} + after := PickByKeys(before, []string{"foobar", "baz"}) + is.IsType(after, before, "type preserved") } func TestPickByValues(t *testing.T) { @@ -78,6 +88,11 @@ func TestPickByValues(t *testing.T) { r1 := PickByValues(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []int{1, 3}) is.Equal(r1, map[string]int{"foo": 1, "baz": 3}) + + type myMap map[string]int + before := myMap{"": 0, "foobar": 6, "baz": 3} + after := PickByValues(before, []int{0, 3}) + is.IsType(after, before, "type preserved") } func TestOmitBy(t *testing.T) { @@ -89,6 +104,11 @@ func TestOmitBy(t *testing.T) { }) is.Equal(r1, map[string]int{"bar": 2}) + + type myMap map[string]int + before := myMap{"": 0, "foobar": 6, "baz": 3} + after := PickBy(before, func(key string, value int) bool { return true }) + is.IsType(after, before, "type preserved") } func TestOmitByKeys(t *testing.T) { @@ -98,6 +118,11 @@ func TestOmitByKeys(t *testing.T) { r1 := OmitByKeys(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []string{"foo", "baz", "qux"}) is.Equal(r1, map[string]int{"bar": 2}) + + type myMap map[string]int + before := myMap{"": 0, "foobar": 6, "baz": 3} + after := OmitByKeys(before, []string{"foobar", "baz"}) + is.IsType(after, before, "type preserved") } func TestOmitByValues(t *testing.T) { @@ -107,6 +132,11 @@ func TestOmitByValues(t *testing.T) { r1 := OmitByValues(map[string]int{"foo": 1, "bar": 2, "baz": 3}, []int{1, 3}) is.Equal(r1, map[string]int{"bar": 2}) + + type myMap map[string]int + before := myMap{"": 0, "foobar": 6, "baz": 3} + after := OmitByValues(before, []int{0, 3}) + is.IsType(after, before, "type preserved") } func TestEntries(t *testing.T) { @@ -211,6 +241,11 @@ func TestAssign(t *testing.T) { is.Len(result1, 3) is.Equal(result1, map[string]int{"a": 1, "b": 3, "c": 4}) + + type myMap map[string]int + before := myMap{"": 0, "foobar": 6, "baz": 3} + after := Assign(before, before) + is.IsType(after, before, "type preserved") } func TestMapKeys(t *testing.T) { diff --git a/slice.go b/slice.go index 74973d7c..5d7b479a 100644 --- a/slice.go +++ b/slice.go @@ -8,8 +8,8 @@ import ( // Filter iterates over elements of collection, returning an array of all elements predicate returns truthy for. // Play: https://go.dev/play/p/Apjg3WeSi7K -func Filter[V any, VC ~[]V](collection VC, predicate func(item V, index int) bool) VC { - result := make(VC, 0, len(collection)) +func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice { + result := make(Slice, 0, len(collection)) for i := range collection { if predicate(collection[i], i) { @@ -109,8 +109,8 @@ func Times[T any](count int, iteratee func(index int) T) []T { // Uniq returns a duplicate-free version of an array, in which only the first occurrence of each element is kept. // The order of result values is determined by the order they occur in the array. // Play: https://go.dev/play/p/DTzbeXZ6iEN -func Uniq[T comparable](collection []T) []T { - result := make([]T, 0, len(collection)) +func Uniq[T comparable, Slice ~[]T](collection Slice) Slice { + result := make(Slice, 0, len(collection)) seen := make(map[T]struct{}, len(collection)) for i := range collection { @@ -129,8 +129,8 @@ func Uniq[T comparable](collection []T) []T { // The order of result values is determined by the order they occur in the array. It accepts `iteratee` which is // invoked for each element in array to generate the criterion by which uniqueness is computed. // Play: https://go.dev/play/p/g42Z3QSb53u -func UniqBy[T any, U comparable](collection []T, iteratee func(item T) U) []T { - result := make([]T, 0, len(collection)) +func UniqBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) Slice { + result := make(Slice, 0, len(collection)) seen := make(map[U]struct{}, len(collection)) for i := range collection { @@ -149,8 +149,8 @@ func UniqBy[T any, U comparable](collection []T, iteratee func(item T) U) []T { // GroupBy returns an object composed of keys generated from the results of running each element of collection through iteratee. // Play: https://go.dev/play/p/XnQBd_v6brd -func GroupBy[T any, U comparable](collection []T, iteratee func(item T) U) map[U][]T { - result := map[U][]T{} +func GroupBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(item T) U) map[U]Slice { + result := map[U]Slice{} for i := range collection { key := iteratee(collection[i]) @@ -164,7 +164,7 @@ func GroupBy[T any, U comparable](collection []T, iteratee func(item T) U) map[U // Chunk returns an array of elements split into groups the length of size. If array can't be split evenly, // the final chunk will be the remaining elements. // Play: https://go.dev/play/p/EeKl0AuTehH -func Chunk[T any](collection []T, size int) [][]T { +func Chunk[T any, Slice ~[]T](collection Slice, size int) []Slice { if size <= 0 { panic("Second parameter must be greater than 0") } @@ -174,7 +174,7 @@ func Chunk[T any](collection []T, size int) [][]T { chunksNum += 1 } - result := make([][]T, 0, chunksNum) + result := make([]Slice, 0, chunksNum) for i := 0; i < chunksNum; i++ { last := (i + 1) * size @@ -191,8 +191,8 @@ func Chunk[T any](collection []T, size int) [][]T { // determined by the order they occur in collection. The grouping is generated from the results // of running each element of collection through iteratee. // Play: https://go.dev/play/p/NfQ_nGjkgXW -func PartitionBy[T any, K comparable](collection []T, iteratee func(item T) K) [][]T { - result := [][]T{} +func PartitionBy[T any, K comparable, Slice ~[]T](collection Slice, iteratee func(item T) K) []Slice { + result := []Slice{} seen := map[K]int{} for i := range collection { @@ -202,7 +202,7 @@ func PartitionBy[T any, K comparable](collection []T, iteratee func(item T) K) [ if !ok { resultIndex = len(result) seen[key] = resultIndex - result = append(result, []T{}) + result = append(result, Slice{}) } result[resultIndex] = append(result[resultIndex], collection[i]) @@ -217,13 +217,13 @@ func PartitionBy[T any, K comparable](collection []T, iteratee func(item T) K) [ // Flatten returns an array a single level deep. // Play: https://go.dev/play/p/rbp9ORaMpjw -func Flatten[T any](collection [][]T) []T { +func Flatten[T any, Slice ~[]T](collection []Slice) Slice { totalLen := 0 for i := range collection { totalLen += len(collection[i]) } - result := make([]T, 0, totalLen) + result := make(Slice, 0, totalLen) for i := range collection { result = append(result, collection[i]...) } @@ -233,9 +233,9 @@ func Flatten[T any](collection [][]T) []T { // Interleave round-robin alternating input slices and sequentially appending value at index into result // Play: https://go.dev/play/p/-RJkTLQEDVt -func Interleave[T any](collections ...[]T) []T { +func Interleave[T any, Slice ~[]T](collections ...Slice) Slice { if len(collections) == 0 { - return []T{} + return Slice{} } maxSize := 0 @@ -249,10 +249,10 @@ func Interleave[T any](collections ...[]T) []T { } if maxSize == 0 { - return []T{} + return Slice{} } - result := make([]T, totalSize) + result := make(Slice, totalSize) resultIdx := 0 for i := 0; i < maxSize; i++ { @@ -271,7 +271,7 @@ func Interleave[T any](collections ...[]T) []T { // Shuffle returns an array of shuffled values. Uses the Fisher-Yates shuffle algorithm. // Play: https://go.dev/play/p/Qp73bnTDnc7 -func Shuffle[T any](collection []T) []T { +func Shuffle[T any, Slice ~[]T](collection Slice) Slice { rand.Shuffle(len(collection), func(i, j int) { collection[i], collection[j] = collection[j], collection[i] }) @@ -281,7 +281,7 @@ func Shuffle[T any](collection []T) []T { // Reverse reverses array so that the first element becomes the last, the second element becomes the second to last, and so on. // Play: https://go.dev/play/p/fhUMLvZ7vS6 -func Reverse[T any](collection []T) []T { +func Reverse[T any, Slice ~[]T](collection Slice) Slice { length := len(collection) half := length / 2 @@ -368,30 +368,30 @@ func SliceToMap[T any, K comparable, V any](collection []T, transform func(item // Drop drops n elements from the beginning of a slice or array. // Play: https://go.dev/play/p/JswS7vXRJP2 -func Drop[T any](collection []T, n int) []T { +func Drop[T any, Slice ~[]T](collection Slice, n int) Slice { if len(collection) <= n { - return make([]T, 0) + return make(Slice, 0) } - result := make([]T, 0, len(collection)-n) + result := make(Slice, 0, len(collection)-n) return append(result, collection[n:]...) } // DropRight drops n elements from the end of a slice or array. // Play: https://go.dev/play/p/GG0nXkSJJa3 -func DropRight[T any](collection []T, n int) []T { +func DropRight[T any, Slice ~[]T](collection Slice, n int) Slice { if len(collection) <= n { - return []T{} + return Slice{} } - result := make([]T, 0, len(collection)-n) + result := make(Slice, 0, len(collection)-n) return append(result, collection[:len(collection)-n]...) } // DropWhile drops elements from the beginning of a slice or array while the predicate returns true. // Play: https://go.dev/play/p/7gBPYw2IK16 -func DropWhile[T any](collection []T, predicate func(item T) bool) []T { +func DropWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice { i := 0 for ; i < len(collection); i++ { if !predicate(collection[i]) { @@ -399,13 +399,13 @@ func DropWhile[T any](collection []T, predicate func(item T) bool) []T { } } - result := make([]T, 0, len(collection)-i) + result := make(Slice, 0, len(collection)-i) return append(result, collection[i:]...) } // DropRightWhile drops elements from the end of a slice or array while the predicate returns true. // Play: https://go.dev/play/p/3-n71oEC0Hz -func DropRightWhile[T any](collection []T, predicate func(item T) bool) []T { +func DropRightWhile[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice { i := len(collection) - 1 for ; i >= 0; i-- { if !predicate(collection[i]) { @@ -413,14 +413,14 @@ func DropRightWhile[T any](collection []T, predicate func(item T) bool) []T { } } - result := make([]T, 0, i+1) + result := make(Slice, 0, i+1) return append(result, collection[:i+1]...) } // Reject is the opposite of Filter, this method returns the elements of collection that predicate does not return truthy for. // Play: https://go.dev/play/p/YkLMODy1WEL -func Reject[V any](collection []V, predicate func(item V, index int) bool) []V { - result := []V{} +func Reject[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice { + result := Slice{} for i := range collection { if !predicate(collection[i], i) { @@ -449,9 +449,9 @@ func RejectMap[T any, R any](collection []T, callback func(item T, index int) (R // FilterReject mixes Filter and Reject, this method returns two slices, one for the elements of collection that // predicate returns truthy for and one for the elements that predicate does not return truthy for. -func FilterReject[V any](collection []V, predicate func(V, int) bool) (kept []V, rejected []V) { - kept = make([]V, 0, len(collection)) - rejected = make([]V, 0, len(collection)) +func FilterReject[T any, Slice ~[]T](collection Slice, predicate func(T, int) bool) (kept Slice, rejected Slice) { + kept = make(Slice, 0, len(collection)) + rejected = make(Slice, 0, len(collection)) for i := range collection { if predicate(collection[i], i) { @@ -515,7 +515,7 @@ func CountValuesBy[T any, U comparable](collection []T, mapper func(item T) U) m // Subset returns a copy of a slice from `offset` up to `length` elements. Like `slice[start:start+length]`, but does not panic on overflow. // Play: https://go.dev/play/p/tOQu1GhFcog -func Subset[T any](collection []T, offset int, length uint) []T { +func Subset[T any, Slice ~[]T](collection Slice, offset int, length uint) Slice { size := len(collection) if offset < 0 { @@ -526,7 +526,7 @@ func Subset[T any](collection []T, offset int, length uint) []T { } if offset > size { - return []T{} + return Slice{} } if length > uint(size)-uint(offset) { @@ -538,11 +538,11 @@ func Subset[T any](collection []T, offset int, length uint) []T { // Slice returns a copy of a slice from `start` up to, but not including `end`. Like `slice[start:end]`, but does not panic on overflow. // Play: https://go.dev/play/p/8XWYhfMMA1h -func Slice[T any](collection []T, start int, end int) []T { +func Slice[T any, Slice ~[]T](collection Slice, start int, end int) Slice { size := len(collection) if start >= end { - return []T{} + return Slice{} } if start > size { @@ -564,8 +564,8 @@ func Slice[T any](collection []T, start int, end int) []T { // Replace returns a copy of the slice with the first n non-overlapping instances of old replaced by new. // Play: https://go.dev/play/p/XfPzmf9gql6 -func Replace[T comparable](collection []T, old T, new T, n int) []T { - result := make([]T, len(collection)) +func Replace[T comparable, Slice ~[]T](collection Slice, old T, new T, n int) Slice { + result := make(Slice, len(collection)) copy(result, collection) for i := range result { @@ -580,16 +580,16 @@ func Replace[T comparable](collection []T, old T, new T, n int) []T { // ReplaceAll returns a copy of the slice with all non-overlapping instances of old replaced by new. // Play: https://go.dev/play/p/a9xZFUHfYcV -func ReplaceAll[T comparable](collection []T, old T, new T) []T { +func ReplaceAll[T comparable, Slice ~[]T](collection Slice, old T, new T) Slice { return Replace(collection, old, new, -1) } // Compact returns a slice of all non-zero elements. // Play: https://go.dev/play/p/tXiy-iK6PAc -func Compact[T comparable](collection []T) []T { +func Compact[T comparable, Slice ~[]T](collection Slice) Slice { var zero T - result := make([]T, 0, len(collection)) + result := make(Slice, 0, len(collection)) for i := range collection { if collection[i] != zero { @@ -629,10 +629,10 @@ func IsSortedByKey[T any, K constraints.Ordered](collection []T, iteratee func(i // Splice inserts multiple elements at index i. A negative index counts back // from the end of the slice. The helper is protected against overflow errors. // Play: https://go.dev/play/p/G5_GhkeSUBA -func Splice[T any](collection []T, i int, elements ...T) []T { +func Splice[T any, Slice ~[]T](collection Slice, i int, elements ...T) Slice { sizeCollection := len(collection) sizeElements := len(elements) - output := make([]T, 0, sizeCollection+sizeElements) // preallocate memory for the output slice + output := make(Slice, 0, sizeCollection+sizeElements) // preallocate memory for the output slice if sizeElements == 0 { return append(output, collection...) // simple copy diff --git a/slice_test.go b/slice_test.go index 1d0611ee..cd39b9c9 100644 --- a/slice_test.go +++ b/slice_test.go @@ -18,13 +18,11 @@ func TestFilter(t *testing.T) { r1 := Filter([]int{1, 2, 3, 4}, func(x int, _ int) bool { return x%2 == 0 }) - is.Equal(r1, []int{2, 4}) r2 := Filter([]string{"", "foo", "", "bar", ""}, func(x string, _ int) bool { return len(x) > 0 }) - is.Equal(r2, []string{"foo", "bar"}) type myStrings []string @@ -132,6 +130,12 @@ func TestReduceRight(t *testing.T) { }, []int{}) is.Equal(result1, []int{4, 5, 2, 3, 0, 1}) + + type collection []int + result3 := ReduceRight(collection{1, 2, 3, 4}, func(agg int, item int, _ int) int { + return agg + item + }, 10) + is.Equal(result3, 20) } func TestForEach(t *testing.T) { @@ -161,6 +165,11 @@ func TestUniq(t *testing.T) { is.Equal(len(result1), 2) is.Equal(result1, []int{1, 2}) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Uniq(allStrings) + is.IsType(nonempty, allStrings, "type preserved") } func TestUniqBy(t *testing.T) { @@ -173,6 +182,13 @@ func TestUniqBy(t *testing.T) { is.Equal(len(result1), 3) is.Equal(result1, []int{0, 1, 2}) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := UniqBy(allStrings, func(i string) string { + return i + }) + is.IsType(nonempty, allStrings, "type preserved") } func TestGroupBy(t *testing.T) { @@ -189,6 +205,13 @@ func TestGroupBy(t *testing.T) { 1: {1, 4}, 2: {2, 5}, }) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := GroupBy(allStrings, func(i string) int { + return 42 + }) + is.IsType(nonempty[42], allStrings, "type preserved") } func TestChunk(t *testing.T) { @@ -207,6 +230,11 @@ func TestChunk(t *testing.T) { is.PanicsWithValue("Second parameter must be greater than 0", func() { Chunk([]int{0}, 0) }) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Chunk(allStrings, 2) + is.IsType(nonempty[0], allStrings, "type preserved") } func TestPartitionBy(t *testing.T) { @@ -227,6 +255,13 @@ func TestPartitionBy(t *testing.T) { is.Equal(result1, [][]int{{-2, -1}, {0, 2, 4}, {1, 3, 5}}) is.Equal(result2, [][]int{}) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := PartitionBy(allStrings, func(item string) int { + return len(item) + }) + is.IsType(nonempty[0], allStrings, "type preserved") } func TestFlatten(t *testing.T) { @@ -236,9 +271,16 @@ func TestFlatten(t *testing.T) { result1 := Flatten([][]int{{0, 1}, {2, 3, 4, 5}}) is.Equal(result1, []int{0, 1, 2, 3, 4, 5}) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Flatten([]myStrings{allStrings}) + is.IsType(nonempty, allStrings, "type preserved") } func TestInterleave(t *testing.T) { + is := assert.New(t) + tests := []struct { name string collections [][]int @@ -282,6 +324,11 @@ func TestInterleave(t *testing.T) { } }) } + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Interleave(allStrings) + is.IsType(nonempty, allStrings, "type preserved") } func TestShuffle(t *testing.T) { @@ -293,6 +340,11 @@ func TestShuffle(t *testing.T) { is.NotEqual(result1, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) is.Equal(result2, []int{}) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Shuffle(allStrings) + is.IsType(nonempty, allStrings, "type preserved") } func TestReverse(t *testing.T) { @@ -306,6 +358,11 @@ func TestReverse(t *testing.T) { is.Equal(result1, []int{5, 4, 3, 2, 1, 0}) is.Equal(result2, []int{6, 5, 4, 3, 2, 1, 0}) is.Equal(result3, []int{}) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Reverse(allStrings) + is.IsType(nonempty, allStrings, "type preserved") } func TestFill(t *testing.T) { @@ -438,6 +495,11 @@ func TestDrop(t *testing.T) { is.Equal([]int{4}, Drop([]int{0, 1, 2, 3, 4}, 4)) is.Equal([]int{}, Drop([]int{0, 1, 2, 3, 4}, 5)) is.Equal([]int{}, Drop([]int{0, 1, 2, 3, 4}, 6)) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Drop(allStrings, 2) + is.IsType(nonempty, allStrings, "type preserved") } func TestDropRight(t *testing.T) { @@ -450,6 +512,11 @@ func TestDropRight(t *testing.T) { is.Equal([]int{0}, DropRight([]int{0, 1, 2, 3, 4}, 4)) is.Equal([]int{}, DropRight([]int{0, 1, 2, 3, 4}, 5)) is.Equal([]int{}, DropRight([]int{0, 1, 2, 3, 4}, 6)) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := DropRight(allStrings, 2) + is.IsType(nonempty, allStrings, "type preserved") } func TestDropWhile(t *testing.T) { @@ -467,6 +534,13 @@ func TestDropWhile(t *testing.T) { is.Equal([]int{0, 1, 2, 3, 4, 5, 6}, DropWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { return t == 10 })) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := DropWhile(allStrings, func(t string) bool { + return t != "foo" + }) + is.IsType(nonempty, allStrings, "type preserved") } func TestDropRightWhile(t *testing.T) { @@ -488,6 +562,13 @@ func TestDropRightWhile(t *testing.T) { is.Equal([]int{}, DropRightWhile([]int{0, 1, 2, 3, 4, 5, 6}, func(t int) bool { return t != 10 })) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := DropRightWhile(allStrings, func(t string) bool { + return t != "foo" + }) + is.IsType(nonempty, allStrings, "type preserved") } func TestReject(t *testing.T) { @@ -505,6 +586,13 @@ func TestReject(t *testing.T) { }) is.Equal(r2, []string{"foo", "bar"}) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Reject(allStrings, func(x string, _ int) bool { + return len(x) > 0 + }) + is.IsType(nonempty, allStrings, "type preserved") } func TestRejectMap(t *testing.T) { @@ -547,6 +635,14 @@ func TestFilterReject(t *testing.T) { is.Equal(left2, []string{"Smith", "Domin", "Olivia"}) is.Equal(right2, []string{"foo", "bar"}) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + a, b := FilterReject(allStrings, func(x string, _ int) bool { + return len(x) > 0 + }) + is.IsType(a, allStrings, "type preserved") + is.IsType(b, allStrings, "type preserved") } func TestCount(t *testing.T) { @@ -649,6 +745,11 @@ func TestSubset(t *testing.T) { is.Equal([]int{3, 4}, out10) is.Equal([]int{1}, out11) is.Equal([]int{1, 2, 3, 4}, out12) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Subset(allStrings, 0, 2) + is.IsType(nonempty, allStrings, "type preserved") } func TestSlice(t *testing.T) { @@ -694,6 +795,11 @@ func TestSlice(t *testing.T) { is.Equal([]int{0}, out16) is.Equal([]int{0, 1, 2}, out17) is.Equal([]int{0, 1, 2, 3, 4}, out18) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Slice(allStrings, 0, 2) + is.IsType(nonempty, allStrings, "type preserved") } func TestReplace(t *testing.T) { @@ -723,6 +829,11 @@ func TestReplace(t *testing.T) { is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out8) is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out9) is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out10) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Replace(allStrings, "0", "2", 1) + is.IsType(nonempty, allStrings, "type preserved") } func TestReplaceAll(t *testing.T) { @@ -736,6 +847,11 @@ func TestReplaceAll(t *testing.T) { is.Equal([]int{42, 1, 42, 1, 2, 3, 42}, out1) is.Equal([]int{0, 1, 0, 1, 2, 3, 0}, out2) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := ReplaceAll(allStrings, "0", "2") + is.IsType(nonempty, allStrings, "type preserved") } func TestCompact(t *testing.T) { @@ -778,6 +894,11 @@ func TestCompact(t *testing.T) { r5 := Compact([]*foo{&e1, &e2, nil, &e3}) is.Equal(r5, []*foo{&e1, &e2, &e3}) + + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Compact(allStrings) + is.IsType(nonempty, allStrings, "type preserved") } func TestIsSorted(t *testing.T) { @@ -851,4 +972,10 @@ func TestSplice(t *testing.T) { is.Equal([]string{"1", "2", "0"}, Splice([]string{"0"}, 0, "1", "2")) is.Equal([]string{"0", "1", "2"}, Splice([]string{"0"}, 1, "1", "2")) is.Equal([]string{"1", "2", "0"}, Splice([]string{"0"}, -1, "1", "2")) + + // type preserved + type myStrings []string + allStrings := myStrings{"", "foo", "bar"} + nonempty := Splice(allStrings, 1, "1", "2") + is.IsType(nonempty, allStrings, "type preserved") }