Skip to content

Commit 7fa07f3

Browse files
committed
types/views: add SliceEqualAnyOrderFunc
Extracted from some code written in the other repo. Updates tailscale/corp#25479 Signed-off-by: Andrew Dunham <[email protected]> Change-Id: I6df062fdffa1705524caa44ac3b6f2788cf64595
1 parent a51672c commit 7fa07f3

File tree

2 files changed

+72
-0
lines changed

2 files changed

+72
-0
lines changed

types/views/views.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,41 @@ func SliceEqualAnyOrder[T comparable](a, b Slice[T]) bool {
360360
return true
361361
}
362362

363+
// SliceEqualAnyOrderFunc reports whether a and b contain the same elements,
364+
// regardless of order. The underlying slices for a and b can be nil.
365+
//
366+
// The provided function should return a comparable value for each element.
367+
func SliceEqualAnyOrderFunc[T any, V comparable](a, b Slice[T], cmp func(T) V) bool {
368+
if a.Len() != b.Len() {
369+
return false
370+
}
371+
372+
var diffStart int // beginning index where a and b differ
373+
for n := a.Len(); diffStart < n; diffStart++ {
374+
av := cmp(a.At(diffStart))
375+
bv := cmp(b.At(diffStart))
376+
if av != bv {
377+
break
378+
}
379+
}
380+
if diffStart == a.Len() {
381+
return true
382+
}
383+
384+
// count the occurrences of remaining values and compare
385+
valueCount := make(map[V]int)
386+
for i, n := diffStart, a.Len(); i < n; i++ {
387+
valueCount[cmp(a.At(i))]++
388+
valueCount[cmp(b.At(i))]--
389+
}
390+
for _, count := range valueCount {
391+
if count != 0 {
392+
return false
393+
}
394+
}
395+
return true
396+
}
397+
363398
// MapSlice is a view over a map whose values are slices.
364399
type MapSlice[K comparable, V any] struct {
365400
// ж is the underlying mutable value, named with a hard-to-type

types/views/views_test.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,43 @@ func TestViewUtils(t *testing.T) {
153153
qt.Equals, true)
154154
}
155155

156+
func TestSliceEqualAnyOrderFunc(t *testing.T) {
157+
type nc struct {
158+
_ structs.Incomparable
159+
v string
160+
}
161+
162+
// ncFrom returns a Slice[nc] from a slice of []string
163+
ncFrom := func(s ...string) Slice[nc] {
164+
var out []nc
165+
for _, v := range s {
166+
out = append(out, nc{v: v})
167+
}
168+
return SliceOf(out)
169+
}
170+
171+
// cmp returns a comparable value for a nc
172+
cmp := func(a nc) string { return a.v }
173+
174+
v := ncFrom("foo", "bar")
175+
c := qt.New(t)
176+
177+
// Simple case of slice equal to itself.
178+
c.Check(SliceEqualAnyOrderFunc(v, v, cmp), qt.Equals, true)
179+
180+
// Different order.
181+
c.Check(SliceEqualAnyOrderFunc(v, ncFrom("bar", "foo"), cmp), qt.Equals, true)
182+
183+
// Different values, same length
184+
c.Check(SliceEqualAnyOrderFunc(v, ncFrom("foo", "baz"), cmp), qt.Equals, false)
185+
186+
// Different values, different length
187+
c.Check(SliceEqualAnyOrderFunc(v, ncFrom("foo"), cmp), qt.Equals, false)
188+
189+
// Nothing shared
190+
c.Check(SliceEqualAnyOrderFunc(v, ncFrom("baz", "qux"), cmp), qt.Equals, false)
191+
}
192+
156193
func TestSliceEqual(t *testing.T) {
157194
a := SliceOf([]string{"foo", "bar"})
158195
b := SliceOf([]string{"foo", "bar"})

0 commit comments

Comments
 (0)