Skip to content

Commit c8c3633

Browse files
authored
Merge pull request #1 from fortio/more_ops
Adding Subset, Equals, Minus, Plus, Len, Clear and JSON serialization/deserialization
2 parents 4bac831 + 1d4d490 commit c8c3633

File tree

2 files changed

+181
-0
lines changed

2 files changed

+181
-0
lines changed

sets.go

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
package sets // import "fortio.org/sets"
99

1010
import (
11+
"encoding/json"
1112
"fmt"
1213
"sort"
1314
"strings"
@@ -98,6 +99,48 @@ func (s Set[T]) Elements() []T {
9899
return res
99100
}
100101

102+
// Subset returns true if all elements of s are in the passed in set.
103+
func (s Set[T]) Subset(bigger Set[T]) bool {
104+
for k := range s {
105+
if !bigger.Has(k) {
106+
return false
107+
}
108+
}
109+
return true
110+
}
111+
112+
// Minus mutates the receiver to remove all the elements of the passed in set.
113+
// If you want a copy use s.Clone().Minus(other). Returns the receiver for chaining.
114+
func (s Set[T]) Minus(other Set[T]) Set[T] {
115+
for k := range other {
116+
s.Remove(k)
117+
}
118+
return s
119+
}
120+
121+
// Plus is similar to Union but mutates the receiver. Added for symmetry with Minus.
122+
// Returns the receiver for chaining.
123+
func (s Set[T]) Plus(others ...Set[T]) Set[T] {
124+
for _, o := range others {
125+
s.Add(o.Elements()...)
126+
}
127+
return s
128+
}
129+
130+
func (s Set[T]) Equals(other Set[T]) bool {
131+
return s.Subset(other) && other.Subset(s)
132+
}
133+
134+
func (s Set[T]) Len() int {
135+
return len(s)
136+
}
137+
138+
func (s Set[T]) Clear() {
139+
for k := range s {
140+
delete(s, k)
141+
}
142+
}
143+
101144
// String() returns a coma separated list of the elements in the set.
102145
// This is mostly for troubleshooting/debug output unless the [T] serializes
103146
// to a string that doesn't contain commas.
@@ -131,6 +174,37 @@ func XOR[T comparable](a, b Set[T]) {
131174
RemoveCommon(a, b)
132175
}
133176

177+
// -- Serialization
178+
179+
// MarshalJSON implements the json.Marshaler interface and only gets the elements as an array.
180+
func (s Set[T]) MarshalJSON() ([]byte, error) {
181+
// How to handle all ordered at once??
182+
switch v := any(s).(type) {
183+
case Set[string]:
184+
return json.Marshal(Sort(v))
185+
case Set[int]:
186+
return json.Marshal(Sort(v))
187+
case Set[int8]:
188+
return json.Marshal(Sort(v))
189+
case Set[int64]:
190+
return json.Marshal(Sort(v))
191+
case Set[float64]:
192+
return json.Marshal(Sort(v))
193+
default:
194+
return json.Marshal(s.Elements())
195+
}
196+
}
197+
198+
// UnmarshalJSON implements the json.Unmarshaler interface turns the slice back to a Set.
199+
func (s *Set[T]) UnmarshalJSON(data []byte) error {
200+
var items []T
201+
if err := json.Unmarshal(data, &items); err != nil {
202+
return err
203+
}
204+
*s = New[T](items...)
205+
return nil
206+
}
207+
134208
// -- Additional operations on sets of ordered types
135209

136210
func Sort[Q constraints.Ordered](s Set[Q]) []Q {

sets_test.go

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
package sets_test
55

66
import (
7+
"encoding/json"
78
"testing"
89

910
"fortio.org/assert"
@@ -13,6 +14,10 @@ import (
1314
func TestSetToString(t *testing.T) {
1415
s := sets.Set[string]{"z": {}, "a": {}, "c": {}, "b": {}}
1516
assert.Equal(t, "a,b,c,z", s.String())
17+
assert.Equal(t, s.Len(), 4)
18+
s.Clear()
19+
assert.Equal(t, "", s.String())
20+
assert.Equal(t, s.Len(), 0)
1621
}
1722

1823
func TestArrayToSet(t *testing.T) {
@@ -34,6 +39,10 @@ func TestRemoveCommon(t *testing.T) {
3439
// also check clone is not modifying the original etc
3540
setAA = setB.Clone() // putting B in AA on purpose and vice versa
3641
setBB = setA.Clone()
42+
assert.True(t, setAA.Equals(setB))
43+
assert.True(t, setB.Equals(setAA))
44+
assert.False(t, setAA.Equals(setA))
45+
assert.False(t, setB.Equals(setBB))
3746
sets.XOR(setAA, setBB)
3847
assert.Equal(t, "a,c", setBB.String())
3948
assert.Equal(t, "e,f,g", setAA.String())
@@ -42,6 +51,24 @@ func TestRemoveCommon(t *testing.T) {
4251
assert.False(t, setBB.Has("c"))
4352
}
4453

54+
func TestMinus(t *testing.T) {
55+
setA := sets.New("a", "b", "c", "d")
56+
setB := sets.New("b", "d", "e", "f", "g")
57+
setAB := setA.Clone().Minus(setB)
58+
setBA := setB.Clone().Minus(setA)
59+
assert.Equal(t, "a,c", setAB.String())
60+
assert.Equal(t, "e,f,g", setBA.String())
61+
}
62+
63+
func TestPlus(t *testing.T) {
64+
setA := sets.New("a", "b", "c", "d")
65+
setB := sets.New("b", "d", "e", "f", "g")
66+
setAB := setA.Clone().Plus(setB)
67+
setBA := setB.Clone().Plus(setA)
68+
assert.Equal(t, "a,b,c,d,e,f,g", setAB.String())
69+
assert.Equal(t, "a,b,c,d,e,f,g", setBA.String())
70+
}
71+
4572
func TestUnion(t *testing.T) {
4673
setA := sets.New("a", "b", "c", "d")
4774
setB := sets.New("b", "d", "e", "f", "g")
@@ -64,3 +91,83 @@ func TestIntersection2(t *testing.T) {
6491
setC := sets.Intersection(setA, setB, setA)
6592
assert.Equal(t, "", setC.String())
6693
}
94+
95+
func TestSubset(t *testing.T) {
96+
setA := sets.New("a", "b", "c", "d")
97+
setB := sets.New("b", "d", "e", "f", "g")
98+
setC := sets.New("b", "d")
99+
assert.True(t, setC.Subset(setA))
100+
assert.True(t, setA.Subset(setA))
101+
assert.False(t, setA.Subset(setC))
102+
assert.False(t, setA.Subset(setB))
103+
assert.False(t, setB.Subset(setA))
104+
}
105+
106+
func TestJSON(t *testing.T) {
107+
setA := sets.New("c,d", "a b", "y\000z", "mno")
108+
b, err := json.Marshal(setA)
109+
assert.NoError(t, err)
110+
assert.Equal(t, `["a b","c,d","mno","y\u0000z"]`, string(b))
111+
jsonStr := `[
112+
"a,b",
113+
"c,d"
114+
]`
115+
setB := sets.New[string]()
116+
err = json.Unmarshal([]byte(jsonStr), &setB)
117+
assert.NoError(t, err)
118+
assert.Equal(t, setB.Len(), 2)
119+
assert.True(t, setB.Has("a,b"))
120+
assert.True(t, setB.Has("c,d"))
121+
setI := sets.New(3, 42, 7, 10)
122+
b, err = json.Marshal(setI)
123+
assert.NoError(t, err)
124+
assert.Equal(t, `[3,7,10,42]`, string(b))
125+
smallIntSet := sets.New[int8](66, 65, 67) // if using byte, aka uint8, one gets base64("ABC")
126+
b, err = json.Marshal(smallIntSet)
127+
assert.NoError(t, err)
128+
t.Logf("smallIntSet: %q", string(b))
129+
assert.Equal(t, `[65,66,67]`, string(b))
130+
floatSet := sets.New[float64](2.3, 1.1, -7.6, 42)
131+
b, err = json.Marshal(floatSet)
132+
assert.NoError(t, err)
133+
t.Logf("floatSet: %q", string(b))
134+
assert.Equal(t, `[-7.6,1.1,2.3,42]`, string(b))
135+
i64Set := sets.New[int64](2, 1, -7, 42)
136+
b, err = json.Marshal(i64Set)
137+
assert.NoError(t, err)
138+
t.Logf("i64Set: %q", string(b))
139+
assert.Equal(t, `[-7,1,2,42]`, string(b))
140+
}
141+
142+
type foo struct {
143+
X int
144+
}
145+
146+
func TestNonOrderedJSON(t *testing.T) {
147+
s := sets.New(
148+
foo{3},
149+
foo{1},
150+
foo{2},
151+
foo{4},
152+
)
153+
b, err := json.Marshal(s)
154+
t.Logf("b: %s", string(b))
155+
assert.NoError(t, err)
156+
// though I guess given it could be in any order it could be accidentally sorted too
157+
assert.NotEqual(t, `[{"X":1},{"X":2},{"X":3},{"X":4}]`, string(b))
158+
u := sets.New[foo]()
159+
json.Unmarshal(b, &u)
160+
assert.NoError(t, err)
161+
assert.Equal(t, 4, u.Len())
162+
assert.True(t, s.Equals(u))
163+
}
164+
165+
func TestBadJson(t *testing.T) {
166+
jsonStr := `[
167+
"a,b",
168+
"c,d"
169+
]`
170+
s := sets.New[int]()
171+
err := json.Unmarshal([]byte(jsonStr), &s)
172+
assert.Error(t, err)
173+
}

0 commit comments

Comments
 (0)