Skip to content

Commit 4a7d053

Browse files
authored
feat: add ContainsOne method (#134)
* feat: add ContainsOne method * chore: removed unnecessary word
1 parent 5ff637d commit 4a7d053

File tree

6 files changed

+182
-0
lines changed

6 files changed

+182
-0
lines changed

bench_test.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,99 @@ func BenchmarkContains100Unsafe(b *testing.B) {
180180
benchContains(b, 100, NewThreadUnsafeSet[int]())
181181
}
182182

183+
func benchContainsOne(b *testing.B, n int, s Set[int]) {
184+
nums := nrand(n)
185+
for _, v := range nums {
186+
s.Add(v)
187+
}
188+
189+
b.ResetTimer()
190+
for i := 0; i < b.N; i++ {
191+
s.ContainsOne(-1)
192+
}
193+
}
194+
195+
func BenchmarkContainsOne1Safe(b *testing.B) {
196+
benchContainsOne(b, 1, NewSet[int]())
197+
}
198+
199+
func BenchmarkContainsOne1Unsafe(b *testing.B) {
200+
benchContainsOne(b, 1, NewThreadUnsafeSet[int]())
201+
}
202+
203+
func BenchmarkContainsOne10Safe(b *testing.B) {
204+
benchContainsOne(b, 10, NewSet[int]())
205+
}
206+
207+
func BenchmarkContainsOne10Unsafe(b *testing.B) {
208+
benchContainsOne(b, 10, NewThreadUnsafeSet[int]())
209+
}
210+
211+
func BenchmarkContainsOne100Safe(b *testing.B) {
212+
benchContainsOne(b, 100, NewSet[int]())
213+
}
214+
215+
func BenchmarkContainsOne100Unsafe(b *testing.B) {
216+
benchContainsOne(b, 100, NewThreadUnsafeSet[int]())
217+
}
218+
219+
// In this scenario, Contains argument escapes to the heap, while ContainsOne does not.
220+
func benchContainsComparison(b *testing.B, n int, s Set[int]) {
221+
nums := nrand(n)
222+
for _, v := range nums {
223+
s.Add(v)
224+
}
225+
226+
b.Run("Contains", func(b *testing.B) {
227+
b.ReportAllocs()
228+
for i := 0; i < b.N; i++ {
229+
for _, v := range nums {
230+
s.Contains(v) // 1 allocation, v is moved to the heap
231+
}
232+
}
233+
})
234+
b.Run("Contains slice", func(b *testing.B) {
235+
b.ReportAllocs()
236+
for i := 0; i < b.N; i++ {
237+
for i := range nums {
238+
s.Contains(nums[i : i+1]...) // no allocations, using heap-allocated slice
239+
}
240+
}
241+
})
242+
b.Run("ContainsOne", func(b *testing.B) {
243+
b.ReportAllocs()
244+
for i := 0; i < b.N; i++ {
245+
for _, v := range nums {
246+
s.ContainsOne(v) // no allocations, using stack-allocated v
247+
}
248+
}
249+
})
250+
}
251+
252+
func BenchmarkContainsComparison1Unsafe(b *testing.B) {
253+
benchContainsComparison(b, 1, NewThreadUnsafeSet[int]())
254+
}
255+
256+
func BenchmarkContainsComparison1Safe(b *testing.B) {
257+
benchContainsComparison(b, 1, NewSet[int]())
258+
}
259+
260+
func BenchmarkContainsComparison10Unsafe(b *testing.B) {
261+
benchContainsComparison(b, 10, NewThreadUnsafeSet[int]())
262+
}
263+
264+
func BenchmarkContainsComparison10Safe(b *testing.B) {
265+
benchContainsComparison(b, 10, NewSet[int]())
266+
}
267+
268+
func BenchmarkContainsComparison100Unsafe(b *testing.B) {
269+
benchContainsComparison(b, 100, NewThreadUnsafeSet[int]())
270+
}
271+
272+
func BenchmarkContainsComparison100Safe(b *testing.B) {
273+
benchContainsComparison(b, 100, NewSet[int]())
274+
}
275+
183276
func benchEqual(b *testing.B, n int, s, t Set[int]) {
184277
nums := nrand(n)
185278
for _, v := range nums {

set.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ type Set[T comparable] interface {
6262
// are all in the set.
6363
Contains(val ...T) bool
6464

65+
// ContainsOne returns whether the given item
66+
// is in the set.
67+
//
68+
// Contains may cause the argument to escape to the heap.
69+
// See: https://github.com/deckarep/golang-set/issues/118
70+
ContainsOne(val T) bool
71+
6572
// ContainsAny returns whether at least one of the
6673
// given items are in the set.
6774
ContainsAny(val ...T) bool

set_test.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,54 @@ func Test_ContainsMultipleUnsafeSet(t *testing.T) {
318318
}
319319
}
320320

321+
func Test_ContainsOneSet(t *testing.T) {
322+
a := NewSet[int]()
323+
324+
a.Add(71)
325+
326+
if !a.ContainsOne(71) {
327+
t.Error("ContainsSet should contain 71")
328+
}
329+
330+
a.Remove(71)
331+
332+
if a.ContainsOne(71) {
333+
t.Error("ContainsSet should not contain 71")
334+
}
335+
336+
a.Add(13)
337+
a.Add(7)
338+
a.Add(1)
339+
340+
if !(a.ContainsOne(13) && a.ContainsOne(7) && a.ContainsOne(1)) {
341+
t.Error("ContainsSet should contain 13, 7, 1")
342+
}
343+
}
344+
345+
func Test_ContainsOneUnsafeSet(t *testing.T) {
346+
a := NewThreadUnsafeSet[int]()
347+
348+
a.Add(71)
349+
350+
if !a.ContainsOne(71) {
351+
t.Error("ContainsSet should contain 71")
352+
}
353+
354+
a.Remove(71)
355+
356+
if a.ContainsOne(71) {
357+
t.Error("ContainsSet should not contain 71")
358+
}
359+
360+
a.Add(13)
361+
a.Add(7)
362+
a.Add(1)
363+
364+
if !(a.ContainsOne(13) && a.ContainsOne(7) && a.ContainsOne(1)) {
365+
t.Error("ContainsSet should contain 13, 7, 1")
366+
}
367+
}
368+
321369
func Test_ContainsAnySet(t *testing.T) {
322370
a := NewSet[int]()
323371

threadsafe.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ func (t *threadSafeSet[T]) Contains(v ...T) bool {
6666
return ret
6767
}
6868

69+
func (t *threadSafeSet[T]) ContainsOne(v T) bool {
70+
t.RLock()
71+
ret := t.uss.ContainsOne(v)
72+
t.RUnlock()
73+
74+
return ret
75+
}
76+
6977
func (t *threadSafeSet[T]) ContainsAny(v ...T) bool {
7078
t.RLock()
7179
ret := t.uss.ContainsAny(v...)

threadsafe_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,27 @@ func Test_ContainsConcurrent(t *testing.T) {
172172
wg.Wait()
173173
}
174174

175+
func Test_ContainsOneConcurrent(t *testing.T) {
176+
runtime.GOMAXPROCS(2)
177+
178+
s := NewSet[int]()
179+
ints := rand.Perm(N)
180+
for _, v := range ints {
181+
s.Add(v)
182+
}
183+
184+
var wg sync.WaitGroup
185+
for _, v := range ints {
186+
number := v
187+
wg.Add(1)
188+
go func() {
189+
s.ContainsOne(number)
190+
wg.Done()
191+
}()
192+
}
193+
wg.Wait()
194+
}
195+
175196
func Test_ContainsAnyConcurrent(t *testing.T) {
176197
runtime.GOMAXPROCS(2)
177198

threadunsafe.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,11 @@ func (s threadUnsafeSet[T]) Contains(v ...T) bool {
9393
return true
9494
}
9595

96+
func (s threadUnsafeSet[T]) ContainsOne(v T) bool {
97+
_, ok := s[v]
98+
return ok
99+
}
100+
96101
func (s threadUnsafeSet[T]) ContainsAny(v ...T) bool {
97102
for _, val := range v {
98103
if _, ok := s[val]; ok {

0 commit comments

Comments
 (0)