Skip to content

Commit

Permalink
types/views: add SliceEqualAnyOrderFunc
Browse files Browse the repository at this point in the history
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
  • Loading branch information
andrew-d committed Jan 9, 2025
1 parent a51672c commit 7fa07f3
Show file tree
Hide file tree
Showing 2 changed files with 72 additions and 0 deletions.
35 changes: 35 additions & 0 deletions types/views/views.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 37 additions & 0 deletions types/views/views_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"})
Expand Down

0 comments on commit 7fa07f3

Please sign in to comment.