types/views: add SliceEqualAnyOrderFunc

Extracted from some code written in the other repo.

Updates tailscale/corp#25479

Signed-off-by: Andrew Dunham <andrew@du.nham.ca>
Change-Id: I6df062fdffa1705524caa44ac3b6f2788cf64595
This commit is contained in:
Andrew Dunham 2025-01-09 16:03:52 -05:00
parent a51672cafd
commit 7fa07f3416
2 changed files with 72 additions and 0 deletions

View File

@ -360,6 +360,41 @@ func SliceEqualAnyOrder[T comparable](a, b Slice[T]) bool {
return true
}
// SliceEqualAnyOrderFunc reports whether a and b contain the same elements,
// regardless of order. The underlying slices for a and b can be nil.
//
// The provided function should return a comparable value for each element.
func SliceEqualAnyOrderFunc[T any, V comparable](a, b Slice[T], cmp func(T) V) 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++ {
av := cmp(a.At(diffStart))
bv := cmp(b.At(diffStart))
if av != bv {
break
}
}
if diffStart == a.Len() {
return true
}
// count the occurrences of remaining values and compare
valueCount := make(map[V]int)
for i, n := diffStart, a.Len(); i < n; i++ {
valueCount[cmp(a.At(i))]++
valueCount[cmp(b.At(i))]--
}
for _, count := range valueCount {
if count != 0 {
return false
}
}
return true
}
// MapSlice is a view over a map whose values are slices.
type MapSlice[K comparable, V any] struct {
// ж is the underlying mutable value, named with a hard-to-type

View File

@ -153,6 +153,43 @@ func TestViewUtils(t *testing.T) {
qt.Equals, true)
}
func TestSliceEqualAnyOrderFunc(t *testing.T) {
type nc struct {
_ structs.Incomparable
v string
}
// ncFrom returns a Slice[nc] from a slice of []string
ncFrom := func(s ...string) Slice[nc] {
var out []nc
for _, v := range s {
out = append(out, nc{v: v})
}
return SliceOf(out)
}
// cmp returns a comparable value for a nc
cmp := func(a nc) string { return a.v }
v := ncFrom("foo", "bar")
c := qt.New(t)
// Simple case of slice equal to itself.
c.Check(SliceEqualAnyOrderFunc(v, v, cmp), qt.Equals, true)
// Different order.
c.Check(SliceEqualAnyOrderFunc(v, ncFrom("bar", "foo"), cmp), qt.Equals, true)
// Different values, same length
c.Check(SliceEqualAnyOrderFunc(v, ncFrom("foo", "baz"), cmp), qt.Equals, false)
// Different values, different length
c.Check(SliceEqualAnyOrderFunc(v, ncFrom("foo"), cmp), qt.Equals, false)
// Nothing shared
c.Check(SliceEqualAnyOrderFunc(v, ncFrom("baz", "qux"), cmp), qt.Equals, false)
}
func TestSliceEqual(t *testing.T) {
a := SliceOf([]string{"foo", "bar"})
b := SliceOf([]string{"foo", "bar"})