From af7029133f8426253ae9245a5de0526076a625d9 Mon Sep 17 00:00:00 2001 From: charlie <3140647@qq.com> Date: Thu, 12 May 2022 09:35:26 +0800 Subject: [PATCH] sets --- sets/hash_set.go | 120 +++++++++++++++++++++++++++++++++--------- sets/hash_set_test.go | 83 +++++++++++++++++++++++++++++ sets/set.go | 98 +++++++++++++++++++++++++++++++++- sets/sorted_set.go | 69 +++++++++++++++++++++++- 4 files changed, 342 insertions(+), 28 deletions(-) create mode 100644 sets/hash_set_test.go diff --git a/sets/hash_set.go b/sets/hash_set.go index e3a761b..5a1980e 100644 --- a/sets/hash_set.go +++ b/sets/hash_set.go @@ -1,11 +1,22 @@ package sets -type hash_set[T comparable] map[T]struct{} +import ( + "bytes" + "encoding/json" + "fmt" + "strings" -func NewHashSet[T comparable](values ...T) hash_set[T] { + "golang.org/x/exp/constraints" +) + +var _ Set[string] = &hash_set[string]{} + +type hash_set[T constraints.Ordered] map[T]struct{} + +func NewHashSet[T constraints.Ordered](values ...T) *hash_set[T] { set := make(hash_set[T], len(values)) set.Add(values...) - return set + return &set } func (s hash_set[T]) Add(values ...T) { @@ -18,12 +29,49 @@ func (s hash_set[T]) Remove(v T) { delete(s, v) } -func (s hash_set[T]) Contain(value T) bool { +func (s hash_set[T]) Contains(value T) bool { _, ok := s[value] return ok } -func (s hash_set[T]) Clone() hash_set[T] { +func (s hash_set[T]) ContainsAny(values ...T) bool { + for _, v := range values { + if _, ok := s[v]; ok { + return true + } + } + + return false +} + +func (s hash_set[T]) ContainsAll(values ...T) bool { + for _, v := range values { + if _, ok := s[v]; !ok { + return false + } + } + + return true +} + +func (s hash_set[T]) Asc() Set[T] { + return s.copyToSorted().Asc() +} + +func (s hash_set[T]) Desc() Set[T] { + return s.copyToSorted().Desc() +} + +func (s hash_set[T]) copyToSorted() Set[T] { + orderd := NewSortedSet[T]() + for k := range s { + orderd.Add(k) + } + + return orderd +} + +func (s *hash_set[T]) Clone() *hash_set[T] { set := NewHashSet[T]() set.Add(s.ToSlice()...) return set @@ -35,25 +83,6 @@ func (s hash_set[T]) Iterate(fn func(value T)) { } } -// Union creates a new set contain all element of set s and other -func (s hash_set[T]) Union(other hash_set[T]) hash_set[T] { - set := s.Clone() - set.Add(other.ToSlice()...) - return set -} - -// Intersection creates a new set whose element both be contained in set s and other -func (s hash_set[T]) Intersection(other hash_set[T]) hash_set[T] { - set := NewHashSet[T]() - s.Iterate(func(value T) { - if other.Contain(value) { - set.Add(value) - } - }) - - return set -} - func (s hash_set[T]) ToSlice() []T { values := make([]T, 0, s.Size()) s.Iterate(func(value T) { @@ -70,3 +99,46 @@ func (s hash_set[T]) IsEmpty() bool { func (s hash_set[T]) Size() int { return len(s) } + +func (s hash_set[T]) MarshalJSON() ([]byte, error) { + items := make([]string, 0, s.Size()) + + for ele := range s { + b, err := json.Marshal(ele) + if err != nil { + return nil, err + } + + items = append(items, string(b)) + } + + return []byte(fmt.Sprintf("[%s]", strings.Join(items, ", "))), nil +} + +func (s hash_set[T]) UnmarshalJSON(b []byte) error { + var i []any + + d := json.NewDecoder(bytes.NewReader(b)) + d.UseNumber() + err := d.Decode(&i) + if err != nil { + return err + } + + for _, v := range i { + if t, ok := v.(T); ok { + s.Add(t) + } + } + + return nil +} + +func (s hash_set[T]) String() string { + l := make([]string, 0, len(s)) + for k := range s { + l = append(l, fmt.Sprint(k)) + } + + return fmt.Sprintf("{%s}", strings.Join(l, ", ")) +} diff --git a/sets/hash_set_test.go b/sets/hash_set_test.go new file mode 100644 index 0000000..68dd9ac --- /dev/null +++ b/sets/hash_set_test.go @@ -0,0 +1,83 @@ +package sets_test + +import ( + "fmt" + "testing" + + "github.com/charlienet/go-mixed/json" + "github.com/charlienet/go-mixed/sets" + "github.com/stretchr/testify/assert" +) + +func TestString(t *testing.T) { + s := sets.NewHashSet("abc", "bcd") + t.Log(s) +} + +func SortedSetStrign(t *testing.T) { + s := sets.NewSortedSet("abc", "bcd") + t.Log(s) +} + +func TestContains(t *testing.T) { + s := sets.NewHashSet("abc", "bcd", "efg", "b") + assert.Equal(t, true, s.Contains("b")) +} + +func TestContainsAll(t *testing.T) { + +} + +func TestContainsAny(t *testing.T) { + +} + +func TestMarshal(t *testing.T) { + s := sets.NewHashSet("abc", "bcd", "efg", "b") + t.Log(json.StructToJsonIndent(s)) +} + +type GenericSet[T any] interface { + WithField(GenericSet[T]) T + Info(args ...any) +} + +var _ GenericSet[*MySet[string]] = &MySet[string]{} + +type MySet[T comparable] struct { +} + +func (m *MySet[T]) WithField(other GenericSet[*MySet[T]]) *MySet[T] { + other.Info("with field") + + return m +} + +func (m *MySet[T]) Info(args ...any) { + fmt.Println("abc", args) +} + +func DoStuff[T GenericSet[T]](t T) { + t.WithField(t).Info("here") +} + +func TestDoStuff(t *testing.T) { + DoStuff(&MySet[string]{}) + + // DoStuff(sets.NewHashSet("aaa")) +} + +func TestUnion(t *testing.T) { + ret := sets.Union[string](sets.NewHashSet("abc", "bcd", "e"), sets.NewHashSet("abc", "f", "bcd")) + t.Log(ret) +} + +func TestDifference(t *testing.T) { + ret := sets.Difference[string](sets.NewHashSet("abc", "bcd", "e"), sets.NewHashSet("abc", "f", "bcd")) + t.Log(ret) +} + +func TestIntersection(t *testing.T) { + ret := sets.Intersection[string](sets.NewHashSet("abc", "bcd", "e"), sets.NewHashSet("abc", "f", "bcd")) + t.Log(ret) +} diff --git a/sets/set.go b/sets/set.go index a2d8d34..f82caad 100644 --- a/sets/set.go +++ b/sets/set.go @@ -1,8 +1,102 @@ package sets +import ( + "sync" + + "github.com/charlienet/go-mixed/locker" + "golang.org/x/exp/constraints" +) + type Set[T comparable] interface { - Add(values ...T) + Add(...T) Remove(v T) - Contain(value T) bool + Asc() Set[T] + Desc() Set[T] + Contains(T) bool + ContainsAny(...T) bool + ContainsAll(...T) bool IsEmpty() bool + ToSlice() []T // 转换为切片 +} + +var defaultOptions = option{locker: locker.NewEmptyLocker()} + +type option struct { + locker sync.Locker +} + +type setFunc func(option) + +func WithSync() setFunc { + return func(o option) { + o.locker = &sync.RWMutex{} + } +} + +// 并集 +func Union[T constraints.Ordered](sets ...Set[T]) Set[T] { + if len(sets) == 0 { + return NewHashSet[T]() + } + if len(sets) == 1 { + return sets[0] + } + + ret := NewHashSet[T]() + for i := range sets { + ret.Add(sets[i].ToSlice()...) + } + + return ret +} + +// 交集 +func Intersection[T constraints.Ordered](sets ...Set[T]) Set[T] { + if len(sets) == 0 { + return NewHashSet[T]() + } + if len(sets) == 1 { + return sets[0] + } + + ret := NewHashSet[T]() + base := sets[0] + for _, v := range base.ToSlice() { + var insert = true + for _, s := range sets[1:] { + if !s.Contains(v) { + insert = false + break + } + } + + if insert { + ret.Add(v) + } + } + + return ret +} + +// 差集 +func Difference[T constraints.Ordered](main Set[T], sets ...Set[T]) Set[T] { + if len(sets) == 0 { + return main + } + + ret := NewHashSet[T]() + for _, v := range sets[0].ToSlice() { + isDiff := true + for _, s := range sets { + if s.Contains(v) { + isDiff = false + } + } + + if isDiff { + ret.Add(v) + } + } + + return ret } diff --git a/sets/sorted_set.go b/sets/sorted_set.go index a366676..15f5514 100644 --- a/sets/sorted_set.go +++ b/sets/sorted_set.go @@ -1,16 +1,81 @@ package sets -type sorted_set[T comparable] struct { +import ( + "golang.org/x/exp/constraints" + "golang.org/x/exp/slices" +) + +type sorted_set[T constraints.Ordered] struct { sorted []T set Set[T] } -func NewSortedSet[T comparable]() *sorted_set[T] { +func NewSortedSet[T constraints.Ordered](t ...T) *sorted_set[T] { return &sorted_set[T]{ set: NewHashSet[T](), } } +func (s *sorted_set[T]) Add(values ...T) { + for _, v := range values { + if !s.set.Contains(v) { + s.sorted = append(s.sorted, v) + s.set.Add(v) + } + } +} + +func (s *sorted_set[T]) Remove(v T) { + if s.set.Contains(v) { + for index := range s.sorted { + if s.sorted[index] == v { + s.sorted = append(s.sorted[:index], s.sorted[index+1:]...) + break + } + } + + s.set.Remove(v) + } +} + +func (s *sorted_set[T]) Asc() Set[T] { + keys := s.sorted + slices.Sort(keys) + + return &sorted_set[T]{ + sorted: keys, + set: NewHashSet(keys...), + } +} + +func (s *sorted_set[T]) Desc() Set[T] { + keys := s.sorted + slices.SortFunc(keys, func(a, b T) bool { + return a > b + }) + + return &sorted_set[T]{ + sorted: keys, + set: NewHashSet(keys...), + } +} + +func (s *sorted_set[T]) Contains(v T) bool { + return s.set.Contains(v) +} + +func (s *sorted_set[T]) ContainsAny(values ...T) bool { + return s.set.ContainsAny(values...) +} + +func (s *sorted_set[T]) ContainsAll(values ...T) bool { + return s.set.ContainsAll(values...) +} + +func (s *sorted_set[T]) IsEmpty() bool { + return s.set.IsEmpty() +} + func (s *sorted_set[T]) ToSlice() []T { return s.sorted }