diff --git a/types/views/views.go b/types/views/views.go index 0f53313c7..40d8811f5 100644 --- a/types/views/views.go +++ b/types/views/views.go @@ -381,6 +381,29 @@ func SliceEqualAnyOrderFunc[T any, V comparable](a, b Slice[T], cmp func(T) V) b return true } + // For a small number of items, avoid the allocation of a map and just + // do the quadratic thing. We can also only check the items between + // diffStart and the end. + nRemain := a.Len() - diffStart + if nRemain <= 5 { + maxLen := a.Len() // same as b.Len() + for i := diffStart; i < maxLen; i++ { + av := cmp(a.At(i)) + found := false + for j := diffStart; j < maxLen; j++ { + bv := cmp(b.At(j)) + if av == bv { + found = true + break + } + } + if !found { + return false + } + } + return true + } + // count the occurrences of remaining values and compare valueCount := make(map[V]int) for i, n := diffStart, a.Len(); i < n; i++ { diff --git a/types/views/views_test.go b/types/views/views_test.go index f290670fb..70e021aa4 100644 --- a/types/views/views_test.go +++ b/types/views/views_test.go @@ -188,6 +188,15 @@ func TestSliceEqualAnyOrderFunc(t *testing.T) { // Nothing shared c.Check(SliceEqualAnyOrderFunc(v, ncFrom("baz", "qux"), cmp), qt.Equals, false) + + // Long slice that matches + longSlice := ncFrom("a", "b", "c", "d", "e", "f", "g", "h", "i", "j") + longSame := ncFrom("b", "a", "c", "d", "e", "f", "g", "h", "i", "j") // first 2 elems swapped + c.Check(SliceEqualAnyOrderFunc(longSlice, longSame, cmp), qt.Equals, true) + + // Long difference; past the quadratic limit + longDiff := ncFrom("b", "a", "c", "d", "e", "f", "g", "h", "i", "k") // differs at end + c.Check(SliceEqualAnyOrderFunc(longSlice, longDiff, cmp), qt.Equals, false) } func TestSliceEqual(t *testing.T) {