Skip to content

Commit

Permalink
Added pull methods from lodash
Browse files Browse the repository at this point in the history
  • Loading branch information
SicParv1sMagna committed Sep 10, 2024
1 parent 4ebf484 commit b47cd50
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 0 deletions.
90 changes: 90 additions & 0 deletions slice.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
112 changes: 112 additions & 0 deletions slice_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
})
}
}

0 comments on commit b47cd50

Please sign in to comment.