summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorTom Proctor <tomhjp@users.noreply.github.com>2026-04-23 14:19:44 +0100
committerTom Proctor <tomhjp@users.noreply.github.com>2026-04-24 11:17:59 +0100
commit5de7033a823a56ac9e56a5eadeef8d18fb1d39f4 (patch)
tree7f1abf226315a94369a0a3f65f96fb3bc5152e23
parenta7d8aeb8aebc4bb01066eb6ffa69b9d8fe178b81 (diff)
downloadtailscale-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.go18
-rw-r--r--util/set/set_test.go68
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)
+ }
+ }
+ }
+}