From 5de7033a823a56ac9e56a5eadeef8d18fb1d39f4 Mon Sep 17 00:00:00 2001 From: Tom Proctor Date: Thu, 23 Apr 2026 14:19:44 +0100 Subject: [PATCH] util/set: add ContainsSet and ContainsAll These are trivial methods, but useful and generally applicable operations for a set type. Updates #cleanup Change-Id: I94610b901cd10fe66dd15c38b8601f29ead71811 Signed-off-by: Tom Proctor --- util/set/set.go | 18 ++++++++++++ util/set/set_test.go | 68 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) 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) + } + } + } +}