diff --git a/README.md b/README.md index e0f2657d..2f84effa 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,7 @@ You can import `lo` using: import ( "github.com/samber/lo" lop "github.com/samber/lo/parallel" + lom "github.com/samber/lo/mutable" ) ``` diff --git a/mutable/slice.go b/mutable/slice.go new file mode 100644 index 00000000..301f8198 --- /dev/null +++ b/mutable/slice.go @@ -0,0 +1,95 @@ +package mutable + +import "math/rand" + +// Filter iterates over elements of collection, returning an array of all elements predicate returns truthy for. +// The function returns the modified slice, which may be shorter than the original if some elements were removed. +// The order of elements in the original slice is preserved in the output. +// Play: +func Filter[T any](collection *[]T, predicate func(item T) bool) { + FilterI(collection, func(item T, index int) bool { + return predicate(item) + }) +} + +// Filter iterates over elements of collection, returning an array of all elements predicate returns truthy for. +// The function returns the modified slice, which may be shorter than the original if some elements were removed. +// The order of elements in the original slice is preserved in the output. +// Play: +func FilterI[T any](collection *[]T, predicate func(item T, index int) bool) { + j := 0 + for i := range *collection { + if predicate((*collection)[i], i) { + (*collection)[j] = (*collection)[i] + j++ + } + } + + *collection = (*collection)[:j] +} + +// 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: +func Uniq[T comparable](collection *[]T) { + size := len(*collection) + seen := make(map[T]struct{}, size) + j := 0 + + for i := 0; i < size; i++ { + if _, ok := seen[(*collection)[i]]; ok { + continue + } + + seen[(*collection)[i]] = struct{}{} + + (*collection)[j] = (*collection)[i] + j++ + } + + *collection = (*collection)[:j] +} + +// UniqBy 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. It accepts `iteratee` which is +// invoked for each element in array to generate the criterion by which uniqueness is computed. +// Play: +func UniqBy[T any, U comparable](collection *[]T, iteratee func(item T) U) { + size := len(*collection) + seen := make(map[U]struct{}, size) + j := 0 + + for i := 0; i < size; i++ { + key := iteratee((*collection)[i]) + if _, ok := seen[key]; ok { + continue + } + + seen[key] = struct{}{} + + (*collection)[j] = (*collection)[i] + j++ + } + + *collection = (*collection)[:j] +} + +// Shuffle returns an array of shuffled values. Uses the Fisher-Yates shuffle algorithm. +// Play: +func Shuffle[T any](collection []T) { + rand.Shuffle(len(collection), func(i, j int) { + collection[i], collection[j] = collection[j], collection[i] + }) +} + +// Reverse reverses array so that the first element becomes the last, the second element becomes the second to last, and so on. +// Play: +func Reverse[T any](collection []T) { + length := len(collection) + half := length / 2 + + for i := 0; i < half; i = i + 1 { + j := length - 1 - i + collection[i], collection[j] = collection[j], collection[i] + } +} diff --git a/mutable/slice_test.go b/mutable/slice_test.go new file mode 100644 index 00000000..ec7347ca --- /dev/null +++ b/mutable/slice_test.go @@ -0,0 +1,94 @@ +package mutable + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestFilter(t *testing.T) { + t.Parallel() + is := assert.New(t) + + r1 := []int{1, 2, 3, 4} + Filter(&r1, func(x int) bool { + return x%2 == 0 + }) + is.Equal([]int{2, 4}, r1) + + r2 := []string{"", "foo", "", "bar", ""} + Filter(&r2, func(x string) bool { + return len(x) > 0 + }) + is.Equal([]string{"foo", "bar"}, r2) +} + +func TestFilterI(t *testing.T) { + t.Parallel() + is := assert.New(t) + + r1 := []int{1, 2, 3, 4} + FilterI(&r1, func(x int, _ int) bool { + return x%2 == 0 + }) + is.Equal([]int{2, 4}, r1) + + r2 := []string{"", "foo", "", "bar", ""} + FilterI(&r2, func(x string, _ int) bool { + return len(x) > 0 + }) + is.Equal([]string{"foo", "bar"}, r2) +} + +func TestUniq(t *testing.T) { + t.Parallel() + is := assert.New(t) + + result1 := []int{1, 2, 2, 1} + Uniq(&result1) + is.Equal(len(result1), 2) + is.Equal(result1, []int{1, 2}) +} + +func TestUniqBy(t *testing.T) { + t.Parallel() + is := assert.New(t) + + result1 := []int{0, 1, 2, 3, 4, 5} + UniqBy(&result1, func(i int) int { + return i % 3 + }) + + is.Equal(len(result1), 3) + is.Equal(result1, []int{0, 1, 2}) +} + +func TestShuffle(t *testing.T) { + t.Parallel() + is := assert.New(t) + + result1 := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + Shuffle(result1) + is.NotEqual(result1, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) + + result2 := []int{} + Shuffle(result2) + is.Equal(result2, []int{}) +} + +func TestReverse(t *testing.T) { + t.Parallel() + is := assert.New(t) + + result1 := []int{0, 1, 2, 3, 4, 5} + Reverse(result1) + is.Equal(result1, []int{5, 4, 3, 2, 1, 0}) + + result2 := []int{0, 1, 2, 3, 4, 5, 6} + Reverse(result2) + is.Equal(result2, []int{6, 5, 4, 3, 2, 1, 0}) + + result3 := []int{} + Reverse(result3) + is.Equal(result3, []int{}) +} diff --git a/parallel/slice.go b/parallel/slice.go index a70fb70f..db5c778b 100644 --- a/parallel/slice.go +++ b/parallel/slice.go @@ -1,6 +1,8 @@ package parallel -import "sync" +import ( + "sync" +) // Map manipulates a slice and transforms it to a slice of another type. // `iteratee` is call in parallel. Result keep the same order. diff --git a/slice.go b/slice.go index 5e8c1555..5bb33560 100644 --- a/slice.go +++ b/slice.go @@ -1,8 +1,8 @@ package lo import ( - "sort" "math/rand" + "sort" "github.com/samber/lo/internal/constraints" ) @@ -273,15 +273,21 @@ func Interleave[T any, Slice ~[]T](collections ...Slice) Slice { // Shuffle returns an array of shuffled values. Uses the Fisher-Yates shuffle algorithm. // Play: https://go.dev/play/p/Qp73bnTDnc7 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] + size := len(collection) + output := make(Slice, size) + copy(output, collection) + + rand.Shuffle(size, func(i, j int) { + output[i], output[j] = output[j], output[i] }) - return collection + return output } // 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 +// +// Deprecated: Use lom.Reverse instead. func Reverse[T any, Slice ~[]T](collection Slice) Slice { length := len(collection) half := length / 2 diff --git a/slice_test.go b/slice_test.go index 0917e171..e6605626 100644 --- a/slice_test.go +++ b/slice_test.go @@ -335,10 +335,13 @@ func TestShuffle(t *testing.T) { t.Parallel() is := assert.New(t) - result1 := Shuffle([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) - result2 := Shuffle([]int{}) - + input1 := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10} + result1 := Shuffle(input1) + is.Equal(input1, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) is.NotEqual(result1, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) + + input2 := []int{} + result2 := Shuffle(input2) is.Equal(result2, []int{}) type myStrings []string