-
Notifications
You must be signed in to change notification settings - Fork 35
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
slice.Remove and slice.RemoveValue added
- Loading branch information
1 parent
8fb0fc9
commit 28ced0e
Showing
2 changed files
with
182 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package slice | ||
|
||
// Remove removes the given index range from the given slice, preserving element order. | ||
// It is the safer version of go/x/exp/slices.Delete() as it will | ||
// not panic on invalid slice ranges. Nil and empty slices are also safe. | ||
// Remove modifies the contents of the given slice and performs no allocations | ||
// or copies. | ||
func Remove[T comparable](slice []T, i, j int) []T { | ||
// Nothing to do | ||
if len(slice) == 0 { | ||
return slice | ||
} | ||
// Prevent invalid slice indexing | ||
if i < 0 || j > len(slice)-1 { | ||
return slice | ||
} | ||
// Note: this modifies the slice | ||
return append(slice[:i], slice[j:]...) | ||
} | ||
|
||
// RemoveAll removes all instances of the given value in the given slice, in place, preserving element order. | ||
// RemoveAll modifies the contents of the given slice. | ||
// If value is not present in the slice, or if the slice is nil or empty, the slice is returned unaltered. | ||
// RemoveAll performs no allocations or copies. | ||
// Note: DO NOT use this for long-lived slices as the elements removed are not garbage collected | ||
func RemoveAll[T comparable](slice []T, value T) []T { | ||
// Nothing to do | ||
if len(slice) == 0 { | ||
return slice | ||
} | ||
b := slice[:0] | ||
for _, v := range slice { | ||
if v != value { | ||
b = append(b, v) | ||
} | ||
} | ||
return b | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
package slice | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestRemove(t *testing.T) { | ||
for _, test := range []struct { | ||
name string | ||
i int | ||
j int | ||
inSlice []int | ||
outSlice []int | ||
}{{ | ||
name: "empty slice", | ||
inSlice: []int{}, | ||
outSlice: []int{}, | ||
}, { | ||
name: "out of range should not panic or modify the slice", | ||
i: 4, | ||
j: 5, | ||
inSlice: []int{2}, | ||
outSlice: []int{2}, | ||
}, { | ||
name: "correct start index but invalid end index", | ||
i: 0, | ||
j: 2, | ||
inSlice: []int{2}, | ||
outSlice: []int{2}, | ||
}, { | ||
name: "single value remove", | ||
i: 0, | ||
j: 1, | ||
inSlice: []int{2}, | ||
outSlice: []int{}, | ||
}, { | ||
name: "middle value removed", | ||
i: 1, | ||
j: 2, | ||
inSlice: []int{1, 2, 3}, | ||
outSlice: []int{1, 3}, | ||
}, { | ||
name: "multi-value remove", | ||
i: 1, | ||
j: 3, | ||
inSlice: []int{1, 2, 3}, | ||
outSlice: []int{1}, | ||
}, { | ||
name: "nil check", | ||
i: 1, | ||
j: 3, | ||
inSlice: nil, | ||
outSlice: nil, | ||
}, {}} { | ||
t.Run(test.name, func(t *testing.T) { | ||
got := Remove(test.inSlice, test.i, test.j) | ||
for i, expected := range test.outSlice { | ||
assert.Equal(t, expected, got[i]) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestRemoveAll(t *testing.T) { | ||
for _, test := range []struct { | ||
name string | ||
remove string | ||
inSlice []string | ||
outSlice []string | ||
}{{ | ||
name: "empty slice", | ||
remove: "localhost", | ||
inSlice: []string{}, | ||
outSlice: []string{}, | ||
}, { | ||
name: "nil check", | ||
remove: "localhost", | ||
inSlice: nil, | ||
outSlice: nil, | ||
}, { | ||
name: "only value should be removed", | ||
remove: "localhost", | ||
inSlice: []string{"localhost"}, | ||
outSlice: []string{}, | ||
}, { | ||
name: "beginning of list", | ||
remove: "localhost", | ||
inSlice: []string{"localhost", "foo"}, | ||
outSlice: []string{"foo"}, | ||
}, { | ||
name: "end of list", | ||
remove: "localhost", | ||
inSlice: []string{"foo", "localhost"}, | ||
outSlice: []string{"foo"}, | ||
}, { | ||
name: "middle of the list", | ||
remove: "localhost", | ||
inSlice: []string{"foo", "localhost", "bar"}, | ||
outSlice: []string{"foo", "bar"}, | ||
}, { | ||
name: "all instances should be removed", | ||
remove: "localhost", | ||
inSlice: []string{"localhost", "foo", "localhost"}, | ||
outSlice: []string{"foo"}, | ||
}, { | ||
name: "order and repeats shouldn't matter", | ||
remove: "localhost", | ||
inSlice: []string{"foo", "localhost", "bar", "localhost", "localhost", "baz.com"}, | ||
outSlice: []string{"foo", "bar", "baz.com"}, | ||
}} { | ||
t.Run(test.name, func(t *testing.T) { | ||
out := RemoveAll(test.inSlice, test.remove) | ||
for i, expected := range test.outSlice { | ||
assert.Equal(t, expected, out[i]) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
var out []string | ||
|
||
func BenchmarkRemove(b *testing.B) { | ||
o := []string{} | ||
in := []string{"localhost", "mg.example.com", "testlabs.io", "localhost", "m.example.com.br", "localhost", "localhost"} | ||
|
||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
o = Remove(in, 2, 4) | ||
} | ||
out = o | ||
} | ||
|
||
func BenchmarkRemoveAll(b *testing.B) { | ||
o := []string{} | ||
in := []string{"localhost", "mg.example.com", "testlabs.io", "localhost", "m.example.com.br", "localhost", "localhost"} | ||
|
||
b.ResetTimer() | ||
for i := 0; i < b.N; i++ { | ||
o = RemoveAll(in, "localhost") | ||
} | ||
out = o | ||
} |