From 6f1eadb7b55ae7a94ad67a7c29b19b76609d6b95 Mon Sep 17 00:00:00 2001 From: charlie <3140647@qq.com> Date: Sun, 27 Mar 2022 10:23:14 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B3=9B=E5=9E=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- collections/generics/arraystack.go | 57 +++++++++++ collections/generics/concurrent_map.go | 102 ++++++++++++++++++++ collections/generics/concurrent_map_test.go | 83 ++++++++++++++++ collections/generics/hashset.go | 69 +++++++++++++ collections/generics/hashset_test.go | 17 ++++ collections/generics/map.go | 11 +++ collections/generics/map_test.go | 31 ++++++ collections/generics/rwlock_map.go | 60 ++++++++++++ 8 files changed, 430 insertions(+) create mode 100644 collections/generics/arraystack.go create mode 100644 collections/generics/concurrent_map.go create mode 100644 collections/generics/concurrent_map_test.go create mode 100644 collections/generics/hashset.go create mode 100644 collections/generics/hashset_test.go create mode 100644 collections/generics/map.go create mode 100644 collections/generics/map_test.go create mode 100644 collections/generics/rwlock_map.go diff --git a/collections/generics/arraystack.go b/collections/generics/arraystack.go new file mode 100644 index 0000000..a4362f6 --- /dev/null +++ b/collections/generics/arraystack.go @@ -0,0 +1,57 @@ +package generics + +import "errors" + +type ArrayStack[T any] struct { + data []T + length int +} + +func NewArrayStack[T any]() *ArrayStack[T] { + return &ArrayStack[T]{data: []T{}, length: 0} +} + +func (s *ArrayStack[T]) Data() []T { + return s.data +} + +func (s *ArrayStack[T]) Size() int { + return s.length +} + +func (s *ArrayStack[T]) IsEmpty() bool { + return s.length == 0 +} + +// Push element into stack +func (s *ArrayStack[T]) Push(value T) { + s.data = append([]T{value}, s.data...) + s.length++ +} + +// Pop delete the top element of stack then return it, if stack is empty, return nil and error +func (s *ArrayStack[T]) Pop() (*T, error) { + if s.IsEmpty() { + return nil, errors.New("stack is empty") + } + + topItem := s.data[0] + s.data = s.data[1:] + s.length-- + + return &topItem, nil +} + +// Peak return the top element of stack then return it +func (s *ArrayStack[T]) Peak() (*T, error) { + if s.IsEmpty() { + return nil, errors.New("stack is empty") + } + return &s.data[0], nil +} + +// Clear the stack data +func (s *ArrayStack[T]) Clear() { + s.data = []T{} + s.length = 0 +} diff --git a/collections/generics/concurrent_map.go b/collections/generics/concurrent_map.go new file mode 100644 index 0000000..3e1982d --- /dev/null +++ b/collections/generics/concurrent_map.go @@ -0,0 +1,102 @@ +package generics + +import ( + "fmt" + "runtime" + "sync" + + "github.com/charlienet/go-mixed/hash" +) + +var _ Map[string, string] = &ConcurrnetMap[string, string]{} + +var defaultNumOfBuckets = runtime.GOMAXPROCS(runtime.NumCPU()) + +type ConcurrnetMap[K comparable, V any] struct { + buckets []Map[K, V] + numOfBuckets uint64 +} + +func NewConcurrnetMap[K comparable, V any]() Map[K, V] { + num := defaultNumOfBuckets + + buckets := make([]Map[K, V], num) + for i := 0; i < num; i++ { + buckets[i] = NewRWLockMap[K, V]() + } + + return &ConcurrnetMap[K, V]{ + numOfBuckets: uint64(num), + buckets: buckets, + } +} + +func (m *ConcurrnetMap[K, V]) Set(key K, value V) { + m.getBucket(key).Set(key, value) +} + +func (m *ConcurrnetMap[K, V]) Get(key K) (V, bool) { + return m.getBucket(key).Get(key) +} + +func (m *ConcurrnetMap[K, V]) Delete(key K) { + im := m.getBucket(key) + im.Delete(key) +} + +func (m *ConcurrnetMap[K, V]) ForEach(f func(K, V)) { + var wg sync.WaitGroup + + num := int(m.numOfBuckets) + + wg.Add(int(m.numOfBuckets)) + for i := 0; i < num; i++ { + go func(i int) { + m.buckets[i].ForEach(f) + wg.Done() + }(i) + } + + wg.Wait() +} + +func (m *ConcurrnetMap[K, V]) Clone() Map[K, V] { + + num := int(m.numOfBuckets) + + buckets := make([]Map[K, V], m.numOfBuckets) + for i := 0; i < num; i++ { + buckets[i] = m.buckets[i].Clone() + } + + return &ConcurrnetMap[K, V]{ + buckets: buckets, + numOfBuckets: m.numOfBuckets, + } +} + +func (m *ConcurrnetMap[K, V]) Clear() { + for i := 0; i < int(m.numOfBuckets); i++ { + m.buckets[i].Clear() + } +} + +func (m *ConcurrnetMap[K, V]) Count() int { + var count int + for i := 0; i < int(m.numOfBuckets); i++ { + count += m.buckets[i].Count() + } + + return count +} + +func (m *ConcurrnetMap[K, V]) getBucket(k K) Map[K, V] { + bytes := getBytes(k) + + id := hash.XXHashUint64(bytes) % m.numOfBuckets + return m.buckets[id] +} + +func getBytes(k any) []byte { + return []byte(fmt.Sprintf("%v", k)) +} diff --git a/collections/generics/concurrent_map_test.go b/collections/generics/concurrent_map_test.go new file mode 100644 index 0000000..3b4aa97 --- /dev/null +++ b/collections/generics/concurrent_map_test.go @@ -0,0 +1,83 @@ +package generics_test + +import ( + "fmt" + "runtime" + "testing" + + "github.com/charlienet/go-mixed/collections/generics" +) + +func TestConcurrentMap(t *testing.T) { + t.Log(runtime.GOMAXPROCS(runtime.NumCPU())) + + key := "abc" + value := "bcd" + + m := generics.NewConcurrnetMap[string, string]() + m.Set(key, value) + v, ok := m.Get(key) + t.Log("v:", v, ok) + + m.Delete(key) + v, ok = m.Get(key) + t.Log("v:", v, ok) +} + +func TestForEach(t *testing.T) { + + m := generics.NewConcurrnetMap[string, string]() + for k := 0; k < 10; k++ { + key := fmt.Sprintf("abc-%d", k) + value := fmt.Sprintf("abc-%d", k) + m.Set(key, value) + } + + m.ForEach(func(s1, s2 string) { + t.Log(s1, s2) + }) + + t.Log("finish") +} + +func TestExist(t *testing.T) { + key := "abc" + value := "bcd" + + m := generics.NewConcurrnetMap[string, string]() + m.Set(key, value) + + keyv2 := "abc" + + t.Logf("%p %p", &key, &keyv2) + _, ok := m.Get(keyv2) + t.Log("ok", ok) +} + +func BenchmarkConcurrnetMap(b *testing.B) { + key := "abc" + value := "bcd" + + m := generics.NewConcurrnetMap[string, string]() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + m.Set(key, value) + m.Get(key) + m.Delete(key) + } + }) +} + +func BenchmarkRWLockMap(b *testing.B) { + key := "abc" + value := "bcd" + + m := generics.NewRWLockMap[string, string]() + b.RunParallel(func(pb *testing.PB) { + for pb.Next() { + m.Set(key, value) + m.Get(key) + m.Delete(key) + } + }) +} diff --git a/collections/generics/hashset.go b/collections/generics/hashset.go new file mode 100644 index 0000000..1758a49 --- /dev/null +++ b/collections/generics/hashset.go @@ -0,0 +1,69 @@ +package generics + + +type Set[T comparable] map[T]struct{} + +func NewHashSet[T comparable](values ...T) Set[T] { + set := make(Set[T], len(values)) + set.Add(values...) + return set +} + +func (s Set[T]) Add(values ...T) { + for _, v := range values { + s[v] = struct{}{} + } +} + +func (s Set[T]) Contain(value T) bool { + _, ok := s[value] + return ok +} + +func (s Set[T]) Clone() Set[T] { + set := NewHashSet[T]() + set.Add(s.Values()...) + return set +} + +func (s Set[T]) Iterate(fn func(value T)) { + for v := range s { + fn(v) + } +} + +// Union creates a new set contain all element of set s and other +func (s Set[T]) Union(other Set[T]) Set[T] { + set := s.Clone() + set.Add(other.Values()...) + return set +} + +// Intersection creates a new set whose element both be contained in set s and other +func (s Set[T]) Intersection(other Set[T]) Set[T] { + set := NewHashSet[T]() + s.Iterate(func(value T) { + if other.Contain(value) { + set.Add(value) + } + }) + + return set +} + +func (s Set[T]) Values() []T { + values := make([]T, 0, s.Size()) + s.Iterate(func(value T) { + values = append(values, value) + }) + + return values +} + +func (s Set[T]) IsEmpty() bool { + return len(s) == 0 +} + +func (s Set[T]) Size() int { + return len(s) +} diff --git a/collections/generics/hashset_test.go b/collections/generics/hashset_test.go new file mode 100644 index 0000000..e13b8c2 --- /dev/null +++ b/collections/generics/hashset_test.go @@ -0,0 +1,17 @@ +package generics_test + +import ( + "testing" + + "github.com/charlienet/go-mixed/collections/generics" +) + +func TestSet(t *testing.T) { + + s := generics.NewHashSet[int]() + s.Add(1, 2, 3) + + expected := generics.NewHashSet(1, 2, 3) + + _ = expected +} diff --git a/collections/generics/map.go b/collections/generics/map.go new file mode 100644 index 0000000..01c72d3 --- /dev/null +++ b/collections/generics/map.go @@ -0,0 +1,11 @@ +package generics + +type Map[K comparable, V any] interface { + Set(key K, value V) + Get(key K) (V, bool) + Delete(key K) + ForEach(f func(K, V)) + Clone() Map[K, V] + Clear() + Count() int +} diff --git a/collections/generics/map_test.go b/collections/generics/map_test.go new file mode 100644 index 0000000..371287a --- /dev/null +++ b/collections/generics/map_test.go @@ -0,0 +1,31 @@ +package generics_test + +import ( + "testing" + + "github.com/charlienet/go-mixed/collections/generics" + "github.com/stretchr/testify/assert" +) + +func TestIMap(t *testing.T) { + k := "abc" + v := "bcd" + + var m generics.Map[string, string] = generics.NewConcurrnetMap[string, string]() + m.Set(k, v) + _, ok := m.Get(k) + assert.True(t, ok, "不存在") + t.Log(m.Count()) + + m.Delete(k) + _, ok = m.Get(k) + assert.False(t, ok, "不存在") + + t.Log(m.Count()) +} + +func TestMapCount(t *testing.T) { + mm := make(map[string]string) + mm["a"] = "b" + assert.Equal(t, 1, len(mm)) +} diff --git a/collections/generics/rwlock_map.go b/collections/generics/rwlock_map.go new file mode 100644 index 0000000..119157d --- /dev/null +++ b/collections/generics/rwlock_map.go @@ -0,0 +1,60 @@ +package generics + +import "sync" + +var _ Map[string, string] = &RWLockMap[string, string]{} + +type RWLockMap[K comparable, V any] struct { + m map[K]V + lock sync.RWMutex +} + +func NewRWLockMap[K comparable, V any]() Map[K, V] { + return &RWLockMap[K, V]{ + m: make(map[K]V), + } +} + +func (m *RWLockMap[K, V]) Get(key K) (V, bool) { + m.lock.RLock() + v, ok := m.m[key] + m.lock.RUnlock() + return v, ok +} + +func (m *RWLockMap[K, V]) Set(key K, value V) { + m.lock.Lock() + m.m[key] = value + m.lock.Unlock() +} + +func (m *RWLockMap[K, V]) Delete(key K) { + m.lock.Lock() + delete(m.m, key) + m.lock.Unlock() +} + +func (m *RWLockMap[K, V]) ForEach(f func(K, V)) { + for k, v := range m.m { + f(k, v) + } +} + +func (m *RWLockMap[K, V]) Clone() Map[K, V] { + new := make(map[K]V, m.Count()) + for k, v := range m.m { + new[k] = v + } + + return &RWLockMap[K, V]{ + m: new, + } +} + +func (m *RWLockMap[K, V]) Clear() { + m.m = make(map[K]V) +} + +func (m *RWLockMap[K, V]) Count() int { + return len(m.m) +}