types/views: add SliceEqualAnyOrderFunc
Some checks are pending
checklocks / checklocks (push) Waiting to run
CodeQL / Analyze (go) (push) Waiting to run
Dockerfile build / deploy (push) Waiting to run
CI / race-root-integration (1/4) (push) Waiting to run
CI / race-root-integration (2/4) (push) Waiting to run
CI / race-root-integration (3/4) (push) Waiting to run
CI / race-root-integration (4/4) (push) Waiting to run
CI / test (-coverprofile=/tmp/coverage.out, amd64) (push) Waiting to run
CI / test (-race, amd64, 1/3) (push) Waiting to run
CI / test (-race, amd64, 2/3) (push) Waiting to run
CI / test (-race, amd64, 3/3) (push) Waiting to run
CI / test (386) (push) Waiting to run
CI / windows (push) Waiting to run
CI / privileged (push) Waiting to run
CI / vm (push) Waiting to run
CI / race-build (push) Waiting to run
CI / cross (386, linux) (push) Waiting to run
CI / cross (amd64, darwin) (push) Waiting to run
CI / cross (amd64, freebsd) (push) Waiting to run
CI / cross (amd64, openbsd) (push) Waiting to run
CI / cross (amd64, windows) (push) Waiting to run
CI / cross (arm, 5, linux) (push) Waiting to run
CI / cross (arm, 7, linux) (push) Waiting to run
CI / cross (arm64, darwin) (push) Waiting to run
CI / cross (arm64, linux) (push) Waiting to run
CI / cross (arm64, windows) (push) Waiting to run
CI / cross (loong64, linux) (push) Waiting to run
CI / ios (push) Waiting to run
CI / crossmin (amd64, plan9) (push) Waiting to run
CI / crossmin (ppc64, aix) (push) Waiting to run
CI / android (push) Waiting to run
CI / wasm (push) Waiting to run
CI / tailscale_go (push) Waiting to run
CI / fuzz (push) Waiting to run
CI / depaware (push) Waiting to run
CI / go_generate (push) Waiting to run
CI / go_mod_tidy (push) Waiting to run
CI / licenses (push) Waiting to run
CI / staticcheck (386, windows) (push) Waiting to run
CI / staticcheck (amd64, darwin) (push) Waiting to run
CI / staticcheck (amd64, linux) (push) Waiting to run
CI / staticcheck (amd64, windows) (push) Waiting to run
CI / notify_slack (push) Blocked by required conditions
CI / check_mergeability (push) Blocked by required conditions

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"})