Skip to content

Commit c0e69b9

Browse files
committed
add sets data structure
1 parent c2cb123 commit c0e69b9

File tree

2 files changed

+299
-0
lines changed

2 files changed

+299
-0
lines changed

ds/sets/stringset.go

+219
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
package sets
2+
3+
import (
4+
"sort"
5+
"sync"
6+
)
7+
8+
// StringSet operations for string keys
9+
type StringSet interface {
10+
Insert(items ...string)
11+
Delete(items ...string)
12+
Clear() []string
13+
Replace(items ...string)
14+
Has(item string) bool
15+
HasAll(items ...string) bool
16+
HasAny(items ...string) bool
17+
IsSuperset(right StringSet) bool
18+
IsSubset(right StringSet) bool
19+
Equal(right StringSet) bool
20+
Diff(right StringSet) StringSet
21+
Union(right StringSet) StringSet
22+
Intersection(right StringSet) StringSet
23+
List() []string
24+
SortedList() []string
25+
PopAny() (string, bool)
26+
Len() int
27+
}
28+
29+
// NewStringSet create a StringSet from a string slice
30+
func NewStringSet(items ...string) StringSet {
31+
ss := &ssetImpl{
32+
set: make(map[string]struct{}),
33+
mutex: new(sync.RWMutex),
34+
}
35+
if len(items) > 0 {
36+
ss.Insert(items...)
37+
}
38+
return ss
39+
}
40+
41+
type ssetImpl struct {
42+
set map[string]struct{}
43+
mutex *sync.RWMutex
44+
}
45+
46+
func (ss *ssetImpl) Insert(items ...string) {
47+
ss.mutex.Lock()
48+
defer ss.mutex.Unlock()
49+
50+
for _, item := range items {
51+
ss.set[item] = struct{}{}
52+
}
53+
}
54+
func (ss *ssetImpl) Delete(items ...string) {
55+
ss.mutex.Lock()
56+
defer ss.mutex.Unlock()
57+
58+
for _, item := range items {
59+
delete(ss.set, item)
60+
}
61+
}
62+
63+
func (ss *ssetImpl) Clear() []string {
64+
ss.mutex.Lock()
65+
defer ss.mutex.Unlock()
66+
67+
result := make([]string, 0, len(ss.set))
68+
for item := range ss.set {
69+
result = append(result, item)
70+
}
71+
// use a new map to replace with the old one, and the old one will be gced.
72+
ss.set = make(map[string]struct{})
73+
74+
return result
75+
}
76+
func (ss *ssetImpl) Replace(items ...string) {
77+
ss.mutex.Lock()
78+
defer ss.mutex.Unlock()
79+
80+
// use a new map to replace with the old one, and the old one will be gced.
81+
ss.set = make(map[string]struct{})
82+
for _, item := range items {
83+
ss.set[item] = struct{}{}
84+
}
85+
}
86+
func (ss *ssetImpl) Has(item string) bool {
87+
ss.mutex.RLock()
88+
defer ss.mutex.RUnlock()
89+
90+
return ss.hasImpl(item)
91+
}
92+
func (ss *ssetImpl) hasImpl(item string) bool {
93+
_, exists := ss.set[item]
94+
return exists
95+
}
96+
97+
func (ss *ssetImpl) HasAll(items ...string) bool {
98+
ss.mutex.RLock()
99+
defer ss.mutex.RUnlock()
100+
101+
for _, item := range items {
102+
if !ss.hasImpl(item) {
103+
return false
104+
}
105+
}
106+
return true
107+
}
108+
func (ss *ssetImpl) HasAny(items ...string) bool {
109+
ss.mutex.RLock()
110+
defer ss.mutex.RUnlock()
111+
112+
for _, item := range items {
113+
if ss.hasImpl(item) {
114+
return true
115+
}
116+
}
117+
return false
118+
}
119+
func (ss *ssetImpl) IsSuperset(right StringSet) bool {
120+
rightImpl := right.(*ssetImpl)
121+
122+
ss.mutex.RLock()
123+
rightImpl.mutex.RLock()
124+
defer ss.mutex.RUnlock()
125+
defer rightImpl.mutex.RUnlock()
126+
127+
for item := range rightImpl.set {
128+
if !ss.hasImpl(item) {
129+
return false
130+
}
131+
}
132+
return true
133+
}
134+
func (ss *ssetImpl) IsSubset(right StringSet) bool {
135+
return right.IsSuperset(ss)
136+
}
137+
func (ss *ssetImpl) Equal(right StringSet) bool {
138+
return ss.Len() == right.Len() && ss.IsSuperset(right)
139+
}
140+
func (ss *ssetImpl) Diff(right StringSet) StringSet {
141+
ss.mutex.RLock()
142+
defer ss.mutex.RUnlock()
143+
144+
result := NewStringSet()
145+
for item := range ss.set {
146+
if !right.Has(item) {
147+
result.Insert(item)
148+
}
149+
}
150+
return result
151+
}
152+
func (ss *ssetImpl) Union(right StringSet) StringSet {
153+
rightImpl := right.(*ssetImpl)
154+
155+
ss.mutex.RLock()
156+
rightImpl.mutex.RLock()
157+
defer ss.mutex.RUnlock()
158+
defer rightImpl.mutex.RUnlock()
159+
160+
result := NewStringSet()
161+
for item := range ss.set {
162+
result.Insert(item)
163+
}
164+
for item := range rightImpl.set {
165+
result.Insert(item)
166+
}
167+
return result
168+
}
169+
func (ss *ssetImpl) Intersection(right StringSet) StringSet {
170+
s1 := ss
171+
s2 := right.(*ssetImpl)
172+
if s1.Len() > s2.Len() {
173+
s1, s2 = s2, s1
174+
}
175+
176+
s1.mutex.RLock()
177+
s2.mutex.RLock()
178+
defer s1.mutex.RUnlock()
179+
defer s2.mutex.RUnlock()
180+
181+
result := NewStringSet()
182+
for item := range s1.set {
183+
if s2.hasImpl(item) {
184+
result.Insert(item)
185+
}
186+
}
187+
return result
188+
}
189+
func (ss *ssetImpl) List() []string {
190+
ss.mutex.RLock()
191+
defer ss.mutex.RUnlock()
192+
193+
result := make([]string, 0, len(ss.set))
194+
for item := range ss.set {
195+
result = append(result, item)
196+
}
197+
return result
198+
}
199+
func (ss *ssetImpl) SortedList() []string {
200+
list := ss.List()
201+
sort.Strings(list)
202+
return list
203+
}
204+
func (ss *ssetImpl) PopAny() (string, bool) {
205+
ss.mutex.Lock()
206+
defer ss.mutex.Unlock()
207+
208+
for item := range ss.set {
209+
delete(ss.set, item)
210+
return item, true
211+
}
212+
return "", false
213+
}
214+
func (ss *ssetImpl) Len() int {
215+
ss.mutex.RLock()
216+
defer ss.mutex.RUnlock()
217+
218+
return len(ss.set)
219+
}

