feat: preserve type alias of slices and maps (see #365)

This commit is contained in:
Samuel Berthe
2024-06-30 00:03:48 +02:00
parent de419c7571
commit 3ba93a16cc
8 changed files with 304 additions and 94 deletions

22
find.go
View File

@@ -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

View File

@@ -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")
}

View File

@@ -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)
}

View File

@@ -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")
}

28
map.go
View File

@@ -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] {

View File

@@ -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) {

View File

@@ -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

View File

@@ -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")
}