From 3e8a006611af121db1786a211976357d680cb74d Mon Sep 17 00:00:00 2001 From: William Dillon Date: Tue, 6 May 2025 22:58:18 -0400 Subject: [PATCH] adding set --- go.mod | 3 ++ set.go | 24 +++++++++ set_test.go | 91 ++++++++++++++++++++++++++++++++++ simpleset/simpleset.go | 59 ++++++++++++++++++++++ threadsafeset/threadsafeset.go | 68 +++++++++++++++++++++++++ 5 files changed, 245 insertions(+) create mode 100644 go.mod create mode 100644 set.go create mode 100644 set_test.go create mode 100644 simpleset/simpleset.go create mode 100644 threadsafeset/threadsafeset.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a9ea2f3 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module set + +go 1.24.3 diff --git a/set.go b/set.go new file mode 100644 index 0000000..8ab8938 --- /dev/null +++ b/set.go @@ -0,0 +1,24 @@ +package set + +import ( + "set/simpleset" + "set/threadsafeset" +) + +type Set[T comparable] interface { + Insert(T) (inserted bool) + Contains(T) bool + Remove(T) (removed bool) + Len() int + ToSlice() []T + Clear() +} + +func New[T comparable](threadsafe bool) Set[T] { + switch threadsafe { + case true: + return threadsafeset.New[T]() + default: + return simpleset.New[T]() + } +} diff --git a/set_test.go b/set_test.go new file mode 100644 index 0000000..4898796 --- /dev/null +++ b/set_test.go @@ -0,0 +1,91 @@ +package set + +import ( + "testing" +) + +func TestSimpleSet(t *testing.T) { + words := []string{"a", "b", "c"} + set := New[string](false) + for _, word := range words { + if !set.Insert(word) { + t.Fatalf("error: expected true while inserting '%s'; got false\n", word) + } + } + if want, got := len(words), set.Len(); want != got { + t.Fatalf("error: wanted %d entries; got %d\n", want, got) + } + for _, word := range words { + if set.Insert(word) { + t.Fatalf("error: expected false during duplicate insert for '%s'; got true\n", word) + } + } + for _, word := range words { + if !set.Contains(word) { + t.Fatalf("error: didn't find word '%s' in set after second insert...\n", word) + } + } + for _, word := range words { + if !set.Remove(word) { + t.Fatalf("error removing word '%s'\n", word) + } + } + if want, got := 0, set.Len(); want != got { + t.Fatalf("error: wanted %d entries; got %d\n", want, got) + } + for _, word := range words { + if !set.Insert(word) { + t.Fatalf("error: expected true while inserting '%s'; got false\n", word) + } + } + if want, got := len(words), set.Len(); want != got { + t.Fatalf("error: wanted %d entries; got %d\n", want, got) + } + set.Clear() + if want, got := 0, set.Len(); want != got { + t.Fatalf("error: wanted %d entries; got %d\n", want, got) + } +} + +func TestThreadsafeSet(t *testing.T) { + words := []string{"a", "b", "c"} + set := New[string](true) + for _, word := range words { + if !set.Insert(word) { + t.Fatalf("error: expected true while inserting '%s'; got false\n", word) + } + } + if want, got := len(words), set.Len(); want != got { + t.Fatalf("error: wanted %d entries; got %d\n", want, got) + } + for _, word := range words { + if set.Insert(word) { + t.Fatalf("error: expected false during duplicate insert for '%s'; got true\n", word) + } + } + for _, word := range words { + if !set.Contains(word) { + t.Fatalf("error: didn't find word '%s' in set after second insert...\n", word) + } + } + for _, word := range words { + if !set.Remove(word) { + t.Fatalf("error removing word '%s'\n", word) + } + } + if want, got := 0, set.Len(); want != got { + t.Fatalf("error: wanted %d entries; got %d\n", want, got) + } + for _, word := range words { + if !set.Insert(word) { + t.Fatalf("error: expected true while inserting '%s'; got false\n", word) + } + } + if want, got := len(words), set.Len(); want != got { + t.Fatalf("error: wanted %d entries; got %d\n", want, got) + } + set.Clear() + if want, got := 0, set.Len(); want != got { + t.Fatalf("error: wanted %d entries; got %d\n", want, got) + } +} diff --git a/simpleset/simpleset.go b/simpleset/simpleset.go new file mode 100644 index 0000000..91c2aec --- /dev/null +++ b/simpleset/simpleset.go @@ -0,0 +1,59 @@ +package simpleset + +type SimpleSet[T comparable] struct { + set map[T]any +} + +func New[T comparable]() *SimpleSet[T] { + return &SimpleSet[T]{ + set: make(map[T]any), + } +} + +func (s *SimpleSet[T]) lockedInitMapIfNil() { + if s.set == nil { + s.set = make(map[T]any) + } +} + +func (s *SimpleSet[T]) Insert(t T) (inserted bool) { + s.lockedInitMapIfNil() + if _, found := s.set[t]; found { + return false + } else { + s.set[t] = struct{}{} + return true + } +} + +func (s *SimpleSet[T]) Contains(t T) bool { + s.lockedInitMapIfNil() + _, found := s.set[t] + return found +} + +func (s *SimpleSet[T]) Len() int { + s.lockedInitMapIfNil() + return len(s.set) +} + +func (s *SimpleSet[T]) ToSlice() []T { + s.lockedInitMapIfNil() + results := make([]T, 0, s.Len()) + for k := range s.set { + results = append(results, k) + } + return results +} + +func (s *SimpleSet[T]) Clear() { + s.set = make(map[T]any) +} + +func (s *SimpleSet[T]) Remove(t T) (removed bool) { + if !s.Contains(t) { + return false + } + delete(s.set, t) + return true +} diff --git a/threadsafeset/threadsafeset.go b/threadsafeset/threadsafeset.go new file mode 100644 index 0000000..ca3c307 --- /dev/null +++ b/threadsafeset/threadsafeset.go @@ -0,0 +1,68 @@ +package threadsafeset + +import ( + "set/simpleset" + "sync" +) + +type ThreadsafeSet[T comparable] struct { + mutex sync.RWMutex + set *simpleset.SimpleSet[T] +} + +func New[T comparable]() *ThreadsafeSet[T] { + return &ThreadsafeSet[T]{ + set: simpleset.New[T](), + } +} + +func (s *ThreadsafeSet[T]) lockedInitMapIfNil() { + if s.set == nil { + s.set = simpleset.New[T]() + } +} + +func (s *ThreadsafeSet[T]) Insert(t T) (inserted bool) { + s.mutex.Lock() + defer s.mutex.Unlock() + s.lockedInitMapIfNil() + return s.set.Insert(t) +} + +func (s *ThreadsafeSet[T]) Contains(t T) bool { + s.mutex.RLock() + defer s.mutex.RUnlock() + return s.set != nil && s.set.Contains(t) +} + +func (s *ThreadsafeSet[T]) Len() int { + s.mutex.RLock() + defer s.mutex.RUnlock() + if s.set == nil { + return 0 + } + return s.set.Len() +} + +func (s *ThreadsafeSet[T]) ToSlice() []T { + s.mutex.RLock() + defer s.mutex.RUnlock() + if s.set == nil { + return nil + } + return s.set.ToSlice() +} + +func (s *ThreadsafeSet[T]) Clear() { + s.mutex.Lock() + defer s.mutex.Unlock() + s.lockedInitMapIfNil() + s.set.Clear() +} + +func (s *ThreadsafeSet[T]) Remove(t T) (removed bool) { + s.mutex.Lock() + defer s.mutex.Unlock() + s.lockedInitMapIfNil() + return s.set.Remove(t) +}