on main help sign in
feat: add `IsSubset` helper

This should probably be moved into `x`.

commit5be56eca71cd4237b67b6cbc3e540a7ffe9006bb authorAndrey Nering <andreynering@users.noreply.github.com> dateMon 18 Aug 2025, 14:50 (1 year ago) parent2d2ee7f
2 files changed+175
internal/slicesext/slices.go+17 internal/slicesext/slices_test.go+158
internal/slicesext/slices.gonew file+17
@@ -0,0 +1,17 @@
1+package slicesext
2+
3+func IsSubset[T comparable](a, b []T) bool {
4+ if len(a) > len(b) {
5+ return false
6+ }
7+ set := make(map[T]struct{}, len(b))
8+ for _, item := range b {
9+ set[item] = struct{}{}
10+ }
11+ for _, item := range a {
12+ if _, exists := set[item]; !exists {
13+ return false
14+ }
15+ }
16+ return true
17+}
internal/slicesext/slices_test.gonew file+158
@@ -0,0 +1,158 @@
1+package slicesext
2+
3+import (
4+ "testing"
5+
6+ "github.com/stretchr/testify/require"
7+)
8+
9+func TestIsSubset(t *testing.T) {
10+ tests := []struct {
11+ name string
12+ a []string
13+ b []string
14+ expect bool
15+ }{
16+ // Basic subset cases
17+ {
18+ name: "empty subset of empty",
19+ a: []string{},
20+ b: []string{},
21+ expect: true,
22+ },
23+ {
24+ name: "empty subset of non-empty",
25+ a: []string{},
26+ b: []string{"a", "b", "c"},
27+ expect: true,
28+ },
29+ {
30+ name: "non-empty not subset of empty",
31+ a: []string{"a"},
32+ b: []string{},
33+ expect: false,
34+ },
35+ {
36+ name: "single element subset",
37+ a: []string{"b"},
38+ b: []string{"a", "b", "c"},
39+ expect: true,
40+ },
41+ {
42+ name: "single element not subset",
43+ a: []string{"d"},
44+ b: []string{"a", "b", "c"},
45+ expect: false,
46+ },
47+ {
48+ name: "multiple elements subset",
49+ a: []string{"a", "c"},
50+ b: []string{"a", "b", "c", "d"},
51+ expect: true,
52+ },
53+ {
54+ name: "multiple elements not subset",
55+ a: []string{"a", "e"},
56+ b: []string{"a", "b", "c", "d"},
57+ expect: false,
58+ },
59+ {
60+ name: "equal sets are subsets",
61+ a: []string{"a", "b", "c"},
62+ b: []string{"a", "b", "c"},
63+ expect: true,
64+ },
65+ {
66+ name: "larger set not subset of smaller",
67+ a: []string{"a", "b", "c", "d"},
68+ b: []string{"a", "b"},
69+ expect: false,
70+ },
71+
72+ // Order independence
73+ {
74+ name: "subset with different order",
75+ a: []string{"c", "a"},
76+ b: []string{"b", "a", "d", "c"},
77+ expect: true,
78+ },
79+
80+ // Duplicate handling
81+ {
82+ name: "duplicates in subset",
83+ a: []string{"a", "a", "b"},
84+ b: []string{"a", "b", "c"},
85+ expect: true,
86+ },
87+ {
88+ name: "duplicates in superset",
89+ a: []string{"a", "b"},
90+ b: []string{"a", "a", "b", "b", "c"},
91+ expect: true,
92+ },
93+ {
94+ name: "duplicates in both",
95+ a: []string{"a", "a", "b"},
96+ b: []string{"a", "a", "b", "b", "c"},
97+ expect: true,
98+ },
99+
100+ // Real-world examples
101+ {
102+ name: "npm flags subset",
103+ a: []string{"-g"},
104+ b: []string{"-g", "--verbose", "--save-dev"},
105+ expect: true,
106+ },
107+ {
108+ name: "npm flags not subset",
109+ a: []string{"--global"},
110+ b: []string{"-g", "--verbose", "--save-dev"},
111+ expect: false,
112+ },
113+ }
114+
115+ for _, tt := range tests {
116+ t.Run(tt.name, func(t *testing.T) {
117+ result := IsSubset(tt.a, tt.b)
118+ require.Equal(t, tt.expect, result,
119+ "IsSubset(%v, %v) should be %v", tt.a, tt.b, tt.expect)
120+ })
121+ }
122+}
123+
124+func TestIsSubsetWithInts(t *testing.T) {
125+ tests := []struct {
126+ name string
127+ a []int
128+ b []int
129+ expect bool
130+ }{
131+ {
132+ name: "int subset",
133+ a: []int{1, 3},
134+ b: []int{1, 2, 3, 4},
135+ expect: true,
136+ },
137+ {
138+ name: "int not subset",
139+ a: []int{1, 5},
140+ b: []int{1, 2, 3, 4},
141+ expect: false,
142+ },
143+ {
144+ name: "empty int subset",
145+ a: []int{},
146+ b: []int{1, 2, 3},
147+ expect: true,
148+ },
149+ }
150+
151+ for _, tt := range tests {
152+ t.Run(tt.name, func(t *testing.T) {
153+ result := IsSubset(tt.a, tt.b)
154+ require.Equal(t, tt.expect, result,
155+ "IsSubset(%v, %v) should be %v", tt.a, tt.b, tt.expect)
156+ })
157+ }
158+}