diff --git a/util/slicesx/slicesx.go b/util/slicesx/slicesx.go index 2e4ac9567..d72722ce0 100644 --- a/util/slicesx/slicesx.go +++ b/util/slicesx/slicesx.go @@ -77,3 +77,17 @@ func EqualSameNil[S ~[]E, E comparable](s1, s2 S) bool { } return true } + +// Filter calls fn with each element of the provided src slice, and appends the +// element to dst if fn returns true. +// +// dst can be nil to allocate a new slice, or set to src[:0] to filter in-place +// without allocating. +func Filter[S ~[]T, T any](dst, src S, fn func(T) bool) S { + for _, x := range src { + if fn(x) { + dst = append(dst, x) + } + } + return dst +} diff --git a/util/slicesx/slicesx_test.go b/util/slicesx/slicesx_test.go index 0d206b364..b4806f6b3 100644 --- a/util/slicesx/slicesx_test.go +++ b/util/slicesx/slicesx_test.go @@ -97,3 +97,42 @@ func TestEqualSameNil(t *testing.T) { c.Check(EqualSameNil([]string{}, nil), qt.Equals, false) c.Check(EqualSameNil[[]string](nil, nil), qt.Equals, true) } + +func TestFilter(t *testing.T) { + var sl []int + for i := 1; i <= 10; i++ { + sl = append(sl, i) + } + + evens := Filter(nil, sl, func(elem int) bool { + return elem%2 == 0 + }) + + want := []int{2, 4, 6, 8, 10} + if !reflect.DeepEqual(evens, want) { + t.Errorf("evens: got %v, want %v", evens, want) + } +} + +func TestFilterNoAllocations(t *testing.T) { + var sl []int + for i := 1; i <= 10; i++ { + sl = append(sl, i) + } + + want := []int{2, 4, 6, 8, 10} + allocs := testing.AllocsPerRun(1000, func() { + src := slices.Clone(sl) + evens := Filter(src[:0], src, func(elem int) bool { + return elem%2 == 0 + }) + if !slices.Equal(evens, want) { + t.Errorf("evens: got %v, want %v", evens, want) + } + }) + + // 1 alloc for 'src', nothing else + if allocs != 1 { + t.Fatalf("got %.4f allocs, want 1", allocs) + } +}