diff options
| author | Tom Proctor <tomhjp@users.noreply.github.com> | 2026-04-23 14:19:44 +0100 |
|---|---|---|
| committer | Tom Proctor <tomhjp@users.noreply.github.com> | 2026-04-24 11:17:59 +0100 |
| commit | 5de7033a823a56ac9e56a5eadeef8d18fb1d39f4 (patch) | |
| tree | 7f1abf226315a94369a0a3f65f96fb3bc5152e23 | |
| parent | a7d8aeb8aebc4bb01066eb6ffa69b9d8fe178b81 (diff) | |
| download | tailscale-tomhjp/set-contains-all.tar.xz tailscale-tomhjp/set-contains-all.zip | |
util/set: add ContainsSet and ContainsAlltomhjp/set-contains-all
These are trivial methods, but useful and generally applicable operations
for a set type.
Updates #cleanup
Change-Id: I94610b901cd10fe66dd15c38b8601f29ead71811
Signed-off-by: Tom Proctor <tomhjp@users.noreply.github.com>
| -rw-r--r-- | util/set/set.go | 18 | ||||
| -rw-r--r-- | util/set/set_test.go | 68 |
2 files changed, 86 insertions, 0 deletions
diff --git a/util/set/set.go b/util/set/set.go index c3d2350a7..105e9782b 100644 --- a/util/set/set.go +++ b/util/set/set.go @@ -6,6 +6,7 @@ package set import ( "encoding/json" + "iter" "maps" "reflect" "sort" @@ -111,6 +112,23 @@ func (s Set[T]) Contains(e T) bool { return ok } +// ContainsSet reports whether s is a superset of e. Returns true if e is nil, +// empty, or equal to s. +func (s Set[T]) ContainsSet(e Set[T]) bool { + return s.ContainsAll(maps.Keys(e)) +} + +// ContainsAll reports whether s contains all elements of e. Returns true if e +// has 0 elements. +func (s Set[T]) ContainsAll(e iter.Seq[T]) bool { + for k := range e { + if !s.Contains(k) { + return false + } + } + return true +} + // Len reports the number of items in s. func (s Set[T]) Len() int { return len(s) } diff --git a/util/set/set_test.go b/util/set/set_test.go index 2188cbb4d..35e66bf36 100644 --- a/util/set/set_test.go +++ b/util/set/set_test.go @@ -5,6 +5,8 @@ package set import ( "encoding/json" + "iter" + "maps" "slices" "testing" ) @@ -200,3 +202,69 @@ func TestMake(t *testing.T) { t.Error("missing 1") } } + +func TestContainsCollection(t *testing.T) { + s := Of(1, 2, 3) + for name, tc := range map[string]struct { + e []int + expected bool + }{ + "equal": { + e: []int{1, 2, 3}, + expected: true, + }, + "superset": { + e: []int{2, 3}, + expected: true, + }, + "disjoint": { + e: []int{4, 5}, + expected: false, + }, + "partial": { + e: []int{2, 4}, + expected: false, + }, + } { + t.Run(name, func(t *testing.T) { + if s.ContainsSet(Of(tc.e...)) != tc.expected { + t.Errorf("ContainsSet(%v) = %v; want %v", tc.e, !tc.expected, tc.expected) + } + if s.ContainsAll(slices.Values(tc.e)) != tc.expected { + t.Errorf("ContainsSet(%v) = %v; want %v", tc.e, !tc.expected, tc.expected) + } + }) + } +} + +// Test ContainsSet always return true for empty collections. +func TestContainsSetEmpty(t *testing.T) { + var emptySets = []Set[int]{ + nil, + make(Set[int]), + Of[int](), + } + for _, s := range []Set[int]{nil, make(Set[int]), Of[int](), Of(1, 2, 3)} { + for _, empty := range emptySets { + if !s.ContainsSet(empty) { + t.Errorf("set %v should contain empty set %v", s, empty) + } + } + } +} + +// Test ContainsAll always return true for empty collections. +func TestContainsAllEmpty(t *testing.T) { + var emptyIters = []iter.Seq[int]{ + func(yield func(int) bool) {}, + slices.Values([]int{}), + maps.Keys(map[int]struct{}{}), + } + for _, s := range []Set[int]{nil, make(Set[int]), Of[int](), Of(1, 2, 3)} { + for _, empty := range emptyIters { + if !s.ContainsAll(empty) { + t.Errorf("set %v should contain empty iterator %v", s, empty) + } + } + } +} |
