diff --git a/mapset/go.mod b/mapset/go.mod new file mode 100644 index 0000000..e0a758f --- /dev/null +++ b/mapset/go.mod @@ -0,0 +1,3 @@ +module git.blauwelle.com/go/crate/mapset + +go 1.20 diff --git a/mapset/mapset.go b/mapset/mapset.go new file mode 100644 index 0000000..8535146 --- /dev/null +++ b/mapset/mapset.go @@ -0,0 +1,294 @@ +package mapset + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" +) + +// New 返回 [MapSet] +func New[T comparable](keys ...T) MapSet[T] { + s := make(MapSet[T], len(keys)) + s.AddMany(keys...) + return s +} + +// MapSet 是集合的范型实现 +type MapSet[T comparable] map[T]struct{} + +// Cardinality 返回集合的元素个数 +func (s MapSet[T]) Cardinality() int { + return len(s) +} + +// AddOne 添加元素到集合 +func (s MapSet[T]) AddOne(key T) { + s[key] = struct{}{} +} + +// AddOneOk 添加元素到集合并返回是否添加成功 +func (s MapSet[T]) AddOneOk(key T) bool { + size := len(s) + s[key] = struct{}{} + return len(s) > size +} + +// AddMany 添加多个元素到集合 +func (s MapSet[T]) AddMany(keys ...T) { + for _, key := range keys { + s[key] = struct{}{} + } +} + +// AddManyOk 添加多个元素到集合并返回添加成功的个数 +func (s MapSet[T]) AddManyOk(keys ...T) int { + size := len(s) + for _, key := range keys { + s[key] = struct{}{} + } + return len(s) - size +} + +// DeleteOne 从集合中删除元素 +func (s MapSet[T]) DeleteOne(key T) { + delete(s, key) +} + +// DeleteOneOk 从集合中删除元素并返回是否删除成功 +func (s MapSet[T]) DeleteOneOk(key T) bool { + size := len(s) + delete(s, key) + return size > len(s) +} + +// DeleteMany 从集合删除多个元素 +func (s MapSet[T]) DeleteMany(keys ...T) { + for _, key := range keys { + delete(s, key) + } +} + +// DeleteManyOk 从集合删除多个元素并返回成功删除的个数 +func (s MapSet[T]) DeleteManyOk(keys ...T) int { + size := len(s) + for _, key := range keys { + delete(s, key) + } + return size - len(s) +} + +// PopOne 从集合中删除1个元素, 并把被删除的元素返回 +func (s MapSet[T]) PopOne() (key T, ok bool) { + for k := range s { + key, ok = k, true + return + } + return +} + +// Clear 清空集合 +func (s MapSet[T]) Clear() { + for key := range s { + delete(s, key) + } +} + +// Clone 返回集合的浅拷贝 +func (s MapSet[T]) Clone() MapSet[T] { + n := make(MapSet[T], len(s)) + for key := range s { + n[key] = struct{}{} + } + return n +} + +// IsEmpty 判断集合是否为空 +func (s MapSet[T]) IsEmpty() bool { + return len(s) == 0 +} + +// Equal 判断集合是否和另1个集合相等 +func (s MapSet[T]) Equal(o MapSet[T]) bool { + if len(s) != len(o) { + return false + } + for key := range s { + if _, exists := o[key]; !exists { + return false + } + } + return true +} + +// ContainsOne 判断集合是否包含1个元素 +func (s MapSet[T]) ContainsOne(key T) bool { + _, exists := s[key] + return exists +} + +// ContainsAll 判断集合是否包含全部元素 +// 参数为空时返回 true +func (s MapSet[T]) ContainsAll(keys ...T) bool { + for _, key := range keys { + if _, exists := s[key]; !exists { + return false + } + } + return true +} + +// ContainsAny 判断集合是否包含任意元素 +// 参数为空时返回 false +func (s MapSet[T]) ContainsAny(keys ...T) bool { + for _, key := range keys { + if _, exists := s[key]; exists { + return true + } + } + return false +} + +// IsSubsetOf 判断集合是否是另1个集合的子集 +func (s MapSet[T]) IsSubsetOf(o MapSet[T]) bool { + if len(s) > len(o) { + return false + } + for key := range s { + if _, exists := o[key]; !exists { + return false + } + } + return true +} + +// IsProperSubsetOf 判断集合是否是另1个集合的真子集 +func (s MapSet[T]) IsProperSubsetOf(o MapSet[T]) bool { + if len(s) >= len(o) { + return false + } + for key := range s { + if _, exists := o[key]; !exists { + return false + } + } + return true +} + +// Union 返回集合和另1个集合的并集 +func (s MapSet[T]) Union(o MapSet[T]) MapSet[T] { + r := make(MapSet[T], maxInt(len(s), len(o))) + for key := range s { + r[key] = struct{}{} + } + for key := range o { + r[key] = struct{}{} + } + return r +} + +// Intersection 返回集合和另1个集合的交集 +func (s MapSet[T]) Intersection(o MapSet[T]) MapSet[T] { + a, b := s, o + if len(a) > len(b) { + a, b = b, a + } + r := make(MapSet[T], len(a)) + for key := range a { + if _, exists := b[key]; exists { + r[key] = struct{}{} + } + } + return r +} + +// Difference 返回集合和另1个集合的差集 +func (s MapSet[T]) Difference(o MapSet[T]) MapSet[T] { + r := make(MapSet[T]) + for key := range s { + if _, exists := o[key]; !exists { + r[key] = struct{}{} + } + } + return r +} + +// SymmetricDifference 返回集合和另1个集合相互差集的并集 +func (s MapSet[T]) SymmetricDifference(o MapSet[T]) MapSet[T] { + r := make(MapSet[T]) + for key := range s { + if _, exists := o[key]; !exists { + r[key] = struct{}{} + } + } + for key := range o { + if _, exists := s[key]; !exists { + r[key] = struct{}{} + } + } + return r +} + +// ToSlice 把集合转换成切片 +func (s MapSet[T]) ToSlice() []T { + keys := make([]T, 0, len(s)) + for key := range s { + keys = append(keys, key) + } + return keys +} + +// ToAnySlice 把集合转换成 []any +func (s MapSet[T]) ToAnySlice() []any { + keys := make([]any, 0, len(s)) + for key := range s { + keys = append(keys, key) + } + return keys +} + +// MarshalJSON 实现 [encoding/json.Marshaler] +func (s MapSet[T]) MarshalJSON() ([]byte, error) { + return json.Marshal(s.ToSlice()) +} + +// UnmarshalJSON 实现 [encoding/json.Unmarshaler] +func (s *MapSet[T]) UnmarshalJSON(b []byte) error { + var keys []T + if *s == nil { + *s = New[T]() + } + if err := json.Unmarshal(b, &keys); err != nil { + return err + } + s.AddMany(keys...) + return nil +} + +func (s MapSet[T]) String() string { + size := s.Cardinality() + if size > 64 { + size = 64 + } + keys := make([]string, 0, size) + for key := range s { + switch any(key).(type) { + case string: + keys = append(keys, fmt.Sprintf("%q", any(key))) + default: + keys = append(keys, fmt.Sprintf("%v", any(key))) + } + size-- + if size == 0 { + break + } + } + return "MapSet(" + strconv.Itoa(s.Cardinality()) + "){" + strings.Join(keys, ",") + "}" +} + +func maxInt(a, b int) int { + if a > b { + return a + } + return b +} diff --git a/mapset/mapset_test.go b/mapset/mapset_test.go new file mode 100644 index 0000000..c796ed9 --- /dev/null +++ b/mapset/mapset_test.go @@ -0,0 +1,523 @@ +package mapset_test + +import ( + "encoding/json" + "sort" + "testing" + + "git.blauwelle.com/go/crate/mapset" +) + +func TestNewEmpty(t *testing.T) { + s := mapset.New[int]() + if size := s.Cardinality(); size != 0 { + t.Fatalf("got %d, want cardinality 0", size) + } +} + +func TestNew(t *testing.T) { + s := mapset.New(1, 2, 3, 4, 5) + if size := s.Cardinality(); size != 5 { + t.Fatalf("got %d, want cardinality 5", size) + } + keys := make([]int, 0, 10) + for i := 1; i < 6; i++ { + keys = append(keys, i) + if ok := s.ContainsOne(i); !ok { + t.Fatalf("got false, want contains %d", i) + } + } + if ok := s.ContainsAll(keys...); !ok { + t.Fatalf("want contains all of %v", keys) + } + for i := 6; i < 11; i++ { + keys = append(keys, i) + if ok := s.ContainsOne(i); ok { + t.Fatalf("want do not contain %d", i) + } + } + if ok := s.ContainsAll(keys...); ok { + t.Fatalf("want do not contain all of %v", keys) + } + if ok := s.ContainsAny(keys...); !ok { + t.Fatalf("want contains any of %v", keys) + } +} + +func TestAddOne(t *testing.T) { + s := mapset.New[int]() + s.AddOne(1) + if size := s.Cardinality(); size != 1 { + t.Fatalf("got %d, want cardinality 1", size) + } + if ok := s.ContainsOne(1); !ok { + t.Fatalf("want contains 1") + } +} + +func TestAddOneOk(t *testing.T) { + t.Run("ok", func(t *testing.T) { + s := mapset.New[int]() + ok := s.AddOneOk(1) + if size := s.Cardinality(); size != 1 { + t.Fatalf("got %d, want cardinality 1", size) + } + if !ok { + t.Fatalf("fail") + } + if ok := s.ContainsOne(1); !ok { + t.Fatalf("want contains 1") + } + }) + t.Run("exists", func(t *testing.T) { + s := mapset.New[int](1) + ok := s.AddOneOk(1) + if size := s.Cardinality(); size != 1 { + t.Fatalf("got %d, want cardinality 1", size) + } + if ok { + t.Fatalf("fail") + } + if ok := s.ContainsOne(1); !ok { + t.Fatalf("want contains 1") + } + }) +} + +func TestAddManyOk(t *testing.T) { + t.Run("ok", func(t *testing.T) { + s := mapset.New[int]() + n := s.AddManyOk(1, 2, 3) + if n != 3 { + t.Fatalf("fail") + } + }) + t.Run("exists", func(t *testing.T) { + s := mapset.New(1, 2) + n := s.AddManyOk(1, 2, 3, 4) + if n != 2 { + t.Fatalf("fail") + } + }) +} + +func TestDeleteOneOk(t *testing.T) { + t.Run("ok", func(t *testing.T) { + s := mapset.New(1, 2, 3) + ok := s.DeleteOneOk(1) + if !ok { + t.Fatalf("fail") + } + }) + t.Run("not_exists", func(t *testing.T) { + s := mapset.New(1, 2, 3) + ok := s.DeleteOneOk(4) + if ok { + t.Fatalf("faila") + } + }) +} + +func TestDeleteManyOk(t *testing.T) { + t.Run("ok", func(t *testing.T) { + s := mapset.New(1, 2, 3) + n := s.DeleteManyOk(1, 2, 3) + if n != 3 { + t.Fatalf("fail") + } + }) + t.Run("delete_more", func(t *testing.T) { + s := mapset.New(1, 2, 3) + n := s.DeleteManyOk(1, 2, 3, 4) + if n != 3 { + t.Fatalf("fail") + } + }) + t.Run("delete_partial", func(t *testing.T) { + s := mapset.New(1, 2, 3) + n := s.DeleteManyOk(1, 2, 4) + if n != 2 { + t.Fatalf("fail") + } + }) +} + +func TestDelete(t *testing.T) { + s := mapset.New(1) + s.DeleteOne(1) + if size := s.Cardinality(); size != 0 { + t.Fatalf("got %d, want cardinality 0", size) + } +} + +func TestDeleteMany(t *testing.T) { + s := mapset.New(1, 2, 3) + s.DeleteMany(1, 3) + if size := s.Cardinality(); size != 1 { + t.Fatalf("got %d, want cardinality 1", size) + } +} + +func TestContainsAny(t *testing.T) { + s := mapset.New(1, 2, 3) + if ok := s.ContainsAny(); ok { + t.Fatalf("got true, should not contains empty") + } + if ok := s.ContainsAny(-1, -2); ok { + t.Fatalf("got true, should not contains any of -1, -2") + } +} + +func TestPopOne(t *testing.T) { + s := mapset.New[int]() + if _, ok := s.PopOne(); ok { + t.Fatalf("should not pop any key") + } + s.AddMany(1, 2, 3) + if key, ok := s.PopOne(); !ok { + t.Fatalf("should pop 1 key") + } else if key < 1 || key > 3 { + t.Fatalf("wrong key") + } +} + +func TestClear(t *testing.T) { + s := mapset.New(1, 2, 3) + s.Clear() + if size := s.Cardinality(); size != 0 { + t.Fatalf("got %d, want 0 cardinality after clean", size) + } +} + +func TestClone(t *testing.T) { + s := mapset.New(1, 2) + c := s.Clone() + if ok := s.Equal(c); !ok { + t.Fatalf("got false, want equal") + } + s.AddOne(3) + if size := c.Cardinality(); size != 2 { + t.Fatalf("got %d, want 2", size) + } + if ok := s.Equal(c); ok { + t.Fatalf("got true, want not equal") + } +} + +func TestIsEmpty(t *testing.T) { + t.Run("empty", func(t *testing.T) { + s := mapset.New[int]() + if ok := s.IsEmpty(); !ok { + t.Fatalf("got false, want empty") + } + }) + t.Run("not_empty", func(t *testing.T) { + s := mapset.New(1) + if ok := s.IsEmpty(); ok { + t.Fatalf("got true, want not empty") + } + }) +} + +func TestEqual(t *testing.T) { + s := mapset.New(1, 2) + c := mapset.New(1, 2) + v := mapset.New(1, 2, 3) + p := mapset.New(2, 3) + t.Run("equal", func(t *testing.T) { + if ok := s.Equal(c); !ok { + t.Fatalf("got false, want equal") + } + }) + t.Run("not_equal_1", func(t *testing.T) { + if ok := s.Equal(v); ok { + t.Fatalf("got true, want not equal") + } + }) + t.Run("not_equal_2", func(t *testing.T) { + if ok := s.Equal(p); ok { + t.Fatalf("got true, want not equal") + } + }) +} + +func TestIsSubsetOf(t *testing.T) { + s := mapset.New(1, 2) + c := mapset.New(1, 2, 3) + v := mapset.New(2, 3) + e := mapset.New[int]() + t.Run("is_subset_of_self", func(t *testing.T) { + if ok := s.IsSubsetOf(s); !ok { + t.Fatalf("fail") + } + }) + t.Run("is_subset", func(t *testing.T) { + if ok := s.IsSubsetOf(c); !ok { + t.Fatalf("fail") + } + }) + t.Run("not_subset_1", func(t *testing.T) { + if ok := s.IsSubsetOf(v); ok { + t.Fatalf("fail") + } + }) + t.Run("not_subset_2", func(t *testing.T) { + if ok := c.IsSubsetOf(s); ok { + t.Fatalf("fail") + } + }) + t.Run("empty_is_subset_of_empty", func(t *testing.T) { + if ok := e.IsSubsetOf(e); !ok { + t.Fatalf("fail") + } + }) + t.Run("empty_is_subset_of_other", func(t *testing.T) { + if ok := e.IsSubsetOf(s); !ok { + t.Fatalf("fail") + } + }) +} + +func TestIsProperSubsetOf(t *testing.T) { + s := mapset.New(1, 2) + c := mapset.New(1, 2, 3) + v := mapset.New(2, 3) + p := mapset.New(1, 3, 5, 7) + t.Run("not_proper_subset_of_self", func(t *testing.T) { + if ok := s.IsProperSubsetOf(s); ok { + t.Fatalf("fail") + } + }) + t.Run("is_proper_subset", func(t *testing.T) { + if ok := s.IsProperSubsetOf(c); !ok { + t.Fatalf("fail") + } + }) + t.Run("not_proper_subset_1", func(t *testing.T) { + if ok := s.IsProperSubsetOf(v); ok { + t.Fatalf("fail") + } + }) + t.Run("not_proper_subset_2", func(t *testing.T) { + if ok := c.IsProperSubsetOf(s); ok { + t.Fatalf("fail") + } + }) + t.Run("not_proper_subset_3", func(t *testing.T) { + if ok := s.IsProperSubsetOf(p); ok { + t.Fatalf("fail") + } + }) +} + +func TestUnion(t *testing.T) { + t.Run("union_1", func(t *testing.T) { + s := mapset.New(1, 2) + c := mapset.New(2, 3) + u := mapset.New(1, 2, 3) + if g := s.Union(c); !g.Equal(u) { + t.Fatalf("fail") + } + }) + t.Run("union_2", func(t *testing.T) { + s := mapset.New(1, 2, 5, 7) + c := mapset.New(2, 3) + u := mapset.New(1, 2, 3, 5, 7) + if g := s.Union(c); !g.Equal(u) { + t.Fatalf("fail") + } + }) +} + +func TestIntersection(t *testing.T) { + t.Run("intersection_1", func(t *testing.T) { + s := mapset.New(1, 2) + c := mapset.New(2, 3) + u := mapset.New(2) + if g := s.Intersection(c); !g.Equal(u) { + t.Fatalf("fail") + } + }) + t.Run("intersection_2", func(t *testing.T) { + s := mapset.New(1, 2, 4) + c := mapset.New(2, 3) + u := mapset.New(2) + if g := s.Intersection(c); !g.Equal(u) { + t.Fatalf("fail") + } + }) +} + +func TestDifference(t *testing.T) { + s := mapset.New(1, 2, 3) + c := mapset.New(1, 2) + t.Run("diff_1", func(t *testing.T) { + o := s.Difference(c) + if !o.Equal(mapset.New(3)) { + t.Fatalf("fail") + } + }) +} + +func TestSymmetricDifference(t *testing.T) { + s := mapset.New(1, 2, 3) + c := mapset.New(1, 2, 4) + t.Run("diff_1", func(t *testing.T) { + o := s.SymmetricDifference(c) + if !o.Equal(mapset.New(3, 4)) { + t.Fatalf("fail") + } + }) +} + +func TestToSlice(t *testing.T) { + s := mapset.New(1, 2, 3) + r := s.ToSlice() + sort.Ints(r) + if size := len(r); size != 3 { + t.Fatalf("got %d, want 3", size) + } + if !([3]int(r) == [3]int{1, 2, 3}) { + t.Fatalf("fail") + } +} + +func TestToAnySlice(t *testing.T) { + s := mapset.New(1, 2, 3) + r := s.ToAnySlice() + if size := len(r); size != 3 { + t.Fatalf("got %d, want 3", size) + } + sort.Slice(r, func(i, j int) bool { + return r[i].(int) < r[j].(int) + }) + if !([3]any(r) == [3]any{1, 2, 3}) { + t.Fatalf("fail") + } +} + +func TestString(t *testing.T) { + t.Run("int", func(t *testing.T) { + s := mapset.New(1, 2, 3) + str := s.String() + t.Log(str) + }) + t.Run("int_large", func(t *testing.T) { + s := mapset.New[int]() + for i := 0; i < 80; i++ { + s.AddOne(i) + } + str := s.String() + t.Log(str) + }) + t.Run("string", func(t *testing.T) { + s := mapset.New("a", "aa,bb", `"cc"`) + t.Log(s.String()) + }) +} + +func TestMarshalUnmarshal(t *testing.T) { + t.Run("marshal", func(t *testing.T) { + s := mapset.New(1) + b, err := json.Marshal(s) + if err != nil { + t.Fatalf("err: %v", err) + } + if string(b) != "[1]" { + t.Fatalf("fail") + } + }) + t.Run("unmarshal", func(t *testing.T) { + b := []byte(`[1, 2]`) + var s mapset.MapSet[int] + if err := json.Unmarshal(b, &s); err != nil { + t.Fatalf("err: %v", err) + } + if !s.Equal(mapset.New(1, 2)) { + t.Fatalf("failed") + } + }) + t.Run("unmarshal_error", func(t *testing.T) { + b := []byte(`1`) + var s mapset.MapSet[int] + if err := json.Unmarshal(b, &s); err == nil { + t.Fatalf("fail") + } + }) +} + +func BenchmarkAdd(b *testing.B) { + size := 1024 * 1 + s := mapset.New[int]() + keys := make([]int, size) + for i := 0; i < size; i++ { + s.AddOne(i) + keys[i] = i + } + b.Run("addOne", func(b *testing.B) { + for i := 0; i < b.N; i++ { + for i := 0; i < size; i++ { + s.AddOne(i) + } + } + }) + b.Run("addMany", func(b *testing.B) { + for i := 0; i < b.N; i++ { + for i := 0; i < size; i++ { + keys[i] = i + } + s.AddMany(keys...) + } + }) + b.Run("addManyPrepared", func(b *testing.B) { + for i := 0; i < b.N; i++ { + s.AddMany(keys...) + } + }) +} + +func BenchmarkContains(b *testing.B) { + s := mapset.New[int]() + for i := 0; i < 1024; i++ { + s.AddOne(i) + } + b.Run("containsAll_keyExists", func(b *testing.B) { + for i := 0; i < b.N; i++ { + s.ContainsAll(1) + } + }) + b.Run("containsAll_keyNotExists", func(b *testing.B) { + for i := 0; i < b.N; i++ { + s.ContainsAll(-1) + } + }) + b.Run("containsOne_keyExists", func(b *testing.B) { + for i := 0; i < b.N; i++ { + s.ContainsOne(1) + } + }) + b.Run("containsOne_keyNotExists", func(b *testing.B) { + for i := 0; i < b.N; i++ { + s.ContainsOne(-1) + } + }) +} + +func BenchmarkRaw(b *testing.B) { + b.Run("raw-64", func(b *testing.B) { + for i := 0; i < b.N; i++ { + m := map[int]struct{}{} + for j := 0; j < 64; j++ { + m[j] = struct{}{} + } + } + }) + b.Run("mapset-64", func(b *testing.B) { + for i := 0; i < b.N; i++ { + s := mapset.New[int]() + for j := 0; j < 64; j++ { + s.AddOne(j) + } + } + }) +}