aboutsummaryrefslogtreecommitdiffstats
path: root/pkg/bisect/minimize
diff options
context:
space:
mode:
authorAleksandr Nogikh <nogikh@google.com>2024-08-22 17:48:30 +0200
committerAleksandr Nogikh <nogikh@google.com>2024-08-27 13:41:43 +0000
commitdee56964d94d28f3f609bc4f62042dd6d1e153ae (patch)
tree7162e08ef8447fe022e1e195cde25bcd189787e2 /pkg/bisect/minimize
parenta1a7b2f0375c39f024ed223545e126e1da17463b (diff)
pkg/bisect/minimize: introduce SliceWithFixed
The function is similar to minimize.Slice(), but it allows to designate a subset of slice elements that must always remain.
Diffstat (limited to 'pkg/bisect/minimize')
-rw-r--r--pkg/bisect/minimize/slice.go45
-rw-r--r--pkg/bisect/minimize/slice_test.go28
2 files changed, 73 insertions, 0 deletions
diff --git a/pkg/bisect/minimize/slice.go b/pkg/bisect/minimize/slice.go
index 76fe82101..fee125542 100644
--- a/pkg/bisect/minimize/slice.go
+++ b/pkg/bisect/minimize/slice.go
@@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"math"
+ "slices"
"strings"
)
@@ -45,6 +46,50 @@ func Slice[T any](config Config[T], slice []T) ([]T, error) {
return ctx.bisect()
}
+// SliceWithFixed behaves like Slice, but also allows to designate the elements that
+// must always remain (the "fixed" ones).
+func SliceWithFixed[T any](config Config[T], slice []T, fixed func(T) bool) ([]T, error) {
+ var freeIdx, fixedIdx []int
+ for i := 0; i < len(slice); i++ {
+ if fixed(slice[i]) {
+ fixedIdx = append(fixedIdx, i)
+ } else {
+ freeIdx = append(freeIdx, i)
+ }
+ }
+ if len(freeIdx) == 0 {
+ return slice, nil
+ }
+ convert := func(idx []int) []T {
+ ret := make([]T, 0, len(idx)+len(fixedIdx))
+ idx, fixedIdx := slices.Clone(idx), slices.Clone(fixedIdx)
+ for len(idx)+len(fixedIdx) > 0 {
+ if len(idx) > 0 && (len(fixedIdx) == 0 ||
+ len(fixedIdx) > 0 && idx[0] < fixedIdx[0]) {
+ ret = append(ret, slice[idx[0]])
+ idx = idx[1:]
+ } else {
+ ret = append(ret, slice[fixedIdx[0]])
+ fixedIdx = fixedIdx[1:]
+ }
+ }
+ return ret
+ }
+ newConfig := Config[int]{
+ MaxSteps: config.MaxSteps,
+ MaxChunks: config.MaxChunks,
+ Pred: func(idx []int) (bool, error) {
+ return config.Pred(convert(idx))
+ },
+ Logf: config.Logf,
+ }
+ result, err := Slice[int](newConfig, freeIdx)
+ if err != nil {
+ return nil, err
+ }
+ return convert(result), nil
+}
+
type sliceCtx[T any] struct {
Config[T]
chunks []*arrayChunk[T]
diff --git a/pkg/bisect/minimize/slice_test.go b/pkg/bisect/minimize/slice_test.go
index 4e3e65202..cd6ec37c6 100644
--- a/pkg/bisect/minimize/slice_test.go
+++ b/pkg/bisect/minimize/slice_test.go
@@ -42,6 +42,34 @@ func TestBisectSliceFull(t *testing.T) {
assert.Equal(t, ret, array)
}
+func TestBisectSliceWithFixed(t *testing.T) {
+ t.Parallel()
+ array := make([]int, 100)
+ for i := 0; i < 100; i++ {
+ array[i] = i
+ }
+ ret, err := SliceWithFixed(Config[int]{
+ Pred: func(arr []int) (bool, error) {
+ // Let's ensure that there are always fixed elements.
+ values := map[int]bool{}
+ for _, v := range arr {
+ values[v] = true
+ }
+ if !values[0] || !values[20] || !values[40] {
+ t.Fatal("predicate step without fixed elements")
+ }
+ // And also require the elements 25 and 50.
+ return values[25] && values[50], nil
+ },
+ Logf: t.Logf,
+ }, array, func(elem int) bool {
+ // Let's keep these 3 elements.
+ return elem == 0 || elem == 20 || elem == 40
+ })
+ assert.NoError(t, err)
+ assert.Equal(t, []int{0, 20, 25, 40, 50}, ret)
+}
+
func TestBisectRandomSlice(t *testing.T) {
t.Parallel()
r := rand.New(testutil.RandSource(t))