From b47cd507cce9beee1e9e4b3abe376c3976fd36e9 Mon Sep 17 00:00:00 2001 From: SicParv1sMagna <92353726+SicParv1sMagna@users.noreply.github.com> Date: Wed, 11 Sep 2024 00:49:04 +0300 Subject: [PATCH 1/9] Added pull methods from lodash --- slice.go | 90 ++++++++++++++++++++++++++++++++++++++++ slice_test.go | 112 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 202 insertions(+) diff --git a/slice.go b/slice.go index d2d3fd84..2568bc49 100644 --- a/slice.go +++ b/slice.go @@ -693,3 +693,93 @@ func Splice[T any, Slice ~[]T](collection Slice, i int, elements ...T) Slice { return append(append(append(output, collection[:i]...), elements...), collection[i:]...) } + +// Pull removes all given values from splice using SameValueZero for equality comparisons. +func Pull[T comparable, Slice ~[]T](collection Slice, elements ...T) Slice { + output := make(Slice, 0, len(collection)) + + for _, item := range collection { + shouldRemove := false + for _, element := range elements { + if item == element { + shouldRemove = true + break + } + } + + if !shouldRemove { + output = append(output, item) + } + } + + return output +} + +// This method is like Pull except that it accepts an array of values to remove +func PullAll[T comparable, Slice ~[]T](collection Slice, elements []T) Slice { + output := make(Slice, 0, len(collection)) + + for _, item := range collection { + shouldRemove := false + for _, element := range elements { + if item == element { + shouldRemove = true + break + } + } + + if !shouldRemove { + output = append(output, item) + } + } + + return output +} + +// This method is like PullAll except that it accepts iteratee which is invoked +// for each element of slice and values to generate the criterion by which they're compared +func PullAllBy[T any, U comparable, Slice ~[]T](collection Slice, elements []U, iteratee func(T) U) Slice { + elementCriteria := make(map[U]struct{}, len(elements)) + for _, element := range elements { + elementCriteria[element] = struct{}{} + } + + var output Slice + for _, item := range collection { + if _, shouldRemove := elementCriteria[iteratee(item)]; !shouldRemove { + output = append(output, item) + } + } + + return output +} + +// This method removes elements from slice corresponding to indexes and returns +// an array of removed elements +func PullAt[T any](slice []T, indexes []int) ([]T, []T) { + if len(slice) == 0 || len(indexes) == 0 { + return nil, slice + } + + sort.Sort(sort.Reverse(sort.IntSlice(indexes))) + + indexMap := make(map[int]struct{}, len(indexes)) + for _, index := range indexes { + if index >= 0 && index < len(slice) { + indexMap[index] = struct{}{} + } + } + + var removed []T + var output []T + + for i, v := range slice { + if _, ok := indexMap[i]; ok { + removed = append(removed, v) + } else { + output = append(output, v) + } + } + + return removed, output +} diff --git a/slice_test.go b/slice_test.go index 9f923eea..02b096b7 100644 --- a/slice_test.go +++ b/slice_test.go @@ -1029,3 +1029,115 @@ func TestSplice(t *testing.T) { nonempty := Splice(allStrings, 1, "1", "2") is.IsType(nonempty, allStrings, "type preserved") } + +func TestPull(t *testing.T) { + t.Parallel() + is := assert.New(t) + + result1 := Pull([]int{0, 1, 1, 2, 2, 2, 3, 3, 3}, 1) + result2 := Pull([]int{0, 1, 1, 2, 2, 2, 3, 3, 3}, 5) + result3 := Pull([]int{}, 1) + result4 := Pull([]int{0}, 0) + result5 := Pull([]string{"a", "b", "c", "a", "b", "c"}, "a", "c") + result6 := Pull([]string{"h", "e", "l", "l", "o"}, "a", "c") + + is.Equal(result1, []int{0, 2, 2, 2, 3, 3, 3}) + is.Equal(result2, []int{0, 1, 1, 2, 2, 2, 3, 3, 3}) + is.Equal(result3, []int{}) + is.Equal(result4, []int{}) + is.Equal(result5, []string{"b", "b"}) + is.Equal(result6, []string{"h", "e", "l", "l", "o"}) +} + +func TestPullAll(t *testing.T) { + t.Parallel() + is := assert.New(t) + + result1 := PullAll([]int{0, 1, 1, 2, 2, 2, 3, 3, 3}, []int{1}) + result2 := PullAll([]int{0, 1, 1, 2, 2, 2, 3, 3, 3}, []int{5}) + result3 := PullAll([]int{}, []int{1}) + result4 := PullAll([]int{0}, []int{0}) + result5 := PullAll([]string{"a", "b", "c", "a", "b", "c"}, []string{"a", "c"}) + result6 := PullAll([]string{"h", "e", "l", "l", "o"}, []string{"a", "c"}) + + is.Equal(result1, []int{0, 2, 2, 2, 3, 3, 3}) + is.Equal(result2, []int{0, 1, 1, 2, 2, 2, 3, 3, 3}) + is.Equal(result3, []int{}) + is.Equal(result4, []int{}) + is.Equal(result5, []string{"b", "b"}) + is.Equal(result6, []string{"h", "e", "l", "l", "o"}) +} + +func TestPullAllBy(t *testing.T) { + t.Parallel() + is := assert.New(t) + + result1 := PullAllBy([]int{1, 2, 3, 4, 5}, []int{2, 4}, func(n int) int { + return n + }) + is.Equal(result1, []int{1, 3, 5}) + + result2 := PullAllBy([]string{"apple", "banana", "pear", "kiwi"}, []int{5, 4}, func(s string) int { + return len(s) + }) + is.Equal(result2, []string{"banana"}) // "apple" и "kiwi" удалены, так как их длина равна 5 + + result3 := PullAllBy([]int{1, 2, 3, 4, 5}, []int{1, 9, 25}, func(n int) int { + return n * n + }) + is.Equal(result3, []int{2, 4}) // 1, 3 и 5 удалены, так как их квадраты совпадают с элементами для удаления + + result4 := PullAllBy([]string{"hello", "hi", "world", "welcome"}, []byte{'h'}, func(s string) byte { + return s[0] + }) + is.Equal(result4, []string{"world", "welcome"}) // "hello" и "hi" удалены, так как их первая буква 'h' +} + +func TestPullAt(t *testing.T) { + assert := assert.New(t) + + tests := []struct { + name string + slice []int + indexes []int + expectedRemoved []int + expectedRemaining []int + }{ + { + name: "Remove multiple elements", + slice: []int{1, 2, 3, 4, 5, 6}, + indexes: []int{1, 3, 5}, + expectedRemoved: []int{2, 4, 6}, + expectedRemaining: []int{1, 3, 5}, + }, + { + name: "Remove elements out of range", + slice: []int{1, 2, 3, 4, 5}, + indexes: []int{10, 15}, + expectedRemoved: []int{}, + expectedRemaining: []int{1, 2, 3, 4, 5}, + }, + { + name: "Empty slice", + slice: []int{}, + indexes: []int{0}, + expectedRemoved: []int{}, + expectedRemaining: []int{}, + }, + { + name: "Remove all elements", + slice: []int{1, 2, 3}, + indexes: []int{0, 1, 2}, + expectedRemoved: []int{1, 2, 3}, + expectedRemaining: []int{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + removed, remaining := PullAt(tt.slice, tt.indexes) + assert.ElementsMatch(tt.expectedRemoved, removed, "Removed elements do not match") + assert.ElementsMatch(tt.expectedRemaining, remaining, "Remaining elements do not match") + }) + } +} From f58312d6cc50d5974f87476bb532cb1606c1b4af Mon Sep 17 00:00:00 2001 From: SicParv1sMagna <92353726+SicParv1sMagna@users.noreply.github.com> Date: Wed, 11 Sep 2024 00:50:45 +0300 Subject: [PATCH 2/9] removed unneseccary comments --- slice_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/slice_test.go b/slice_test.go index 02b096b7..7ca0285a 100644 --- a/slice_test.go +++ b/slice_test.go @@ -1080,17 +1080,17 @@ func TestPullAllBy(t *testing.T) { result2 := PullAllBy([]string{"apple", "banana", "pear", "kiwi"}, []int{5, 4}, func(s string) int { return len(s) }) - is.Equal(result2, []string{"banana"}) // "apple" и "kiwi" удалены, так как их длина равна 5 + is.Equal(result2, []string{"banana"}) result3 := PullAllBy([]int{1, 2, 3, 4, 5}, []int{1, 9, 25}, func(n int) int { return n * n }) - is.Equal(result3, []int{2, 4}) // 1, 3 и 5 удалены, так как их квадраты совпадают с элементами для удаления + is.Equal(result3, []int{2, 4}) result4 := PullAllBy([]string{"hello", "hi", "world", "welcome"}, []byte{'h'}, func(s string) byte { return s[0] }) - is.Equal(result4, []string{"world", "welcome"}) // "hello" и "hi" удалены, так как их первая буква 'h' + is.Equal(result4, []string{"world", "welcome"}) } func TestPullAt(t *testing.T) { From f564cf91eb1ab042f437dd0885241f98890132cb Mon Sep 17 00:00:00 2001 From: tayushiev <92353726+SicParv1sMagna@users.noreply.github.com> Date: Sun, 22 Sep 2024 21:11:59 +0300 Subject: [PATCH 3/9] Update slice.go Co-authored-by: ccoVeille <3875889+ccoVeille@users.noreply.github.com> --- slice.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slice.go b/slice.go index 2568bc49..1959144f 100644 --- a/slice.go +++ b/slice.go @@ -694,7 +694,7 @@ func Splice[T any, Slice ~[]T](collection Slice, i int, elements ...T) Slice { return append(append(append(output, collection[:i]...), elements...), collection[i:]...) } -// Pull removes all given values from splice using SameValueZero for equality comparisons. +// Pull removes all given values from slice using SameValueZero for equality comparisons. func Pull[T comparable, Slice ~[]T](collection Slice, elements ...T) Slice { output := make(Slice, 0, len(collection)) From daa7ac6af536c166ef8eddaffdd3502c15f897d8 Mon Sep 17 00:00:00 2001 From: tayushiev <92353726+SicParv1sMagna@users.noreply.github.com> Date: Sun, 22 Sep 2024 21:12:59 +0300 Subject: [PATCH 4/9] Update slice.go Co-authored-by: ccoVeille <3875889+ccoVeille@users.noreply.github.com> --- slice.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slice.go b/slice.go index 1959144f..eab20a21 100644 --- a/slice.go +++ b/slice.go @@ -715,7 +715,7 @@ func Pull[T comparable, Slice ~[]T](collection Slice, elements ...T) Slice { return output } -// This method is like Pull except that it accepts an array of values to remove +// PullAll is like [Pull] except that it accepts an array of values to remove func PullAll[T comparable, Slice ~[]T](collection Slice, elements []T) Slice { output := make(Slice, 0, len(collection)) From f2cd79f6e7c75d803c59eab8ac9256dd35b713d5 Mon Sep 17 00:00:00 2001 From: tayushiev <92353726+SicParv1sMagna@users.noreply.github.com> Date: Sun, 22 Sep 2024 21:13:28 +0300 Subject: [PATCH 5/9] Update slice.go Co-authored-by: ccoVeille <3875889+ccoVeille@users.noreply.github.com> --- slice.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/slice.go b/slice.go index eab20a21..a0b34ecd 100644 --- a/slice.go +++ b/slice.go @@ -716,7 +716,8 @@ func Pull[T comparable, Slice ~[]T](collection Slice, elements ...T) Slice { } // PullAll is like [Pull] except that it accepts an array of values to remove -func PullAll[T comparable, Slice ~[]T](collection Slice, elements []T) Slice { +func PullAll[T comparable, Slice ~[]T](collection Slice, +...elements T) Slice { output := make(Slice, 0, len(collection)) for _, item := range collection { From 1e73203718e0b7640f8ed448f4c6afaf7cad96ca Mon Sep 17 00:00:00 2001 From: tayushiev <92353726+SicParv1sMagna@users.noreply.github.com> Date: Sun, 22 Sep 2024 21:14:51 +0300 Subject: [PATCH 6/9] Update slice.go Co-authored-by: ccoVeille <3875889+ccoVeille@users.noreply.github.com> --- slice.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/slice.go b/slice.go index a0b34ecd..236ba6e8 100644 --- a/slice.go +++ b/slice.go @@ -755,7 +755,7 @@ func PullAllBy[T any, U comparable, Slice ~[]T](collection Slice, elements []U, return output } -// This method removes elements from slice corresponding to indexes and returns +// PullAt removes elements from slice corresponding to indexes and returns // an array of removed elements func PullAt[T any](slice []T, indexes []int) ([]T, []T) { if len(slice) == 0 || len(indexes) == 0 { From e9e380cc2576fdddc12c7537b4f547ee9f931a81 Mon Sep 17 00:00:00 2001 From: SicParv1sMagna <92353726+SicParv1sMagna@users.noreply.github.com> Date: Sun, 22 Sep 2024 21:36:37 +0300 Subject: [PATCH 7/9] Review fix --- slice.go | 26 +++++--------------------- slice_test.go | 12 ++++++------ 2 files changed, 11 insertions(+), 27 deletions(-) diff --git a/slice.go b/slice.go index 236ba6e8..75ffa59c 100644 --- a/slice.go +++ b/slice.go @@ -1,6 +1,7 @@ package lo import ( + "slices" "sort" "github.com/samber/lo/internal/constraints" @@ -699,15 +700,7 @@ func Pull[T comparable, Slice ~[]T](collection Slice, elements ...T) Slice { output := make(Slice, 0, len(collection)) for _, item := range collection { - shouldRemove := false - for _, element := range elements { - if item == element { - shouldRemove = true - break - } - } - - if !shouldRemove { + if !slices.Contains(elements, item) { output = append(output, item) } } @@ -716,20 +709,11 @@ func Pull[T comparable, Slice ~[]T](collection Slice, elements ...T) Slice { } // PullAll is like [Pull] except that it accepts an array of values to remove -func PullAll[T comparable, Slice ~[]T](collection Slice, -...elements T) Slice { +func PullAll[T comparable, Slice ~[]T](collection Slice, elements ...T) Slice { output := make(Slice, 0, len(collection)) for _, item := range collection { - shouldRemove := false - for _, element := range elements { - if item == element { - shouldRemove = true - break - } - } - - if !shouldRemove { + if !slices.Contains(elements, item) { output = append(output, item) } } @@ -737,7 +721,7 @@ func PullAll[T comparable, Slice ~[]T](collection Slice, return output } -// This method is like PullAll except that it accepts iteratee which is invoked +// PullAllBy is like [PullAll] except that it accepts iteratee which is invoked // for each element of slice and values to generate the criterion by which they're compared func PullAllBy[T any, U comparable, Slice ~[]T](collection Slice, elements []U, iteratee func(T) U) Slice { elementCriteria := make(map[U]struct{}, len(elements)) diff --git a/slice_test.go b/slice_test.go index 7ca0285a..8428355a 100644 --- a/slice_test.go +++ b/slice_test.go @@ -1053,12 +1053,12 @@ func TestPullAll(t *testing.T) { t.Parallel() is := assert.New(t) - result1 := PullAll([]int{0, 1, 1, 2, 2, 2, 3, 3, 3}, []int{1}) - result2 := PullAll([]int{0, 1, 1, 2, 2, 2, 3, 3, 3}, []int{5}) - result3 := PullAll([]int{}, []int{1}) - result4 := PullAll([]int{0}, []int{0}) - result5 := PullAll([]string{"a", "b", "c", "a", "b", "c"}, []string{"a", "c"}) - result6 := PullAll([]string{"h", "e", "l", "l", "o"}, []string{"a", "c"}) + result1 := PullAll([]int{0, 1, 1, 2, 2, 2, 3, 3, 3}, 1) + result2 := PullAll([]int{0, 1, 1, 2, 2, 2, 3, 3, 3}, 5) + result3 := PullAll([]int{}, 1) + result4 := PullAll([]int{0}, 0) + result5 := PullAll([]string{"a", "b", "c", "a", "b", "c"}, "a", "c") + result6 := PullAll([]string{"h", "e", "l", "l", "o"}, "a", "c") is.Equal(result1, []int{0, 2, 2, 2, 3, 3, 3}) is.Equal(result2, []int{0, 1, 1, 2, 2, 2, 3, 3, 3}) From 081bb3b79c5c8efdd579afb5e997f96768e6ab04 Mon Sep 17 00:00:00 2001 From: SicParv1sMagna <92353726+SicParv1sMagna@users.noreply.github.com> Date: Sun, 22 Sep 2024 22:07:52 +0300 Subject: [PATCH 8/9] Removed PullAll and add iteratee --- slice.go | 21 ++++----------------- slice_test.go | 37 +++++++++---------------------------- 2 files changed, 13 insertions(+), 45 deletions(-) diff --git a/slice.go b/slice.go index 75ffa59c..25e5250c 100644 --- a/slice.go +++ b/slice.go @@ -708,22 +708,9 @@ func Pull[T comparable, Slice ~[]T](collection Slice, elements ...T) Slice { return output } -// PullAll is like [Pull] except that it accepts an array of values to remove -func PullAll[T comparable, Slice ~[]T](collection Slice, elements ...T) Slice { - output := make(Slice, 0, len(collection)) - - for _, item := range collection { - if !slices.Contains(elements, item) { - output = append(output, item) - } - } - - return output -} - -// PullAllBy is like [PullAll] except that it accepts iteratee which is invoked +// PullAllBy is like [Pull] except that it accepts iteratee which is invoked // for each element of slice and values to generate the criterion by which they're compared -func PullAllBy[T any, U comparable, Slice ~[]T](collection Slice, elements []U, iteratee func(T) U) Slice { +func PullAllBy[T any, U comparable, Slice ~[]T](collection Slice, iteratee func(T) U, elements ...U) Slice { elementCriteria := make(map[U]struct{}, len(elements)) for _, element := range elements { elementCriteria[element] = struct{}{} @@ -741,7 +728,7 @@ func PullAllBy[T any, U comparable, Slice ~[]T](collection Slice, elements []U, // PullAt removes elements from slice corresponding to indexes and returns // an array of removed elements -func PullAt[T any](slice []T, indexes []int) ([]T, []T) { +func PullAt[T any](slice []T, indexes ...int) ([]T, []T) { if len(slice) == 0 || len(indexes) == 0 { return nil, slice } @@ -756,7 +743,7 @@ func PullAt[T any](slice []T, indexes []int) ([]T, []T) { } var removed []T - var output []T + output := make([]T, 0, len(slice)-len(indexMap)) for i, v := range slice { if _, ok := indexMap[i]; ok { diff --git a/slice_test.go b/slice_test.go index 8428355a..ef522566 100644 --- a/slice_test.go +++ b/slice_test.go @@ -1049,47 +1049,28 @@ func TestPull(t *testing.T) { is.Equal(result6, []string{"h", "e", "l", "l", "o"}) } -func TestPullAll(t *testing.T) { - t.Parallel() - is := assert.New(t) - - result1 := PullAll([]int{0, 1, 1, 2, 2, 2, 3, 3, 3}, 1) - result2 := PullAll([]int{0, 1, 1, 2, 2, 2, 3, 3, 3}, 5) - result3 := PullAll([]int{}, 1) - result4 := PullAll([]int{0}, 0) - result5 := PullAll([]string{"a", "b", "c", "a", "b", "c"}, "a", "c") - result6 := PullAll([]string{"h", "e", "l", "l", "o"}, "a", "c") - - is.Equal(result1, []int{0, 2, 2, 2, 3, 3, 3}) - is.Equal(result2, []int{0, 1, 1, 2, 2, 2, 3, 3, 3}) - is.Equal(result3, []int{}) - is.Equal(result4, []int{}) - is.Equal(result5, []string{"b", "b"}) - is.Equal(result6, []string{"h", "e", "l", "l", "o"}) -} - func TestPullAllBy(t *testing.T) { t.Parallel() is := assert.New(t) - result1 := PullAllBy([]int{1, 2, 3, 4, 5}, []int{2, 4}, func(n int) int { + result1 := PullAllBy([]int{1, 2, 3, 4, 5}, func(n int) int { return n - }) + }, 2, 4) is.Equal(result1, []int{1, 3, 5}) - result2 := PullAllBy([]string{"apple", "banana", "pear", "kiwi"}, []int{5, 4}, func(s string) int { + result2 := PullAllBy([]string{"apple", "banana", "pear", "kiwi"}, func(s string) int { return len(s) - }) + }, 5, 4) is.Equal(result2, []string{"banana"}) - result3 := PullAllBy([]int{1, 2, 3, 4, 5}, []int{1, 9, 25}, func(n int) int { + result3 := PullAllBy([]int{1, 2, 3, 4, 5}, func(n int) int { return n * n - }) + }, 1, 9, 25) is.Equal(result3, []int{2, 4}) - result4 := PullAllBy([]string{"hello", "hi", "world", "welcome"}, []byte{'h'}, func(s string) byte { + result4 := PullAllBy([]string{"hello", "hi", "world", "welcome"}, func(s string) byte { return s[0] - }) + }, 'h') is.Equal(result4, []string{"world", "welcome"}) } @@ -1135,7 +1116,7 @@ func TestPullAt(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - removed, remaining := PullAt(tt.slice, tt.indexes) + removed, remaining := PullAt(tt.slice, tt.indexes...) assert.ElementsMatch(tt.expectedRemoved, removed, "Removed elements do not match") assert.ElementsMatch(tt.expectedRemaining, remaining, "Remaining elements do not match") }) From 1cb478a3263f5bf680c01593cdcd8d23132037a1 Mon Sep 17 00:00:00 2001 From: SicParv1sMagna <92353726+SicParv1sMagna@users.noreply.github.com> Date: Sun, 22 Sep 2024 22:54:28 +0300 Subject: [PATCH 9/9] added some test cases --- slice.go | 2 +- slice_test.go | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/slice.go b/slice.go index 25e5250c..798b5ebb 100644 --- a/slice.go +++ b/slice.go @@ -695,7 +695,7 @@ func Splice[T any, Slice ~[]T](collection Slice, i int, elements ...T) Slice { return append(append(append(output, collection[:i]...), elements...), collection[i:]...) } -// Pull removes all given values from slice using SameValueZero for equality comparisons. +// Pull removes all given values from slice func Pull[T comparable, Slice ~[]T](collection Slice, elements ...T) Slice { output := make(Slice, 0, len(collection)) diff --git a/slice_test.go b/slice_test.go index ef522566..4842810b 100644 --- a/slice_test.go +++ b/slice_test.go @@ -1112,6 +1112,27 @@ func TestPullAt(t *testing.T) { expectedRemoved: []int{1, 2, 3}, expectedRemaining: []int{}, }, + { + name: "Passing negative indexes", + slice: []int{1, 2, 3}, + indexes: []int{-1, -2}, + expectedRemoved: []int{}, + expectedRemaining: []int{1, 2, 3}, + }, + { + name: "Passing more indexes than slice contains", + slice: []int{1}, + indexes: []int{0, 1, 2}, + expectedRemoved: []int{1}, + expectedRemaining: []int{}, + }, + { + name: "Passing same indexes", + slice: []int{1}, + indexes: []int{0, 0, 0}, + expectedRemoved: []int{1}, + expectedRemaining: []int{}, + }, } for _, tt := range tests {