diff --git a/types/views/views.go b/types/views/views.go index 92d230674..d0310a470 100644 --- a/types/views/views.go +++ b/types/views/views.go @@ -170,6 +170,37 @@ func SliceContains[T comparable](v Slice[T], e T) bool { return false } +// SliceEqualAnyOrder reports whether a and b contain the same elements, regardless of order. +// The underlying slices for a and b can be nil. +func SliceEqualAnyOrder[T comparable](a, b Slice[T]) bool { + if a.Len() != b.Len() { + return false + } + + var diffStart int // beginning index where a and b differ + for n := a.Len(); diffStart < n; diffStart++ { + if a.At(diffStart) != b.At(diffStart) { + break + } + } + if diffStart == a.Len() { + return true + } + + // count the occurrences of remaining values and compare + valueCount := make(map[T]int) + for i, n := diffStart, a.Len(); i < n; i++ { + valueCount[a.At(i)]++ + valueCount[b.At(i)]-- + } + for _, count := range valueCount { + if count != 0 { + return false + } + } + return true +} + // IPPrefixSlice is a read-only accessor for a slice of netip.Prefix. type IPPrefixSlice struct { ж Slice[netip.Prefix] diff --git a/types/views/views_test.go b/types/views/views_test.go index 75fdc0f59..63902fd8b 100644 --- a/types/views/views_test.go +++ b/types/views/views_test.go @@ -125,4 +125,8 @@ func TestViewUtils(t *testing.T) { c.Check(v.IndexFunc(func(s string) bool { return strings.HasPrefix(s, "z") }), qt.Equals, -1) c.Check(SliceContains(v, "bar"), qt.Equals, true) c.Check(SliceContains(v, "baz"), qt.Equals, false) + c.Check(SliceEqualAnyOrder(v, v), qt.Equals, true) + c.Check(SliceEqualAnyOrder(v, SliceOf([]string{"bar", "foo"})), qt.Equals, true) + c.Check(SliceEqualAnyOrder(v, SliceOf([]string{"foo"})), qt.Equals, false) + c.Check(SliceEqualAnyOrder(SliceOf([]string{"a", "a", "b"}), SliceOf([]string{"a", "b", "b"})), qt.Equals, false) }