From 7fa07f34169b11a02e44165fd875e48b2e2e211d Mon Sep 17 00:00:00 2001 From: Andrew Dunham Date: Thu, 9 Jan 2025 16:03:52 -0500 Subject: [PATCH] types/views: add SliceEqualAnyOrderFunc Extracted from some code written in the other repo. Updates tailscale/corp#25479 Signed-off-by: Andrew Dunham Change-Id: I6df062fdffa1705524caa44ac3b6f2788cf64595 --- types/views/views.go | 35 +++++++++++++++++++++++++++++++++++ types/views/views_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 72 insertions(+) diff --git a/types/views/views.go b/types/views/views.go index 4addc64487f76..0f53313c76fe1 100644 --- a/types/views/views.go +++ b/types/views/views.go @@ -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 diff --git a/types/views/views_test.go b/types/views/views_test.go index 51b086a4e05d3..f290670fb12fc 100644 --- a/types/views/views_test.go +++ b/types/views/views_test.go @@ -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"})