diff --git a/sliceutil/sets.go b/sliceutil/sets.go index b216cbe..438a16d 100644 --- a/sliceutil/sets.go +++ b/sliceutil/sets.go @@ -57,3 +57,18 @@ func Intersection[T comparable](a, b []T) []T { } return inter } + +// IntersectionOfMany returns the elements common to all arguments +func IntersectionOfMany[T comparable](slices ...[]T) []T { + var common []T + for i, s := range slices { + if i == 0 { + common = s + } + common = Intersection(common, s) + if len(common) == 0 { + break + } + } + return common +} diff --git a/sliceutil/sets_test.go b/sliceutil/sets_test.go index f41345a..85efcab 100644 --- a/sliceutil/sets_test.go +++ b/sliceutil/sets_test.go @@ -155,6 +155,38 @@ func TestIntersection(t *testing.T) { } } +func TestIntersectionOfMany(t *testing.T) { + type ciTest struct { + name string + a []int64 + b []int64 + c []int64 + result []int64 + } + tests := []ciTest{ + { + name: "EmptyLists", + result: []int64{}, + }, + { + name: "three", + a: []int64{1, 2, 3}, + b: []int64{3}, + c: []int64{3, 4, 5}, + result: []int64{3}, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + result := IntersectionOfMany(test.a, test.b) + + if !reflect.DeepEqual(result, test.result) { + t.Errorf("result wrong\ngot: %#v\nwant: %#v\n", result, test.result) + } + }) + } +} + func BenchmarkComplement_equal(b *testing.B) { listA := []int64{1, 2, 3} listB := []int64{1, 2, 3} diff --git a/sliceutil/sliceutil.go b/sliceutil/sliceutil.go index 0b24043..16a861f 100644 --- a/sliceutil/sliceutil.go +++ b/sliceutil/sliceutil.go @@ -93,6 +93,16 @@ func Contains[T comparable](tt []T, item T) bool { return false } +// ContainsAny checks if slice contain element +func ContainsAny[T comparable](slice []T, elements ...T) bool { + for _, element := range elements { + if Contains(slice, element) { + return true + } + } + return false +} + // InFoldedStringSlice reports whether str is within list(case-insensitive) func InFoldedStringSlice(list []string, str string) bool { for _, item := range list { @@ -201,3 +211,17 @@ func Values[T comparable, N any](tt []T, fn func(T) N) []N { return ret } + +// IsSubset returns if all elements of 'slice' are in 'set' +func IsSubset[T comparable](slice, subset []T) bool { + subsetMap := make(map[T]bool, len(subset)) + for _, v := range subset { + subsetMap[v] = true + } + for _, v := range slice { + if !subsetMap[v] { + return false + } + } + return true +} diff --git a/sliceutil/sliceutil_test.go b/sliceutil/sliceutil_test.go index 7df4418..bc30e16 100644 --- a/sliceutil/sliceutil_test.go +++ b/sliceutil/sliceutil_test.go @@ -307,6 +307,51 @@ func TestItemInSlice_String(t *testing.T) { } } +func TestContainsAny(t *testing.T) { + tests := []struct { + list []string + find string + find2 string + expected bool + }{ + {[]string{"hello"}, "hello", "world", true}, + {[]string{"hello"}, "hell", "world", false}, + {[]string{"hello", "world", "test"}, "world", "potato", true}, + {[]string{"hello", "world", "test"}, "", "potato", false}, + {[]string{}, "", "", false}, + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("test-%v", i), func(t *testing.T) { + got := ContainsAny(tc.list, tc.find) + if got != tc.expected { + t.Errorf(diff.Cmp(tc.expected, got)) + } + }) + } +} + +func TestIsSubset(t *testing.T) { + tests := []struct { + set []int + subset []int + expected bool + }{ + {[]int{1, 2, 3, 4}, []int{2, 3}, true}, + {[]int{1, 2, 3, 4}, []int{2, 3, 8}, false}, + {[]int{1, 2, 3, 4}, []int{1, 2, 3, 4}, false}, + } + + for i, tc := range tests { + t.Run(fmt.Sprintf("test-%v", i), func(t *testing.T) { + got := IsSubset(tc.set, tc.subset) + if got != tc.expected { + t.Errorf(diff.Cmp(tc.expected, got)) + } + }) + } +} + func TestInFoldedStringSlice(t *testing.T) { tests := []struct { list []string