-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
bd7c0b8
commit 864a900
Showing
3 changed files
with
244 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() | ||
} | ||
}) | ||
} |