From 415a6a9087a47e1c0253a4f99d59d1bb774c213f Mon Sep 17 00:00:00 2001 From: Samuel Berthe Date: Sat, 25 Jan 2025 18:59:22 +0100 Subject: [PATCH 1/4] feat(mutable): adding Map, MapI, Filter, FilterI --- README.md | 13 +++++++ mutable/slice.go | 48 ++++++++++++++++++++++++ mutable/slice_example_test.go | 22 +++++++++++ mutable/slice_test.go | 69 +++++++++++++++++++++++++++++++++++ 4 files changed, 152 insertions(+) diff --git a/README.md b/README.md index 7d33b996..4ca45579 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" ) ``` @@ -353,6 +354,18 @@ lop.Map([]int64{1, 2, 3, 4}, func(x int64, _ int) string { // []string{"1", "2", "3", "4"} ``` +Mutable: like `lo.Map()`, but the slice is updated in place. + +```go +import lom "github.com/samber/lo/mutable" + +list := []int{1, 2, 3, 4} +lom.Map(list, func(x int) int { + return i*2 +}) +// []int{2, 4, 6, 8} +``` + ### UniqMap Manipulates a slice and transforms it to a slice of another type with unique values. diff --git a/mutable/slice.go b/mutable/slice.go index 136cf421..caa0f515 100644 --- a/mutable/slice.go +++ b/mutable/slice.go @@ -1,5 +1,53 @@ package mutable +// Filter is a generic function that modifies the input slice in-place to contain only the elements +// that satisfy the provided predicate function. The predicate function takes an element of the slice and its index, +// and should return true for elements that should be kept and false for elements that should be removed. +// The function returns the modified slice, which may be shorter than the original if some elements were removed. +// Note that the order of elements in the original slice is preserved in the output. +func Filter[T any, Slice ~[]T](collection Slice, predicate func(item T) bool) Slice { + j := 0 + for _, item := range collection { + if predicate(item) { + collection[j] = item + j++ + } + } + return collection[:j] +} + +// FilterI is a generic function that modifies the input slice in-place to contain only the elements +// that satisfy the provided predicate function. The predicate function takes an element of the slice and its index, +// and should return true for elements that should be kept and false for elements that should be removed. +// The function returns the modified slice, which may be shorter than the original if some elements were removed. +// Note that the order of elements in the original slice is preserved in the output. +func FilterI[T any, Slice ~[]T](collection Slice, predicate func(item T, index int) bool) Slice { + j := 0 + for i, item := range collection { + if predicate(item, i) { + collection[j] = item + j++ + } + } + return collection[:j] +} + +// Map is a generic function that modifies the input slice in-place to contain the result of applying the provided +// function to each element of the slice. The function returns the modified slice, which has the same length as the original. +func Map[T any, Slice ~[]T](collection Slice, fn func(item T) T) { + for i := range collection { + collection[i] = fn(collection[i]) + } +} + +// MapI is a generic function that modifies the input slice in-place to contain the result of applying the provided +// function to each element of the slice. The function returns the modified slice, which has the same length as the original. +func MapI[T any, Slice ~[]T](collection Slice, fn func(item T, index int) T) { + for i := range collection { + collection[i] = fn(collection[i], i) + } +} + // 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/iv2e9jslfBM func Reverse[T any, Slice ~[]T](collection Slice) { diff --git a/mutable/slice_example_test.go b/mutable/slice_example_test.go index 21b94567..c5da0706 100644 --- a/mutable/slice_example_test.go +++ b/mutable/slice_example_test.go @@ -2,6 +2,28 @@ package mutable import "fmt" +func ExampleMap() { + list := []int{1, 2, 3, 4} + + Map(list, func(nbr int) int { + return nbr * 2 + }) + + fmt.Printf("%v", list) + // Output: [2 4 6 8] +} + +func ExampleMapI() { + list := []int{1, 2, 3, 4} + + MapI(list, func(nbr int, index int) int { + return nbr * index + }) + + fmt.Printf("%v", list) + // Output: [0 2 6 12] +} + func ExampleReverse() { list := []int{0, 1, 2, 3, 4, 5} diff --git a/mutable/slice_test.go b/mutable/slice_test.go index 67448fc6..eeb46f2c 100644 --- a/mutable/slice_test.go +++ b/mutable/slice_test.go @@ -6,6 +6,75 @@ import ( "github.com/stretchr/testify/assert" ) +func TestFilter(t *testing.T) { + t.Parallel() + is := assert.New(t) + + r1 := Filter([]int{1, 2, 3, 4}, func(x int) bool { + return x%2 == 0 + }) + + is.Equal(r1, []int{2, 4}) + + r2 := Filter([]string{"", "foo", "", "bar", ""}, func(x string) bool { + return len(x) > 0 + }) + + is.Equal(r2, []string{"foo", "bar"}) +} + +func TestFilterI(t *testing.T) { + t.Parallel() + is := assert.New(t) + + r1 := FilterI([]int{1, 2, 3, 4}, func(x int, i int) bool { + is.Equal(i, x-1) + return x%2 == 0 + }) + + is.Equal(r1, []int{2, 4}) +} + +func TestMap(t *testing.T) { + t.Parallel() + is := assert.New(t) + + list := []int{1, 2, 3, 4} + Map(list, func(x int) int { + return x * 2 + }) + is.Equal(len(list), 4) + is.Equal(list, []int{2, 4, 6, 8}) + + list = []int{1, 2, 3, 4} + Map(list, func(x int) int { + return x * 4 + }) + is.Equal(len(list), 4) + is.Equal(list, []int{4, 8, 12, 16}) +} + +func TestMapI(t *testing.T) { + t.Parallel() + is := assert.New(t) + + list := []int{1, 2, 3, 4} + MapI(list, func(x int, index int) int { + is.Equal(index, x-1) + return x * 2 + }) + is.Equal(len(list), 4) + is.Equal(list, []int{2, 4, 6, 8}) + + list = []int{1, 2, 3, 4} + MapI(list, func(x int, index int) int { + is.Equal(index, x-1) + return x * 4 + }) + is.Equal(len(list), 4) + is.Equal(list, []int{4, 8, 12, 16}) +} + func TestReverse(t *testing.T) { t.Parallel() is := assert.New(t) From d406060cb5c1109db61e3471020ac2a36877b235 Mon Sep 17 00:00:00 2001 From: Samuel Berthe Date: Sat, 26 Apr 2025 18:19:50 +0200 Subject: [PATCH 2/4] Update README.md --- README.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/README.md b/README.md index 4ca45579..caa7d79a 100644 --- a/README.md +++ b/README.md @@ -328,6 +328,18 @@ even := lo.Filter([]int{1, 2, 3, 4}, func(x int, index int) bool { [[play](https://go.dev/play/p/Apjg3WeSi7K)] +Mutable: like `lo.Filter()`, but the slice is updated in place. + +```go +import lom "github.com/samber/lo/mutable" + +list := []int{1, 2, 3, 4} +lom.Filter(list, func(x int) bool { + return x%2 == 0 +}) +// []int{2, 4, 6, 8} +``` + ### Map Manipulates a slice of one type and transforms it into a slice of another type: From 0a02284438eeea65508df0dd3f58a8d5a27acd3f Mon Sep 17 00:00:00 2001 From: Samuel Berthe Date: Sat, 26 Apr 2025 18:27:31 +0200 Subject: [PATCH 3/4] doc: add doc and examples for lom.Filter --- README.md | 9 +++++++-- mutable/slice_example_test.go | 26 ++++++++++++++++++++++++++ mutable/slice_test.go | 8 ++++++-- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index caa7d79a..b05ef4ec 100644 --- a/README.md +++ b/README.md @@ -334,10 +334,15 @@ Mutable: like `lo.Filter()`, but the slice is updated in place. import lom "github.com/samber/lo/mutable" list := []int{1, 2, 3, 4} -lom.Filter(list, func(x int) bool { +newList := lom.Filter(list, func(x int) bool { return x%2 == 0 }) -// []int{2, 4, 6, 8} + +list +// []int{2, 4, 3, 4} + +newList +// []int{2, 4} ``` ### Map diff --git a/mutable/slice_example_test.go b/mutable/slice_example_test.go index c5da0706..820d6163 100644 --- a/mutable/slice_example_test.go +++ b/mutable/slice_example_test.go @@ -2,6 +2,32 @@ package mutable import "fmt" +func ExampleFilter() { + list := []int{1, 2, 3, 4} + + newList := Filter(list, func(nbr int) bool { + return nbr%2 == 0 + }) + + fmt.Printf("%v\n%v", list, newList) + // Output: + // [2 4 3 4] + // [2 4] +} + +func ExampleFilterI() { + list := []int{1, 2, 3, 4} + + newList := Filter(list, func(nbr int) bool { + return nbr%2 == 0 + }) + + fmt.Printf("%v\n%v", list, newList) + // Output: + // [2 4 3 4] + // [2 4] +} + func ExampleMap() { list := []int{1, 2, 3, 4} diff --git a/mutable/slice_test.go b/mutable/slice_test.go index eeb46f2c..a578dc40 100644 --- a/mutable/slice_test.go +++ b/mutable/slice_test.go @@ -10,16 +10,20 @@ func TestFilter(t *testing.T) { t.Parallel() is := assert.New(t) - r1 := Filter([]int{1, 2, 3, 4}, func(x int) bool { + input1 := []int{1, 2, 3, 4} + r1 := Filter(input1, func(x int) bool { return x%2 == 0 }) + is.Equal(input1, []int{2, 4, 3, 4}) is.Equal(r1, []int{2, 4}) - r2 := Filter([]string{"", "foo", "", "bar", ""}, func(x string) bool { + input2 := []string{"", "foo", "", "bar", ""} + r2 := Filter(input2, func(x string) bool { return len(x) > 0 }) + is.Equal(input2, []string{"foo", "bar", "", "bar", ""}) is.Equal(r2, []string{"foo", "bar"}) } From 882fa2ac6db0d2f63501f265983e1aa4d999a7dd Mon Sep 17 00:00:00 2001 From: Samuel Berthe Date: Sat, 26 Apr 2025 18:40:01 +0200 Subject: [PATCH 4/4] oops --- mutable/slice.go | 1 + 1 file changed, 1 insertion(+) diff --git a/mutable/slice.go b/mutable/slice.go index 1302f53d..969f3995 100644 --- a/mutable/slice.go +++ b/mutable/slice.go @@ -48,6 +48,7 @@ func MapI[T any, Slice ~[]T](collection Slice, fn func(item T, index int) T) { for i := range collection { collection[i] = fn(collection[i], i) } +} // Shuffle returns an array of shuffled values. Uses the Fisher-Yates shuffle algorithm. // Play: https://go.dev/play/p/2xb3WdLjeSJ