ds/sets/stringset_test.go

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
package sets_test
2+
3+
import (
4+
"testing"
5+
6+
"github.com/leopoldxx/go-utils/ds/sets"
7+
)
8+
9+
func TestStringSet(t *testing.T) {
10+
left := sets.NewStringSet("a", "b", "c", "d", "e")
11+
right := sets.NewStringSet("d", "e", "f", "g", "h")
12+
right2 := sets.NewStringSet("a", "b", "f", "n", "m")
13+
14+
if !left.Diff(right.Union(right2)).Equal(sets.NewStringSet("c")) {
15+
t.Fatal("should equal")
16+
}
17+
18+
if !left.Has("a") {
19+
t.Fatal("should have 'a'")
20+
}
21+
22+
if left.Len() != right.Len() || left.Len() != 5 || len(left.List()) != len(left.SortedList()) {
23+
t.Fatal("not same length")
24+
}
25+
26+
res := left.Diff(right)
27+
if !res.Equal(sets.NewStringSet("a", "b", "c")) {
28+
t.Fatal("invalid diff: %v", res)
29+
}
30+
31+
res = right.Diff(left)
32+
if !res.Equal(sets.NewStringSet("f", "g", "h")) {
33+
t.Fatal("invalid diff: %v", res)
34+
}
35+
36+
if !left.Intersection(right).Equal(sets.NewStringSet("d", "e")) {
37+
t.Fatal("invalid intersection: %v", res)
38+
}
39+
40+
left.Delete("a")
41+
42+
if left.Has("a") {
43+
t.Fatal("should not have 'a'")
44+
}
45+
46+
if !left.HasAll("b", "c", "d", "e") {
47+
t.Fatal("should have all")
48+
}
49+
50+
if !left.HasAny("b", "c", "m", "n") {
51+
t.Fatal("should have any")
52+
}
53+
54+
if !left.Union(right).Equal(sets.NewStringSet("b", "c", "d", "e", "f", "g", "h")) {
55+
t.Fatal("invalid union")
56+
}
57+
58+
if !left.IsSuperset(sets.NewStringSet("c", "d")) {
59+
t.Fatal("invalid superset")
60+
}
61+
62+
left.PopAny()
63+
if left.Len() != 3 {
64+
t.Fatal("invalid popany")
65+
}
66+
67+
_, ok := left.PopAny()
68+
for ok {
69+
_, ok = left.PopAny()
70+
}
71+
if left.Len() != 0 {
72+
t.Fatal("invalid popany2")
73+
}
74+
t.Logf("%v", left)
75+
left.Replace(right.List()...)
76+
t.Logf("%v", left)
77+
if !left.Equal(right) {
78+
t.Fatal("should equal")
79+
}
80+
}

0 commit comments

Comments
 (0)