Skip to content

Commit

Permalink
set.MapSet
Browse files Browse the repository at this point in the history
  • Loading branch information
CarsonSlovoka committed Apr 17, 2024
1 parent bd7c0b8 commit 864a900
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 1 deletion.
2 changes: 1 addition & 1 deletion v2/go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
module github.com/CarsonSlovoka/go-pkg/v2

go 1.19
go 1.21
146 changes: 146 additions & 0 deletions v2/set/set.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package set

import (
"cmp"
"fmt"
"slices"
"strings"
"sync"
)

type MapSet[T comparable] struct {
elem map[T]struct{}
sync.RWMutex
}

func NewMapSet[T comparable](elems ...T) *MapSet[T] {
s := MapSet[T]{}
s.elem = make(map[T]struct{})
s.Add(elems...)
return &s
}

func (s *MapSet[T]) Add(keys ...T) {
s.Lock()
defer s.Unlock()
for _, key := range keys {
if _, exists := s.elem[key]; exists {
continue
}
s.elem[key] = struct{}{}
}
}

func (s *MapSet[T]) Delete(keys ...T) {
s.Lock()
defer s.Unlock()
for _, key := range keys {
if _, exists := s.elem[key]; !exists {
continue
}
delete(s.elem, key)
}
}

func (s *MapSet[T]) Clear(keys ...T) {
s.Lock()
defer s.Unlock()
clear(s.elem)
}

func (s *MapSet[T]) Has(key T) bool {
s.RLock()
defer s.RUnlock()
_, exists := s.elem[key]
return exists
}

func (s *MapSet[T]) Contains(keys ...T) bool {
for _, key := range keys {
if !s.Has(key) {
return false
}
}
return true
}

func (s *MapSet[_]) Size() int {
s.RLock()
defer s.RUnlock()
return len(s.elem)
}

func (s *MapSet[_]) IsEmpty() bool {
s.RLock()
defer s.RUnlock()
return len(s.elem) == 0
}

func (s *MapSet[T]) Items() []T {
s.RLock()
defer s.RUnlock()
var items []T
for key, _ := range s.elem {
items = append(items, key)
}
return items
}

func (s *MapSet[T]) Union(s2 *MapSet[T]) *MapSet[T] {
s.RLock()
s2.RLock()
defer func() {
s.RUnlock()
s2.RUnlock()
}()

all := NewMapSet[T](s.Items()...)
all.Add(s2.Items()...)
return all
}

func (s *MapSet[T]) Diff(s2 *MapSet[T]) *MapSet[T] {
s.RLock()
s2.RLock()
defer func() {
s.RUnlock()
s2.RUnlock()
}()

all := s.Union(s2)

for _, k := range all.Items() {
if (s.Has(k) && s2.Has(k)) ||
(s2.Has(k) && s.Has(k)) {
all.Delete(k)
}
}
return all
}

func (s *MapSet[T]) String() string {
s.RLock()
defer s.RUnlock()
var strs []string
for key, _ := range s.elem { // 因為map是無序的,所以取出來的順序可能不同
strs = append(strs, fmt.Sprintf("%v", key))
}

// make sure the order do not change
slices.SortFunc(strs, func(a, b string) int {
return cmp.Compare(a, b)
})
return strings.Join(strs, " ")
}

func (s *MapSet[T]) Clone() *MapSet[T] {
s.RLock()
defer s.RUnlock()
s2 := NewMapSet[T]()
var ks []T
for key, _ := range s.elem {
ks = append(ks, key)
}
s2.Add(ks...)
return s2
}
97 changes: 97 additions & 0 deletions v2/set/set_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
package set_test

import (
"fmt"
"github.com/CarsonSlovoka/go-pkg/v2/set"
"testing"
)

func ExampleNewMapSet() {
s := set.NewMapSet[string]()
s.Add("Apple")
s.Add("Foo")
s.Add("Foo")
s.Add("Bar")
fmt.Println(s.Size()) // 3
fmt.Println(s.String()) // Apple Bar Foo
fmt.Println(s) // same as above
s.Delete("Foo")
fmt.Println(s.Has("Foo"))
fmt.Println(s.Has("Bar"))
fmt.Println(s) // Apple Bar

// Output:
// 3
// Apple Bar Foo
// Apple Bar Foo
// false
// true
// Apple Bar
}

func ExampleMapSet_Clear() {
s := set.NewMapSet([]int{1, 8, 3, 5, 8, 8}...)
s.Clear()
fmt.Println(s.Size())
s.Add(200, 100)
fmt.Println(s)
// Output:
// 0
// 100 200
}

func ExampleNewMapSet_slices() {
s := set.NewMapSet([]uint8{1, 8, 3, 5, 8, 8}...)
fmt.Println(s)
// Output:
// 1 3 5 8
}

func ExampleNewMapSet_string() {
s := set.NewMapSet("apple", "foo", "bar")
s.Delete("foo")
fmt.Println(s)
// Output:
// apple bar
}

func TestMapSet(t *testing.T) {
s1 := set.NewMapSet(1, 2, 3)
t.Run("clone", func(tt *testing.T) {
s2 := s1.Clone()
s2.Delete(2)
s2.Add(5)
if s2.String() != "1 3 5" {
tt.Fatal()
}
if s1.String() != "1 2 3" {
tt.Fatal()
}
})

t.Run("union", func(tt *testing.T) {
s2 := set.NewMapSet(2, 5, 8)
s3 := s1.Union(s2)
if s3.String() != "1 2 3 5 8" {
tt.Fatal()
}
})

t.Run("diff", func(tt *testing.T) {
s2 := set.NewMapSet(2, 5, 8)
s3 := s1.Diff(s2)
if s3.String() != "1 3 5 8" {
tt.Fatal()
}
})

t.Run("contains", func(tt *testing.T) {
if !s1.Contains(1, 3) { // true
tt.Fatal()
}

if s1.Contains(1, 8) { // false 8不符合
tt.Fatal()
}
})
}

0 comments on commit 864a900

Please sign in to comment.