adding set interface and threadsafe implementation
This commit is contained in:
parent
4ea82a9ed2
commit
5ae74c0ea0
2
go.mod
2
go.mod
@ -2,4 +2,4 @@ module code.wmdillon.com/wmdillon/set
|
||||
|
||||
go 1.25.3
|
||||
|
||||
require golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39
|
||||
require golang.org/x/exp v0.0.0-20260718201538-764159d718ef
|
||||
|
||||
2
go.sum
2
go.sum
@ -1,2 +1,4 @@
|
||||
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39 h1:DHNhtq3sNNzrvduZZIiFyXWOL9IWaDPHqTnLJp+rCBY=
|
||||
golang.org/x/exp v0.0.0-20251125195548-87e1e737ad39/go.mod h1:46edojNIoXTNOhySWIWdix628clX9ODXwPsQuG6hsK0=
|
||||
golang.org/x/exp v0.0.0-20260718201538-764159d718ef h1:LkZ48HFgy/TvhTI0bcWkjgFkgLyKUwcTbDjS0DUjw+A=
|
||||
golang.org/x/exp v0.0.0-20260718201538-764159d718ef/go.mod h1:EdfpwwqSu+0Li0mzskwHU6FWDV3t9Q+RZDo3QMUtL3Q=
|
||||
|
||||
82
set.go
82
set.go
@ -1,75 +1,23 @@
|
||||
package set
|
||||
|
||||
import (
|
||||
"maps"
|
||||
"code.wmdillon.com/wmdillon/set/simpleset"
|
||||
"code.wmdillon.com/wmdillon/set/threadsafeset"
|
||||
)
|
||||
|
||||
type Set[T comparable] map[T]struct{}
|
||||
type Set[T comparable] interface {
|
||||
Add(element T)
|
||||
Remove(element T)
|
||||
Contains(element T) bool
|
||||
Size() int
|
||||
ToSlice() []T
|
||||
}
|
||||
|
||||
func New[T comparable](elements ...T) Set[T] {
|
||||
s := Set[T]{}
|
||||
for _, e := range elements {
|
||||
s.Add(e)
|
||||
func New[T comparable](threadsafe bool, elements ...T) Set[T] {
|
||||
switch threadsafe {
|
||||
case true:
|
||||
return threadsafeset.New(elements...)
|
||||
default:
|
||||
return simpleset.New(elements...)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s Set[T]) Equal(other Set[T]) bool {
|
||||
return maps.Equal(s, other)
|
||||
}
|
||||
|
||||
func (s Set[T]) Add(element T) {
|
||||
s[element] = struct{}{}
|
||||
}
|
||||
|
||||
func (s Set[T]) Remove(element T) {
|
||||
delete(s, element)
|
||||
}
|
||||
|
||||
func (s Set[T]) Contains(element T) bool {
|
||||
_, exists := s[element]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (s Set[T]) Size() int {
|
||||
return len(s)
|
||||
}
|
||||
|
||||
func (s Set[T]) ToSlice() []T {
|
||||
elements := make([]T, 0, len(s))
|
||||
for e := range s {
|
||||
elements = append(elements, e)
|
||||
}
|
||||
return elements
|
||||
}
|
||||
|
||||
func (s Set[T]) Union(other Set[T]) Set[T] {
|
||||
result := New[T]()
|
||||
for e := range s {
|
||||
result.Add(e)
|
||||
}
|
||||
for e := range other {
|
||||
result.Add(e)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s Set[T]) Intersection(other Set[T]) Set[T] {
|
||||
result := New[T]()
|
||||
for e := range s {
|
||||
if other.Contains(e) {
|
||||
result.Add(e)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s Set[T]) Difference(other Set[T]) Set[T] {
|
||||
result := New[T]()
|
||||
for e := range s {
|
||||
if !other.Contains(e) {
|
||||
result.Add(e)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
205
set_test.go
205
set_test.go
@ -1,196 +1,21 @@
|
||||
package set
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"testing"
|
||||
|
||||
func TestIntersection(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3, 4)
|
||||
s2 := New[int](3, 4, 5, 6)
|
||||
want := New[int](3, 4)
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
"code.wmdillon.com/wmdillon/set/simpleset"
|
||||
"code.wmdillon.com/wmdillon/set/threadsafeset"
|
||||
)
|
||||
|
||||
func TestIntersection_Empty(t *testing.T) {
|
||||
s1 := New[int](1, 2)
|
||||
s2 := New[int](3, 4)
|
||||
want := New[int]()
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntersection_Identical(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3)
|
||||
s2 := New[int](1, 2, 3)
|
||||
want := New[int](1, 2, 3)
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntersection_Subset(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3, 4, 5)
|
||||
s2 := New[int](2, 3)
|
||||
want := New[int](2, 3)
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntersection_EmptySet(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3)
|
||||
s2 := New[int]()
|
||||
want := New[int]()
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntersection_BothEmpty(t *testing.T) {
|
||||
s1 := New[int]()
|
||||
s2 := New[int]()
|
||||
want := New[int]()
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifference(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3, 4)
|
||||
s2 := New[int](3, 4, 5, 6)
|
||||
want := New[int](1, 2)
|
||||
got := s1.Difference(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Difference() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifference_Empty(t *testing.T) {
|
||||
s1 := New[int](1, 2)
|
||||
s2 := New[int](1, 2)
|
||||
want := New[int]()
|
||||
got := s1.Difference(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Difference() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifference_Subset(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3, 4, 5)
|
||||
s2 := New[int](2, 3)
|
||||
want := New[int](1, 4, 5)
|
||||
got := s1.Difference(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Difference() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifference_EmptySet(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3)
|
||||
s2 := New[int]()
|
||||
want := New[int](1, 2, 3)
|
||||
got := s1.Difference(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Difference() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifference_BothEmpty(t *testing.T) {
|
||||
s1 := New[int]()
|
||||
s2 := New[int]()
|
||||
want := New[int]()
|
||||
got := s1.Difference(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Difference() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnion(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3)
|
||||
s2 := New[int](3, 4, 5)
|
||||
want := New[int](1, 2, 3, 4, 5)
|
||||
got := s1.Union(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Union() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnion_EmptySet(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3)
|
||||
s2 := New[int]()
|
||||
want := New[int](1, 2, 3)
|
||||
got := s1.Union(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Union() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnion_BothEmpty(t *testing.T) {
|
||||
s1 := New[int]()
|
||||
s2 := New[int]()
|
||||
want := New[int]()
|
||||
got := s1.Union(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Union() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
want := New[int](1, 2, 3)
|
||||
got := New[int]()
|
||||
got.Add(1)
|
||||
got.Add(2)
|
||||
got.Add(3)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Add() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemov(t *testing.T) {
|
||||
want := New[int](1, 3)
|
||||
got := New[int](1, 2, 3)
|
||||
got.Remove(2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Remove() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
func TestContains(t *testing.T) {
|
||||
s := New[int](1, 2, 3)
|
||||
if !s.Contains(2) {
|
||||
t.Errorf("Contains(2) = false; want true")
|
||||
}
|
||||
if s.Contains(4) {
|
||||
t.Errorf("Contains(4) = true; want false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSize(t *testing.T) {
|
||||
s := New[int](1, 2, 3, 4)
|
||||
want := 4
|
||||
got := s.Size()
|
||||
if got != want {
|
||||
t.Errorf("Size() = %d; want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToSlice(t *testing.T) {
|
||||
s := New[int](1, 2, 3)
|
||||
want := []int{1, 2, 3}
|
||||
got := s.ToSlice()
|
||||
mapping := make(map[int]bool)
|
||||
for _, v := range got {
|
||||
mapping[v] = true
|
||||
}
|
||||
for _, v := range want {
|
||||
if !mapping[v] {
|
||||
t.Errorf("ToSlice() missing element %d; got %v", v, got)
|
||||
}
|
||||
func TestNew(t *testing.T) {
|
||||
wantThreadsafe := false
|
||||
set := New[string](wantThreadsafe)
|
||||
if _, ok := set.(*simpleset.Set[string]); !ok {
|
||||
t.Fatalf("error: wanted *simpleset.Set[string]; got %T\n", set)
|
||||
}
|
||||
wantThreadsafe = true
|
||||
set = New[string](wantThreadsafe)
|
||||
if _, ok := set.(*threadsafeset.Set[string]); !ok {
|
||||
t.Fatalf("error: wanted *threadsafeset.Set[string]; got %T\n", set)
|
||||
}
|
||||
}
|
||||
|
||||
77
simpleset/set.go
Normal file
77
simpleset/set.go
Normal file
@ -0,0 +1,77 @@
|
||||
package simpleset
|
||||
|
||||
import "maps"
|
||||
|
||||
type Set[T comparable] struct {
|
||||
contents map[T]struct{}
|
||||
}
|
||||
|
||||
func New[T comparable](elements ...T) *Set[T] {
|
||||
s := &Set[T]{
|
||||
contents: make(map[T]struct{}),
|
||||
}
|
||||
for _, e := range elements {
|
||||
s.Add(e)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Set[T]) Add(element T) {
|
||||
s.contents[element] = struct{}{}
|
||||
}
|
||||
|
||||
func (s *Set[T]) Remove(element T) {
|
||||
delete(s.contents, element)
|
||||
}
|
||||
|
||||
func (s *Set[T]) Contains(element T) bool {
|
||||
_, exists := s.contents[element]
|
||||
return exists
|
||||
}
|
||||
|
||||
func (s *Set[T]) Size() int {
|
||||
return len(s.contents)
|
||||
}
|
||||
|
||||
func (s *Set[T]) ToSlice() []T {
|
||||
elements := make([]T, 0, len(s.contents))
|
||||
for e := range s.contents {
|
||||
elements = append(elements, e)
|
||||
}
|
||||
return elements
|
||||
}
|
||||
|
||||
func (s *Set[T]) Equal(other *Set[T]) bool {
|
||||
return maps.Equal(s.contents, other.contents)
|
||||
}
|
||||
|
||||
func (s *Set[T]) Union(other *Set[T]) *Set[T] {
|
||||
result := New[T]()
|
||||
for e := range s.contents {
|
||||
result.Add(e)
|
||||
}
|
||||
for e := range other.contents {
|
||||
result.Add(e)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *Set[T]) Intersection(other *Set[T]) *Set[T] {
|
||||
result := New[T]()
|
||||
for e := range s.contents {
|
||||
if other.Contains(e) {
|
||||
result.Add(e)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (s *Set[T]) Difference(other *Set[T]) *Set[T] {
|
||||
result := New[T]()
|
||||
for e := range s.contents {
|
||||
if !other.Contains(e) {
|
||||
result.Add(e)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
196
simpleset/set_test.go
Normal file
196
simpleset/set_test.go
Normal file
@ -0,0 +1,196 @@
|
||||
package simpleset
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestIntersection(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3, 4)
|
||||
s2 := New[int](3, 4, 5, 6)
|
||||
want := New[int](3, 4)
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntersection_Empty(t *testing.T) {
|
||||
s1 := New[int](1, 2)
|
||||
s2 := New[int](3, 4)
|
||||
want := New[int]()
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntersection_Identical(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3)
|
||||
s2 := New[int](1, 2, 3)
|
||||
want := New[int](1, 2, 3)
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntersection_Subset(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3, 4, 5)
|
||||
s2 := New[int](2, 3)
|
||||
want := New[int](2, 3)
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntersection_EmptySet(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3)
|
||||
s2 := New[int]()
|
||||
want := New[int]()
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntersection_BothEmpty(t *testing.T) {
|
||||
s1 := New[int]()
|
||||
s2 := New[int]()
|
||||
want := New[int]()
|
||||
got := s1.Intersection(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Intersection() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifference(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3, 4)
|
||||
s2 := New[int](3, 4, 5, 6)
|
||||
want := New[int](1, 2)
|
||||
got := s1.Difference(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Difference() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifference_Empty(t *testing.T) {
|
||||
s1 := New[int](1, 2)
|
||||
s2 := New[int](1, 2)
|
||||
want := New[int]()
|
||||
got := s1.Difference(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Difference() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifference_Subset(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3, 4, 5)
|
||||
s2 := New[int](2, 3)
|
||||
want := New[int](1, 4, 5)
|
||||
got := s1.Difference(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Difference() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifference_EmptySet(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3)
|
||||
s2 := New[int]()
|
||||
want := New[int](1, 2, 3)
|
||||
got := s1.Difference(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Difference() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDifference_BothEmpty(t *testing.T) {
|
||||
s1 := New[int]()
|
||||
s2 := New[int]()
|
||||
want := New[int]()
|
||||
got := s1.Difference(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Difference() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnion(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3)
|
||||
s2 := New[int](3, 4, 5)
|
||||
want := New[int](1, 2, 3, 4, 5)
|
||||
got := s1.Union(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Union() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnion_EmptySet(t *testing.T) {
|
||||
s1 := New[int](1, 2, 3)
|
||||
s2 := New[int]()
|
||||
want := New[int](1, 2, 3)
|
||||
got := s1.Union(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Union() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUnion_BothEmpty(t *testing.T) {
|
||||
s1 := New[int]()
|
||||
s2 := New[int]()
|
||||
want := New[int]()
|
||||
got := s1.Union(s2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Union() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
want := New[int](1, 2, 3)
|
||||
got := New[int]()
|
||||
got.Add(1)
|
||||
got.Add(2)
|
||||
got.Add(3)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Add() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemov(t *testing.T) {
|
||||
want := New[int](1, 3)
|
||||
got := New[int](1, 2, 3)
|
||||
got.Remove(2)
|
||||
if !got.Equal(want) {
|
||||
t.Errorf("Remove() = %v; want %v", got, want)
|
||||
}
|
||||
}
|
||||
func TestContains(t *testing.T) {
|
||||
s := New[int](1, 2, 3)
|
||||
if !s.Contains(2) {
|
||||
t.Errorf("Contains(2) = false; want true")
|
||||
}
|
||||
if s.Contains(4) {
|
||||
t.Errorf("Contains(4) = true; want false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSize(t *testing.T) {
|
||||
s := New[int](1, 2, 3, 4)
|
||||
want := 4
|
||||
got := s.Size()
|
||||
if got != want {
|
||||
t.Errorf("Size() = %d; want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToSlice(t *testing.T) {
|
||||
s := New[int](1, 2, 3)
|
||||
want := []int{1, 2, 3}
|
||||
got := s.ToSlice()
|
||||
mapping := make(map[int]bool)
|
||||
for _, v := range got {
|
||||
mapping[v] = true
|
||||
}
|
||||
for _, v := range want {
|
||||
if !mapping[v] {
|
||||
t.Errorf("ToSlice() missing element %d; got %v", v, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
81
threadsafeset/set.go
Normal file
81
threadsafeset/set.go
Normal file
@ -0,0 +1,81 @@
|
||||
package threadsafeset
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"code.wmdillon.com/wmdillon/set/simpleset"
|
||||
)
|
||||
|
||||
type Set[T comparable] struct {
|
||||
mutex sync.RWMutex
|
||||
set *simpleset.Set[T]
|
||||
}
|
||||
|
||||
func New[T comparable](elements ...T) *Set[T] {
|
||||
s := &Set[T]{
|
||||
set: simpleset.New(elements...),
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Set[T]) Add(element T) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
s.set.Add(element)
|
||||
}
|
||||
|
||||
func (s *Set[T]) Remove(element T) {
|
||||
s.mutex.Lock()
|
||||
defer s.mutex.Unlock()
|
||||
s.set.Remove(element)
|
||||
}
|
||||
|
||||
func (s *Set[T]) Contains(element T) bool {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
return s.set.Contains(element)
|
||||
}
|
||||
|
||||
func (s *Set[T]) Size() int {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
return s.set.Size()
|
||||
}
|
||||
|
||||
func (s *Set[T]) ToSlice() []T {
|
||||
s.mutex.RLock()
|
||||
defer s.mutex.RUnlock()
|
||||
return s.set.ToSlice()
|
||||
}
|
||||
|
||||
// only locks this.mutex, locking other.mutex is the responsibility
|
||||
// of the caller.
|
||||
func (this *Set[T]) Equal(other *Set[T]) bool {
|
||||
this.mutex.RLock()
|
||||
defer this.mutex.RUnlock()
|
||||
return this.set.Equal(other.set)
|
||||
}
|
||||
|
||||
// only locks this.mutex, locking other.mutex is the responsibility
|
||||
// of the caller.
|
||||
func (this *Set[T]) Union(other *Set[T]) *Set[T] {
|
||||
this.mutex.RLock()
|
||||
defer this.mutex.RUnlock()
|
||||
return &Set[T]{set: this.set.Union(other.set)}
|
||||
}
|
||||
|
||||
// only locks this.mutex, locking other.mutex is the responsibility
|
||||
// of the caller.
|
||||
func (this *Set[T]) Intersection(other *Set[T]) *Set[T] {
|
||||
this.mutex.RLock()
|
||||
defer this.mutex.RUnlock()
|
||||
return &Set[T]{set: this.set.Intersection(other.set)}
|
||||
}
|
||||
|
||||
// only locks this.mutex, locking other.mutex is the responsibility
|
||||
// of the caller.
|
||||
func (this *Set[T]) Difference(other *Set[T]) *Set[T] {
|
||||
this.mutex.RLock()
|
||||
defer this.mutex.RUnlock()
|
||||
return &Set[T]{set: this.set.Difference(other.set)}
|
||||
}
|
||||
137
threadsafeset/set_test.go
Normal file
137
threadsafeset/set_test.go
Normal file
@ -0,0 +1,137 @@
|
||||
package threadsafeset
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"math/rand/v2"
|
||||
"os"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var Words = func() []string {
|
||||
f, err := os.Open("../words")
|
||||
if err != nil {
|
||||
panic("error opening ../words: " + err.Error())
|
||||
}
|
||||
defer f.Close()
|
||||
scanner := bufio.NewScanner(f)
|
||||
results := make([]string, 0)
|
||||
for scanner.Scan() {
|
||||
if line := strings.TrimSpace(scanner.Text()); len(line) > 0 {
|
||||
results = append(results, line)
|
||||
}
|
||||
}
|
||||
if err := scanner.Err(); err != nil {
|
||||
panic("error from scanner.Err(): " + err.Error())
|
||||
}
|
||||
return results
|
||||
}()
|
||||
|
||||
func CloneWords() []string {
|
||||
results := make([]string, 0, len(Words))
|
||||
for _, word := range Words {
|
||||
results = append(results, strings.Clone(word))
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func RandomWord() string {
|
||||
return Words[rand.IntN(len(Words))]
|
||||
}
|
||||
|
||||
func ContextNotDone(ctx context.Context) bool {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return false
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func TestAdd(t *testing.T) {
|
||||
set := New[string]()
|
||||
var wg sync.WaitGroup
|
||||
ctx, cancel := context.WithTimeout(t.Context(), time.Second*2)
|
||||
defer cancel()
|
||||
for range runtime.NumCPU() * 10 {
|
||||
wg.Go(func() {
|
||||
for i := 0; i < len(Words) && ContextNotDone(ctx); i++ {
|
||||
set.Add(Words[i])
|
||||
}
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestContains(t *testing.T) {
|
||||
set := New(Words...)
|
||||
var wg sync.WaitGroup
|
||||
ctx, cancel := context.WithTimeout(t.Context(), time.Second*2)
|
||||
defer cancel()
|
||||
for range runtime.NumCPU() * 10 {
|
||||
wg.Go(func() {
|
||||
for i := 0; i < len(Words) && ContextNotDone(ctx); i++ {
|
||||
if !set.Contains(Words[i]) {
|
||||
panic("error: " + Words[i] + " not found")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestRemove(t *testing.T) {
|
||||
set := New(Words...)
|
||||
var wg sync.WaitGroup
|
||||
for range runtime.NumCPU() * 10 {
|
||||
wg.Go(func() {
|
||||
for _, word := range Words {
|
||||
set.Remove(word)
|
||||
}
|
||||
})
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestSize(t *testing.T) {
|
||||
set := New[string]()
|
||||
for index, word := range Words {
|
||||
if want, got := index, set.Size(); want != got {
|
||||
t.Fatalf("error: wanted size %d; got %d\n", want, got)
|
||||
}
|
||||
set.Add(word)
|
||||
if want, got := index+1, set.Size(); want != got {
|
||||
t.Fatalf("error: wanted size %d; got %d\n", want, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestToSlice(t *testing.T) {
|
||||
want := CloneWords()
|
||||
slices.Sort(want)
|
||||
set := New(Words...)
|
||||
got := set.ToSlice()
|
||||
slices.Sort(got)
|
||||
if !slices.Equal(want, got) {
|
||||
t.Fatalf("error: mismatched ToSlice() output\n")
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAdd(b *testing.B) {
|
||||
set := New[string]()
|
||||
for b.Loop() {
|
||||
set.Add(RandomWord())
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkContains(b *testing.B) {
|
||||
set := New(Words...)
|
||||
for b.Loop() {
|
||||
set.Contains(RandomWord())
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user