From f93c480f2f23d991cea242be8d339d72d3b893f5 Mon Sep 17 00:00:00 2001 From: Edward Muller Date: Thu, 27 Mar 2025 21:23:36 -0700 Subject: [PATCH 1/3] Expose concrete types * Renames * func Map to func MapBy * func New to func NewMap * func NewFrom to func NewMapFrom * func NewWith to func NewMapWith * Set types are now exported, so that ... * Concrete types can be used in structs and json.Unmarshaled to. * sets.Map (basic set type) * sets.Locked (basic locked set type) * sets.Ordered (basic ordred set type) * sets.LockedOrdered (basic locked+ordered set type) * sets.SyncMap (sync.Map based set type) * Added more JSON tests, especially for above #minor --- CHANGELOG.MD | 16 ++- README.MD | 3 +- examples_test.go | 92 +++++++-------- locked.go | 80 ++++++------- locked_ordered.go | 82 +++++++------- locker.go | 10 ++ map.go | 53 +++++---- ordered.go | 54 +++++---- set.go | 6 +- set_test.go | 280 ++++++++++++++++++++++++++++++++++++++++++++-- sync.go | 50 ++++----- 11 files changed, 508 insertions(+), 218 deletions(-) create mode 100644 locker.go diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 8b2c327..b777773 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,6 +1,20 @@ # Changelog -## Unreleased +## v0.9.0 + +* Renames + * func Map to func MapBy + * func New to func NewMap + * func NewFrom to func NewMapFrom + * func NewWith to func NewMapWith +* Set types are now exported, so that ... +* Concrete types can be used in structs and json.Unmarshaled to. + * sets.Map (basic set type) + * sets.Locked (basic locked set type) + * sets.Ordered (basic ordred set type) + * sets.LockedOrdered (basic locked+ordered set type) + * sets.SyncMap (sync.Map based set type) +* Added more JSON tests, especially for above ## v0.8.0 diff --git a/README.MD b/README.MD index 4893c36..802e976 100644 --- a/README.MD +++ b/README.MD @@ -43,7 +43,6 @@ go get github.com/freeformz/sets ## JSON Sets marshal to/from JSON as JSON arrays. -They must be initialized before doing so as the zero value of an interface is nil. A JSON array with repeated values unmarshaled to a Set will not preserve duplicates. An empty Set marshals to `[]`. OrderedSets preserve order when {un,}marshaling, while Sets do not. @@ -71,7 +70,7 @@ These helpers work on all Set types, including OrderedSets. * `sets.Min(aSet)` : Returns the min element in the set as determined by the min builtin. * `sets.Chunk(aSet,n)` : Chunks the set into n sets of equal size. The last set will have fewer elements if the cardinality of the set is not a multiple of n. * `sets.IsEmpty(aSet)` : Returns true if the set is empty, otherwise false. -* `sets.Map(aSet, func(v V) X { return ... }) bSet` : Maps the elements of the set to a new set. +* `sets.MapBy(aSet, func(v V) X { return ... }) bSet` : Maps the elements of the set to a new set. * `sets.MapTo(aSet, bSet, func(v V) X { return ... })` : Maps the elements of aSet into bSet. * `sets.MapToSlice(aSet, func(v V) X { return ... }) aSlice` : Maps the elements of the set to a new slice. * `sets.Filter(aSet, func(v V) bool { return true/false }) bSet` : Filters the elements of the set and returns a new set. diff --git a/examples_test.go b/examples_test.go index dd5c022..68c12a9 100644 --- a/examples_test.go +++ b/examples_test.go @@ -9,7 +9,7 @@ import ( ) func ExampleSet() { - ints := New[int]() + ints := NewMap[int]() ints.Add(5) ints.Add(1) ints.Add(9) @@ -150,7 +150,7 @@ func ExampleOrderedSet() { } func ExampleElements() { - ints := NewWith(5, 3, 2) + ints := NewMapWith(5, 3, 2) // []T is returned elements := Elements(ints) @@ -164,7 +164,7 @@ func ExampleElements() { } func ExampleAppendSeq() { - ints := NewWith(5, 3) + ints := NewMapWith(5, 3) // adds 2,4,1 to the set since 5 and 3 already exist added := AppendSeq(ints, slices.Values([]int{5, 3, 2, 4, 1})) @@ -173,7 +173,7 @@ func ExampleAppendSeq() { } func ExampleRemoveSeq() { - ints := NewWith(5, 3, 2) + ints := NewMapWith(5, 3, 2) // removes 2 from the set since 5 and 3 exist removed := RemoveSeq(ints, slices.Values([]int{2, 4, 1})) @@ -182,8 +182,8 @@ func ExampleRemoveSeq() { } func ExampleUnion() { - a := NewWith(5, 3) - b := NewWith(3, 2) + a := NewMapWith(5, 3) + b := NewMapWith(3, 2) c := Union(a, b) out := make([]int, 0, c.Cardinality()) @@ -201,8 +201,8 @@ func ExampleUnion() { } func ExampleIntersection() { - a := NewWith(5, 3) - b := NewWith(3, 2) + a := NewMapWith(5, 3) + b := NewMapWith(3, 2) c := Intersection(a, b) out := make([]int, 0, c.Cardinality()) @@ -217,8 +217,8 @@ func ExampleIntersection() { } func ExampleDifference() { - a := NewWith(5, 3) - b := NewWith(3, 2) + a := NewMapWith(5, 3) + b := NewMapWith(3, 2) c := Difference(a, b) out := make([]int, 0, c.Cardinality()) @@ -233,8 +233,8 @@ func ExampleDifference() { } func ExampleSymmetricDifference() { - a := NewWith(5, 3) - b := NewWith(3, 2) + a := NewMapWith(5, 3) + b := NewMapWith(3, 2) c := SymmetricDifference(a, b) for i := range c.Iterator { @@ -246,8 +246,8 @@ func ExampleSymmetricDifference() { } func ExampleSubset() { - a := NewWith(5, 3) - b := NewWith(5, 3, 2) + a := NewMapWith(5, 3) + b := NewMapWith(5, 3, 2) if Subset(a, b) { fmt.Println("a is a subset of b") @@ -262,8 +262,8 @@ func ExampleSubset() { } func ExampleSuperset() { - a := NewWith(5, 3) - b := NewWith(5, 3, 2) + a := NewMapWith(5, 3) + b := NewMapWith(5, 3, 2) if !Superset(a, b) { fmt.Println("a is not a superset of b") @@ -278,8 +278,8 @@ func ExampleSuperset() { } func ExampleEqual() { - a := NewWith(5, 3) - b := NewWith(5, 3) + a := NewMapWith(5, 3) + b := NewMapWith(5, 3) if Equal(a, b) { fmt.Println("a and b are equal") @@ -303,7 +303,7 @@ func ExampleEqual() { } func ExampleContainsSeq() { - ints := New[int]() + ints := NewMap[int]() if ContainsSeq(ints, slices.Values([]int{})) { fmt.Println("Empty set contains empty sequence") } @@ -326,8 +326,8 @@ func ExampleContainsSeq() { } func ExampleDisjoint() { - a := NewWith(5, 3) - b := NewWith(2, 4) + a := NewMapWith(5, 3) + b := NewMapWith(2, 4) if Disjoint(a, b) { fmt.Println("a and b are disjoint") @@ -368,7 +368,7 @@ func ExampleEqualOrdered() { } func ExampleMin() { - ints := NewWith(3, 2, 5) + ints := NewMapWith(3, 2, 5) min := Min(ints) fmt.Println(min) @@ -376,7 +376,7 @@ func ExampleMin() { } func ExampleMax() { - ints := NewWith(3, 5, 2) + ints := NewMapWith(3, 5, 2) max := Max(ints) fmt.Println(max) @@ -492,8 +492,8 @@ func Example_json() { // OrderedSet[float32]([1 1.2 1.3 1.4 1.5]) } -func ExampleNewWith() { - set := NewWith("a", "b", "c", "b") +func ExampleNewMapWith() { + set := NewMapWith("a", "b", "c", "b") fmt.Println(set.Cardinality()) // Output: 3 @@ -536,15 +536,15 @@ func ExampleNewLockedOrderedWith() { // c } -func ExampleNewSyncWith() { - set := NewSyncWith("a", "b", "c", "b") +func ExampleNewSyncMapWith() { + set := NewSyncMapWith("a", "b", "c", "b") fmt.Println(set.Cardinality()) // Output: 3 } -func ExampleNew() { - set := New[string]() +func ExampleNewMap() { + set := NewMap[string]() set.Add("a") set.Add("b") set.Add("c") @@ -603,8 +603,8 @@ func ExampleNewLockedOrdered() { // c } -func ExampleNewSync() { - set := NewSync[string]() +func ExampleNewSyncMap() { + set := NewSyncMap[string]() set.Add("a") set.Add("b") set.Add("c") @@ -614,9 +614,9 @@ func ExampleNewSync() { // Output: 3 } -func ExampleNewFrom() { +func ExampleNewMapFrom() { m := []string{"a", "b", "c", "b"} - set := NewFrom(slices.Values(m)) + set := NewMapFrom(slices.Values(m)) fmt.Println(set.Cardinality()) // Output: 3 @@ -662,16 +662,16 @@ func ExampleNewLockedOrderedFrom() { // c } -func ExampleNewSyncFrom() { +func ExampleNewSyncMapFrom() { m := []string{"a", "b", "c", "b"} - set := NewSyncFrom(slices.Values(m)) + set := NewSyncMapFrom(slices.Values(m)) fmt.Println(set.Cardinality()) // Output: 3 } func ExampleNewLockedWrapping() { - set := NewWith("a", "b", "c", "b") + set := NewMapWith("a", "b", "c", "b") wrapped := NewLockedWrapping(set) // wrapped is safe for concurrent use @@ -691,7 +691,7 @@ func ExampleNewLockedOrderedWrapping() { } func ExampleIsEmpty() { - set := New[int]() + set := NewMap[int]() if IsEmpty(set) { fmt.Println("set is empty") } @@ -705,17 +705,17 @@ func ExampleIsEmpty() { // set is not empty } -func ExampleMap() { - set := NewWith(1, 2, 3) +func ExampleMapBy() { + set := NewMapWith(1, 2, 3) - mapped := Map(set, func(i int) int { + mapped := MapBy(set, func(i int) int { return i * 2 }) for i := range mapped.Iterator { fmt.Println(i) } - mapped2 := Map(set, func(i int) string { + mapped2 := MapBy(set, func(i int) string { return fmt.Sprintf("%d", i) }) for i := range mapped2.Iterator { @@ -733,7 +733,7 @@ func ExampleMap() { func ExampleMapTo() { set := NewOrderedWith(3, 1, 2) - dest := New[string]() + dest := NewMap[string]() MapTo(set, dest, func(i int) string { return fmt.Sprintf("%d=%d*2", i*2, i) }) @@ -747,7 +747,7 @@ func ExampleMapTo() { } func ExampleMapToSlice() { - set := NewWith(3, 1, 2) + set := NewMapWith(3, 1, 2) mapped := MapToSlice(set, func(i int) string { return fmt.Sprintf("%d=%d*2", i*2, i) @@ -762,7 +762,7 @@ func ExampleMapToSlice() { } func ExampleFilter() { - set := NewWith(3, 0, 1, 2, 4) + set := NewMapWith(3, 0, 1, 2, 4) filtered := Filter(set, func(i int) bool { return i > 2 @@ -776,7 +776,7 @@ func ExampleFilter() { } func ExampleReduce() { - set := NewWith(3, 1, 2) + set := NewMapWith(3, 1, 2) sum := Reduce(set, 0, func(agg, v int) int { return agg + v @@ -801,7 +801,7 @@ func ExampleReduceRight() { } func ExampleForEach() { - set := NewWith(3, 1, 2) + set := NewMapWith(3, 1, 2) ForEach(set, func(i int) { fmt.Println(i) diff --git a/locked.go b/locked.go index 2a6cb02..c0394db 100644 --- a/locked.go +++ b/locked.go @@ -8,27 +8,24 @@ import ( "sync" ) -type lockedMap[M comparable] struct { +type Locked[M comparable] struct { set Set[M] - *sync.RWMutex + sync.RWMutex *sync.Cond iterating bool } -var _ Set[int] = new(lockedMap[int]) +var _ Set[int] = new(Locked[int]) // NewLocked returns an empty Set[M] that is safe for concurrent use. -func NewLocked[M comparable]() Set[M] { - mu := &sync.RWMutex{} - return &lockedMap[M]{ - set: New[M](), - RWMutex: mu, - Cond: sync.NewCond(mu), - } +func NewLocked[M comparable]() *Locked[M] { + l := &Locked[M]{set: NewMap[M]()} + l.Cond = sync.NewCond(&l.RWMutex) + return l } // NewLockedFrom returns a new Set[M] filled with the values from the sequence. -func NewLockedFrom[M comparable](seq iter.Seq[M]) Set[M] { +func NewLockedFrom[M comparable](seq iter.Seq[M]) *Locked[M] { s := NewLocked[M]() for x := range seq { s.Add(x) @@ -37,40 +34,30 @@ func NewLockedFrom[M comparable](seq iter.Seq[M]) Set[M] { } // NewLockedWith the values provides. Duplicates are removed. -func NewLockedWith[M comparable](m ...M) Set[M] { +func NewLockedWith[M comparable](m ...M) *Locked[M] { return NewLockedFrom(slices.Values(m)) } -type locker interface { - Lock() - Unlock() - RLock() - RUnlock() - Wait() - Broadcast() -} - // NewLockedWrapping returns a Set[M]. If set is already a locked set, then it is just returned as is. If set isn't a locked set // then the returned set is wrapped so that it is safe for concurrent use. func NewLockedWrapping[M comparable](set Set[M]) Set[M] { - if _, lok := set.(locker); lok { - return set - } - mu := &sync.RWMutex{} - return &lockedMap[M]{ - set: set, - RWMutex: mu, - Cond: sync.NewCond(mu), + if lset, ok := set.(*Locked[M]); ok { + return lset } + + lset := NewLocked[M]() + lset.set = set + + return lset } -func (s *lockedMap[M]) Contains(m M) bool { +func (s *Locked[M]) Contains(m M) bool { s.RWMutex.RLock() defer s.RWMutex.RUnlock() return s.set.Contains(m) } -func (s *lockedMap[M]) Clear() int { +func (s *Locked[M]) Clear() int { s.Cond.L.Lock() if s.iterating { s.Cond.Wait() @@ -79,7 +66,7 @@ func (s *lockedMap[M]) Clear() int { return s.set.Clear() } -func (s *lockedMap[M]) Add(m M) bool { +func (s *Locked[M]) Add(m M) bool { s.Cond.L.Lock() if s.iterating { s.Cond.Wait() @@ -89,7 +76,7 @@ func (s *lockedMap[M]) Add(m M) bool { return s.set.Add(m) } -func (s *lockedMap[M]) Remove(m M) bool { +func (s *Locked[M]) Remove(m M) bool { s.Cond.L.Lock() if s.iterating { s.Cond.Wait() @@ -99,7 +86,7 @@ func (s *lockedMap[M]) Remove(m M) bool { return s.set.Remove(m) } -func (s *lockedMap[M]) Cardinality() int { +func (s *Locked[M]) Cardinality() int { if s == nil { return 0 } @@ -111,7 +98,7 @@ func (s *lockedMap[M]) Cardinality() int { // Iterator yields all elements in the set. It holds a lock for the duration of iteration. Calling methods other than // `Contains` and `Cardinality` will block until the iteration is complete. -func (s *lockedMap[M]) Iterator(yield func(M) bool) { +func (s *Locked[M]) Iterator(yield func(M) bool) { s.Cond.L.Lock() s.iterating = true defer func() { @@ -123,15 +110,15 @@ func (s *lockedMap[M]) Iterator(yield func(M) bool) { s.set.Iterator(yield) } -func (s *lockedMap[M]) Clone() Set[M] { +func (s *Locked[M]) Clone() Set[M] { return NewLockedFrom(s.Iterator) } -func (s *lockedMap[M]) NewEmpty() Set[M] { +func (s *Locked[M]) NewEmpty() Set[M] { return NewLocked[M]() } -func (s *lockedMap[M]) Pop() (M, bool) { +func (s *Locked[M]) Pop() (M, bool) { s.L.Lock() if s.iterating { s.Wait() @@ -141,13 +128,13 @@ func (s *lockedMap[M]) Pop() (M, bool) { return s.set.Pop() } -func (s *lockedMap[M]) String() string { +func (s *Locked[M]) String() string { s.RLock() defer s.RUnlock() return "Locked" + s.set.String() } -func (s *lockedMap[M]) MarshalJSON() ([]byte, error) { +func (s *Locked[M]) MarshalJSON() ([]byte, error) { s.RLock() defer s.RUnlock() @@ -163,13 +150,20 @@ func (s *lockedMap[M]) MarshalJSON() ([]byte, error) { return d, nil } -func (s *lockedMap[M]) UnmarshalJSON(d []byte) error { - s.L.Lock() +// UnmarshalJSON implements json.Unmarshaler. It will unmarshal the JSON data into the set. +func (s *Locked[M]) UnmarshalJSON(d []byte) error { + s.Lock() + if s.Cond == nil { + s.Cond = sync.NewCond(&s.RWMutex) + } if s.iterating { s.Wait() } - defer s.L.Unlock() + defer s.Unlock() + if s.set == nil { + s.set = NewMap[M]() + } um, ok := s.set.(json.Unmarshaler) if !ok { return fmt.Errorf("cannot unmarshal set of type %T - not json.Unmarshaler", s.set) diff --git a/locked_ordered.go b/locked_ordered.go index b67a69a..d3fe57d 100644 --- a/locked_ordered.go +++ b/locked_ordered.go @@ -9,28 +9,25 @@ import ( "sync" ) -type lockedOrdered[M cmp.Ordered] struct { +type LockedOrdered[M cmp.Ordered] struct { set OrderedSet[M] - *sync.RWMutex + sync.RWMutex *sync.Cond iterating bool } -var _ Set[int] = new(lockedOrdered[int]) +var _ Set[int] = new(LockedOrdered[int]) // NewLockedOrdered returns an empty OrderedSet[M] instance that is safe for concurrent use. -func NewLockedOrdered[M cmp.Ordered]() OrderedSet[M] { - mu := &sync.RWMutex{} - return &lockedOrdered[M]{ - set: NewOrdered[M](), - RWMutex: mu, - Cond: sync.NewCond(mu), - } +func NewLockedOrdered[M cmp.Ordered]() *LockedOrdered[M] { + set := &LockedOrdered[M]{set: NewOrdered[M]()} + set.Cond = sync.NewCond(&set.RWMutex) + return set } // NewLockedOrderedFrom returns a new OrderedSet[M] instance filled with the values from the sequence. The set is safe // for concurrent use. -func NewLockedOrderedFrom[M cmp.Ordered](seq iter.Seq[M]) OrderedSet[M] { +func NewLockedOrderedFrom[M cmp.Ordered](seq iter.Seq[M]) *LockedOrdered[M] { s := NewLockedOrdered[M]() for x := range seq { s.Add(x) @@ -39,31 +36,28 @@ func NewLockedOrderedFrom[M cmp.Ordered](seq iter.Seq[M]) OrderedSet[M] { } // NewOrderedWith the values provides. Duplicates are removed. -func NewLockedOrderedWith[M cmp.Ordered](m ...M) OrderedSet[M] { +func NewLockedOrderedWith[M cmp.Ordered](m ...M) *LockedOrdered[M] { return NewLockedOrderedFrom(slices.Values(m)) } // NewLockedOrderedWrapping returns an OrderedSet[M]. If set is already a locked set, then it is just returned as is. If set isn't a locked set // then the returned set is wrapped so that it is safe for concurrent use. func NewLockedOrderedWrapping[M cmp.Ordered](set OrderedSet[M]) OrderedSet[M] { - if _, lok := set.(locker); lok { - return set - } - mu := &sync.RWMutex{} - return &lockedOrdered[M]{ - set: set, - RWMutex: mu, - Cond: sync.NewCond(mu), + if lset, ok := set.(*LockedOrdered[M]); ok { + return lset } + lset := NewLockedOrdered[M]() + lset.set = set + return lset } -func (s *lockedOrdered[M]) Contains(m M) bool { +func (s *LockedOrdered[M]) Contains(m M) bool { s.RLock() defer s.RUnlock() return s.set.Contains(m) } -func (s *lockedOrdered[M]) Clear() int { +func (s *LockedOrdered[M]) Clear() int { s.L.Lock() if s.iterating { s.Wait() @@ -72,7 +66,7 @@ func (s *lockedOrdered[M]) Clear() int { return s.set.Clear() } -func (s *lockedOrdered[M]) Add(m M) bool { +func (s *LockedOrdered[M]) Add(m M) bool { s.L.Lock() if s.iterating { s.Wait() @@ -81,7 +75,7 @@ func (s *lockedOrdered[M]) Add(m M) bool { return s.set.Add(m) } -func (s *lockedOrdered[M]) Remove(m M) bool { +func (s *LockedOrdered[M]) Remove(m M) bool { s.L.Lock() if s.iterating { s.Wait() @@ -90,7 +84,7 @@ func (s *lockedOrdered[M]) Remove(m M) bool { return s.set.Remove(m) } -func (s *lockedOrdered[M]) Cardinality() int { +func (s *LockedOrdered[M]) Cardinality() int { if s == nil { return 0 } @@ -101,7 +95,7 @@ func (s *lockedOrdered[M]) Cardinality() int { // Iterator yields all elements in the set in order. It holds a lock for the duration of iteration. Calling methods other than // `Contains` and `Cardinality` will block until the iteration is complete. -func (s *lockedOrdered[M]) Iterator(yield func(M) bool) { +func (s *LockedOrdered[M]) Iterator(yield func(M) bool) { s.L.Lock() s.iterating = true defer func() { @@ -113,12 +107,12 @@ func (s *lockedOrdered[M]) Iterator(yield func(M) bool) { s.set.Iterator(yield) } -func (s *lockedOrdered[M]) Clone() Set[M] { +func (s *LockedOrdered[M]) Clone() Set[M] { return NewLockedOrderedFrom(s.Iterator) } // Ordered iteration yields the index and value of each element in the set in order. -func (s *lockedOrdered[M]) Ordered(yield func(int, M) bool) { +func (s *LockedOrdered[M]) Ordered(yield func(int, M) bool) { s.L.Lock() s.iterating = true defer func() { @@ -130,7 +124,7 @@ func (s *lockedOrdered[M]) Ordered(yield func(int, M) bool) { s.set.Ordered(yield) } -func (s *lockedOrdered[M]) Backwards(yield func(int, M) bool) { +func (s *LockedOrdered[M]) Backwards(yield func(int, M) bool) { s.L.Lock() s.iterating = true defer func() { @@ -142,15 +136,15 @@ func (s *lockedOrdered[M]) Backwards(yield func(int, M) bool) { s.set.Backwards(yield) } -func (s *lockedOrdered[M]) NewEmptyOrdered() OrderedSet[M] { +func (s *LockedOrdered[M]) NewEmptyOrdered() OrderedSet[M] { return NewLockedOrdered[M]() } -func (s *lockedOrdered[M]) NewEmpty() Set[M] { +func (s *LockedOrdered[M]) NewEmpty() Set[M] { return NewLockedOrdered[M]() } -func (s *lockedOrdered[M]) Pop() (M, bool) { +func (s *LockedOrdered[M]) Pop() (M, bool) { s.L.Lock() if s.iterating { s.Wait() @@ -160,7 +154,7 @@ func (s *lockedOrdered[M]) Pop() (M, bool) { return s.set.Pop() } -func (s *lockedOrdered[M]) Sort() { +func (s *LockedOrdered[M]) Sort() { s.L.Lock() if s.iterating { s.Wait() @@ -170,28 +164,28 @@ func (s *lockedOrdered[M]) Sort() { s.set.Sort() } -func (s *lockedOrdered[M]) At(i int) (M, bool) { +func (s *LockedOrdered[M]) At(i int) (M, bool) { s.RLock() defer s.RUnlock() return s.set.At(i) } -func (s *lockedOrdered[M]) Index(m M) int { +func (s *LockedOrdered[M]) Index(m M) int { s.RLock() defer s.RUnlock() return s.set.Index(m) } -func (s *lockedOrdered[M]) String() string { +func (s *LockedOrdered[M]) String() string { s.RLock() defer s.RUnlock() return "Locked" + s.set.String() } -func (s *lockedOrdered[M]) MarshalJSON() ([]byte, error) { +func (s *LockedOrdered[M]) MarshalJSON() ([]byte, error) { s.RLock() defer s.RUnlock() @@ -208,12 +202,20 @@ func (s *lockedOrdered[M]) MarshalJSON() ([]byte, error) { return d, nil } -func (s *lockedOrdered[M]) UnmarshalJSON(d []byte) error { - s.L.Lock() +// UnmarshalJSON implements json.Unmarshaler. It will unmarshal the JSON data into the set. +func (s *LockedOrdered[M]) UnmarshalJSON(d []byte) error { + s.Lock() + if s.Cond == nil { + s.Cond = sync.NewCond(&s.RWMutex) + } if s.iterating { s.Wait() } - defer s.L.Unlock() + defer s.Unlock() + + if s.set == nil { + s.set = NewOrdered[M]() + } um, ok := s.set.(json.Unmarshaler) if !ok { diff --git a/locker.go b/locker.go new file mode 100644 index 0000000..3a00ba0 --- /dev/null +++ b/locker.go @@ -0,0 +1,10 @@ +package sets + +type locker interface { + Lock() + Unlock() + RLock() + RUnlock() + Wait() + Broadcast() +} diff --git a/map.go b/map.go index 156784c..6a4b9ce 100644 --- a/map.go +++ b/map.go @@ -8,37 +8,39 @@ import ( "slices" ) -type mapSet[M comparable] struct { +type Map[M comparable] struct { set map[M]struct{} } -// New returns an empty Set[M] instance. -func New[M comparable]() Set[M] { - return &mapSet[M]{ +var x Set[int] = new(Map[int]) + +// NewMap returns an empty Set[M] instance. +func NewMap[M comparable]() *Map[M] { + return &Map[M]{ set: make(map[M]struct{}), } } -// NewFrom returns a new Set[M] filled with the values from the sequence. -func NewFrom[M comparable](seq iter.Seq[M]) Set[M] { - s := New[M]() +// NewMapFrom returns a new Set[M] filled with the values from the sequence. +func NewMapFrom[M comparable](seq iter.Seq[M]) *Map[M] { + s := NewMap[M]() for x := range seq { s.Add(x) } return s } -// NewWith the values provides. Duplicates are removed. -func NewWith[M comparable](m ...M) Set[M] { - return NewFrom(slices.Values(m)) +// NewMapWith the values provides. Duplicates are removed. +func NewMapWith[M comparable](m ...M) *Map[M] { + return NewMapFrom(slices.Values(m)) } -func (s *mapSet[M]) Contains(m M) bool { +func (s *Map[M]) Contains(m M) bool { _, ok := s.set[m] return ok } -func (s *mapSet[M]) Clear() int { +func (s *Map[M]) Clear() int { n := len(s.set) for k := range s.set { delete(s.set, k) @@ -46,7 +48,7 @@ func (s *mapSet[M]) Clear() int { return n } -func (s *mapSet[M]) Add(m M) bool { +func (s *Map[M]) Add(m M) bool { if s.Contains(m) { return false } @@ -54,7 +56,7 @@ func (s *mapSet[M]) Add(m M) bool { return true } -func (s *mapSet[M]) Remove(m M) bool { +func (s *Map[M]) Remove(m M) bool { if !s.Contains(m) { return false } @@ -62,12 +64,12 @@ func (s *mapSet[M]) Remove(m M) bool { return true } -func (s *mapSet[M]) Cardinality() int { +func (s *Map[M]) Cardinality() int { return len(s.set) } // Iterator yields all elements in the set. -func (s *mapSet[M]) Iterator(yield func(M) bool) { +func (s *Map[M]) Iterator(yield func(M) bool) { for k := range s.set { if !yield(k) { return @@ -75,15 +77,15 @@ func (s *mapSet[M]) Iterator(yield func(M) bool) { } } -func (s *mapSet[M]) Clone() Set[M] { - return NewFrom(s.Iterator) +func (s *Map[M]) Clone() Set[M] { + return NewMapFrom(s.Iterator) } -func (s *mapSet[M]) NewEmpty() Set[M] { - return New[M]() +func (s *Map[M]) NewEmpty() Set[M] { + return NewMap[M]() } -func (s *mapSet[M]) Pop() (M, bool) { +func (s *Map[M]) Pop() (M, bool) { for k := range s.set { delete(s.set, k) return k, true @@ -92,12 +94,12 @@ func (s *mapSet[M]) Pop() (M, bool) { return m, false } -func (s *mapSet[M]) String() string { +func (s *Map[M]) String() string { var m M return fmt.Sprintf("Set[%T](%v)", m, slices.Collect(maps.Keys(s.set))) } -func (s *mapSet[M]) MarshalJSON() ([]byte, error) { +func (s *Map[M]) MarshalJSON() ([]byte, error) { v := slices.Collect(s.Iterator) if len(v) == 0 { return []byte("[]"), nil @@ -110,13 +112,16 @@ func (s *mapSet[M]) MarshalJSON() ([]byte, error) { return d, nil } -func (s *mapSet[M]) UnmarshalJSON(d []byte) error { +func (s *Map[M]) UnmarshalJSON(d []byte) error { var um []M if err := json.Unmarshal(d, &um); err != nil { return fmt.Errorf("unmarshaling map set: %w", err) } s.Clear() + if s.set == nil { + s.set = make(map[M]struct{}) + } for _, m := range um { s.Add(m) } diff --git a/ordered.go b/ordered.go index 9f500b8..728fb30 100644 --- a/ordered.go +++ b/ordered.go @@ -8,21 +8,23 @@ import ( "slices" ) -type ordered[M cmp.Ordered] struct { +type Ordered[M cmp.Ordered] struct { idx map[M]int values []M } +var _ OrderedSet[int] = new(Ordered[int]) + // NewOrdered returns an empty OrderedSet[M]. -func NewOrdered[M cmp.Ordered]() OrderedSet[M] { - return &ordered[M]{ +func NewOrdered[M cmp.Ordered]() *Ordered[M] { + return &Ordered[M]{ idx: make(map[M]int), values: make([]M, 0), } } // NewOrderedFrom returns a new OrderedSet[M] filled with the values from the sequence. -func NewOrderedFrom[M cmp.Ordered](seq iter.Seq[M]) OrderedSet[M] { +func NewOrderedFrom[M cmp.Ordered](seq iter.Seq[M]) *Ordered[M] { s := NewOrdered[M]() for x := range seq { s.Add(x) @@ -31,16 +33,16 @@ func NewOrderedFrom[M cmp.Ordered](seq iter.Seq[M]) OrderedSet[M] { } // NewOrderedWith the values provides. Duplicates are removed. -func NewOrderedWith[M cmp.Ordered](m ...M) OrderedSet[M] { +func NewOrderedWith[M cmp.Ordered](m ...M) *Ordered[M] { return NewOrderedFrom(slices.Values(m)) } -func (s *ordered[M]) Contains(m M) bool { +func (s *Ordered[M]) Contains(m M) bool { _, ok := s.idx[m] return ok } -func (s *ordered[M]) Clear() int { +func (s *Ordered[M]) Clear() int { n := len(s.values) for k := range s.idx { delete(s.idx, k) @@ -49,7 +51,7 @@ func (s *ordered[M]) Clear() int { return n } -func (s *ordered[M]) Add(m M) bool { +func (s *Ordered[M]) Add(m M) bool { if s.Contains(m) { return false } @@ -58,7 +60,7 @@ func (s *ordered[M]) Add(m M) bool { return true } -func (s *ordered[M]) Remove(m M) bool { +func (s *Ordered[M]) Remove(m M) bool { if !s.Contains(m) { return false } @@ -71,7 +73,7 @@ func (s *ordered[M]) Remove(m M) bool { return true } -func (s *ordered[M]) Cardinality() int { +func (s *Ordered[M]) Cardinality() int { if s == nil { return 0 } @@ -79,7 +81,7 @@ func (s *ordered[M]) Cardinality() int { } // Iterator yields all elements in the set in order. -func (s *ordered[M]) Iterator(yield func(M) bool) { +func (s *Ordered[M]) Iterator(yield func(M) bool) { for _, k := range s.values { if !yield(k) { return @@ -87,12 +89,12 @@ func (s *ordered[M]) Iterator(yield func(M) bool) { } } -func (s *ordered[M]) Clone() Set[M] { +func (s *Ordered[M]) Clone() Set[M] { return NewOrderedFrom(s.Iterator) } // Ordered iteration yields the index and value of each element in the set in order. -func (s *ordered[M]) Ordered(yield func(int, M) bool) { +func (s *Ordered[M]) Ordered(yield func(int, M) bool) { for i, k := range s.values { if !yield(i, k) { return @@ -100,7 +102,7 @@ func (s *ordered[M]) Ordered(yield func(int, M) bool) { } } -func (s *ordered[M]) Backwards(yield func(int, M) bool) { +func (s *Ordered[M]) Backwards(yield func(int, M) bool) { for i := len(s.values) - 1; i >= 0; i-- { if !yield(i, s.values[i]) { return @@ -108,15 +110,15 @@ func (s *ordered[M]) Backwards(yield func(int, M) bool) { } } -func (s *ordered[M]) NewEmptyOrdered() OrderedSet[M] { +func (s *Ordered[M]) NewEmptyOrdered() OrderedSet[M] { return NewOrdered[M]() } -func (s *ordered[M]) NewEmpty() Set[M] { +func (s *Ordered[M]) NewEmpty() Set[M] { return NewOrdered[M]() } -func (s *ordered[M]) Pop() (M, bool) { +func (s *Ordered[M]) Pop() (M, bool) { for k := range s.idx { s.Remove(k) return k, true @@ -125,14 +127,14 @@ func (s *ordered[M]) Pop() (M, bool) { return m, false } -func (s *ordered[M]) Sort() { +func (s *Ordered[M]) Sort() { slices.Sort(s.values) for i, v := range s.values { s.idx[v] = i } } -func (s *ordered[M]) At(i int) (M, bool) { +func (s *Ordered[M]) At(i int) (M, bool) { var zero M if i < 0 || i >= len(s.values) { return zero, false @@ -140,7 +142,7 @@ func (s *ordered[M]) At(i int) (M, bool) { return s.values[i], true } -func (s *ordered[M]) Index(m M) int { +func (s *Ordered[M]) Index(m M) int { i, ok := s.idx[m] if !ok { return -1 @@ -148,12 +150,12 @@ func (s *ordered[M]) Index(m M) int { return i } -func (s *ordered[M]) String() string { +func (s *Ordered[M]) String() string { var m M return fmt.Sprintf("OrderedSet[%T](%v)", m, s.values) } -func (s *ordered[M]) MarshalJSON() ([]byte, error) { +func (s *Ordered[M]) MarshalJSON() ([]byte, error) { if len(s.values) == 0 { return []byte("[]"), nil } @@ -165,12 +167,18 @@ func (s *ordered[M]) MarshalJSON() ([]byte, error) { return d, nil } -func (s *ordered[M]) UnmarshalJSON(d []byte) error { +func (s *Ordered[M]) UnmarshalJSON(d []byte) error { s.Clear() + if s.values == nil { + s.values = make([]M, 0) + } if err := json.Unmarshal(d, &s.values); err != nil { return fmt.Errorf("unmarshaling ordered set: %w", err) } + if s.idx == nil { + s.idx = make(map[M]int) + } for i, v := range s.values { s.idx[v] = i } diff --git a/set.go b/set.go index 5664d8b..1556690 100644 --- a/set.go +++ b/set.go @@ -246,9 +246,9 @@ func IsEmpty[K comparable](s Set[K]) bool { return s.Cardinality() == 0 } -// Map applies the function to each element in the set and returns a new set with the results. -func Map[K comparable, V comparable](s Set[K], f func(K) V) Set[V] { - m := New[V]() +// MapBy applies the function to each element in the set and returns a new set with the results. +func MapBy[K comparable, V comparable](s Set[K], f func(K) V) Set[V] { + m := NewMap[V]() for k := range s.Iterator { m.Add(f(k)) } diff --git a/set_test.go b/set_test.go index 7e20d48..4df4e16 100644 --- a/set_test.go +++ b/set_test.go @@ -28,7 +28,7 @@ func TestMap(t *testing.T) { t.Parallel() setStateMachine := &SetStateMachine{ - set: New[int](), + set: NewMap[int](), stateI: make(map[int]int), } rapid.Check(t, func(t *rapid.T) { @@ -40,7 +40,7 @@ func TestSyncMap(t *testing.T) { t.Parallel() setStateMachine := &SetStateMachine{ - set: NewSync[int](), + set: NewSyncMap[int](), stateI: make(map[int]int), } rapid.Check(t, func(t *rapid.T) { @@ -109,6 +109,7 @@ func (sm *SetStateMachine) Contains(t *rapid.T) { t.Fatalf("expected %v to exist: %v", i, got) } } + func (sm *SetStateMachine) Clone(t *rapid.T) { sm.set = sm.set.Clone() } @@ -181,7 +182,7 @@ func (sm *SetStateMachine) Intersection(t *rapid.T) { } } -func (sm *SetStateMachine) add(t *rapid.T, i int) { +func (sm *SetStateMachine) add(_ *rapid.T, i int) { if _, exist := sm.stateI[i]; exist { return } @@ -388,6 +389,8 @@ func (sm *SetStateMachine) Check(t *rapid.T) { } func testSetConcurrency(t *testing.T, set Set[int]) { + t.Helper() + started := make(chan struct{}) start := make(chan struct{}) var finished sync.WaitGroup @@ -430,7 +433,7 @@ func testSetConcurrency(t *testing.T, set Set[int]) { finished.Done() }, func(base int) { - other := New[int]() + other := NewMap[int]() for i := range (base + 1) * 100 { other.Add(i) } @@ -486,7 +489,7 @@ func TestLockedOrdered_Concurrency(t *testing.T) { func TestSync_Concurrency(t *testing.T) { t.Parallel() testSetConcurrency(t, - NewSyncFrom(slices.Values([]int{9, 8, 7, 6, 5, 4, 3, 2, 1})), + NewSyncMapFrom(slices.Values([]int{9, 8, 7, 6, 5, 4, 3, 2, 1})), ) } @@ -574,7 +577,7 @@ func TestChunk_Ordered(t *testing.T) { func TestChunk(t *testing.T) { t.Parallel() - s := New[int]() + s := NewMap[int]() for i := range 22 { s.Add(i) } @@ -614,9 +617,84 @@ type foo struct { func (f *foo) Foo() {} -func TestSet_json(t *testing.T) { +func TestLocked_JSON(t *testing.T) { + t.Parallel() + set := NewLocked[Foo]() + set.Add(&foo{}) + set.Add(&bar{}) + d, err := json.Marshal(set) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Log("JSON:", string(d)) + // can't unmarshal into a set of interfaces + if err := json.Unmarshal(d, &set); err == nil { + t.Fatalf("expected error: %v", err) + } + + set2 := NewLocked[foo]() + set2.Add(foo{Baz: "bar"}) + set2.Add(foo{Baz: "foo"}) + + d, err = json.Marshal(set2) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Log("JSON:", string(d)) + if err = json.Unmarshal(d, &set2); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + set3 := NewLocked[*foo]() + set3.Add(&foo{Baz: "bar"}) + set3.Add(&foo{Baz: "foo"}) + d, err = json.Marshal(set3) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Log("JSON:", string(d)) + if err = json.Unmarshal(d, &set3); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + set4 := NewLocked[chan foo]() + set4.Add(make(chan foo)) + set4.Add(make(chan foo)) + // see comparison rules for channels + if set.Cardinality() != 2 { + t.Fatalf("expected 2 elements, got %d", set.Cardinality()) + } + _, err = json.Marshal(set4) + // can't marshal a set of channels + if err == nil { + t.Fatalf("expected error: %v", err) + } + + type Bar struct { + Set *Locked[int] + } + + b := Bar{Set: NewLocked[int]()} + b.Set.Add(1) + b.Set.Add(2) + + d, err = json.Marshal(b) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Log("JSON:", string(d)) + var c Bar + if err = json.Unmarshal(d, &c); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !Equal(b.Set, c.Set) { + t.Fatalf("expected %v, got %v", Elements(b.Set), Elements(c.Set)) + } +} + +func TestMap_JSON(t *testing.T) { t.Parallel() - set := New[Foo]() + set := NewMap[Foo]() set.Add(&foo{}) set.Add(&bar{}) d, err := json.Marshal(set) @@ -629,7 +707,7 @@ func TestSet_json(t *testing.T) { t.Fatalf("expected error: %v", err) } - set2 := New[foo]() + set2 := NewMap[foo]() set2.Add(foo{Baz: "bar"}) set2.Add(foo{Baz: "foo"}) @@ -642,7 +720,7 @@ func TestSet_json(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - set3 := New[*foo]() + set3 := NewMap[*foo]() set3.Add(&foo{Baz: "bar"}) set3.Add(&foo{Baz: "foo"}) d, err = json.Marshal(set3) @@ -654,7 +732,7 @@ func TestSet_json(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - set4 := New[chan foo]() + set4 := NewMap[chan foo]() set4.Add(make(chan foo)) set4.Add(make(chan foo)) // see comparison rules for channels @@ -666,4 +744,184 @@ func TestSet_json(t *testing.T) { if err == nil { t.Fatalf("expected error: %v", err) } + + type Bar struct { + Set *Map[int] + } + + b := Bar{Set: NewMap[int]()} + b.Set.Add(1) + b.Set.Add(2) + + d, err = json.Marshal(b) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Log("JSON:", string(d)) + var c Bar + if err = json.Unmarshal(d, &c); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !Equal(b.Set, c.Set) { + t.Fatalf("expected %v, got %v", Elements(b.Set), Elements(c.Set)) + } +} + +func TestOrdered_JSON(t *testing.T) { + t.Parallel() + + a := NewOrdered[int]() + a.Add(1) + a.Add(2) + + j, err := json.Marshal(a) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Log("JSON:", string(j)) + var b *Ordered[int] + if err = json.Unmarshal(j, &b); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !Equal(a, b) { + t.Fatalf("expected %v, got %v", Elements(a), Elements(b)) + } + + type Bar struct { + Set *Ordered[int] + } + + c := Bar{Set: NewOrdered[int]()} + c.Set.Add(1) + c.Set.Add(2) + + j, err = json.Marshal(c) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Log("JSON:", string(j)) + var d Bar + if err = json.Unmarshal(j, &d); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !Equal(c.Set, d.Set) { + t.Fatalf("expected %v, got %v", Elements(c.Set), Elements(d.Set)) + } +} + +func TestLockedOrdered_JSON(t *testing.T) { + t.Parallel() + + a := NewLockedOrdered[int]() + a.Add(1) + a.Add(2) + + j, err := json.Marshal(a) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Log("JSON:", string(j)) + var b *LockedOrdered[int] + if err = json.Unmarshal(j, &b); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !Equal(a, b) { + t.Fatalf("expected %v, got %v", Elements(a), Elements(b)) + } + + type Bar struct { + Set *LockedOrdered[int] + } + + c := Bar{Set: NewLockedOrdered[int]()} + c.Set.Add(1) + c.Set.Add(2) + + j, err = json.Marshal(c) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Log("JSON:", string(j)) + var d Bar + if err = json.Unmarshal(j, &d); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !Equal(c.Set, d.Set) { + t.Fatalf("expected %v, got %v", Elements(c.Set), Elements(d.Set)) + } +} + +func TestSyncMap_JSON(t *testing.T) { + t.Parallel() + set := NewSyncMap[Foo]() + set.Add(&foo{}) + set.Add(&bar{}) + d, err := json.Marshal(set) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Log("JSON:", string(d)) + // can't unmarshal into a set of interfaces + if err := json.Unmarshal(d, &set); err == nil { + t.Fatalf("expected error: %v", err) + } + + set2 := NewSyncMap[foo]() + set2.Add(foo{Baz: "bar"}) + set2.Add(foo{Baz: "foo"}) + + d, err = json.Marshal(set2) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Log("JSON:", string(d)) + if err = json.Unmarshal(d, &set2); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + set3 := NewSyncMap[*foo]() + set3.Add(&foo{Baz: "bar"}) + set3.Add(&foo{Baz: "foo"}) + d, err = json.Marshal(set3) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Log("JSON:", string(d)) + if err = json.Unmarshal(d, &set3); err != nil { + t.Fatalf("unexpected error: %v", err) + } + + set4 := NewSyncMap[chan foo]() + set4.Add(make(chan foo)) + set4.Add(make(chan foo)) + // see comparison rules for channels + if set.Cardinality() != 2 { + t.Fatalf("expected 2 elements, got %d", set.Cardinality()) + } + _, err = json.Marshal(set4) + // can't marshal a set of channels + if err == nil { + t.Fatalf("expected error: %v", err) + } + + type Bar struct { + Set *SyncMap[int] + } + + b := Bar{Set: NewSyncMap[int]()} + b.Set.Add(1) + b.Set.Add(2) + + d, err = json.Marshal(b) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + t.Log("JSON:", string(d)) + var c Bar + if err = json.Unmarshal(d, &c); err != nil { + t.Fatalf("unexpected error: %v", err) + } + if !Equal(b.Set, c.Set) { + t.Fatalf("expected %v, got %v", Elements(b.Set), Elements(c.Set)) + } } diff --git a/sync.go b/sync.go index 4a1de02..20ab575 100644 --- a/sync.go +++ b/sync.go @@ -8,42 +8,42 @@ import ( "sync" ) -type syncMap[M comparable] struct { +type SyncMap[M comparable] struct { m sync.Map } -var _ Set[int] = new(syncMap[int]) +var _ Set[int] = new(SyncMap[int]) -// NewSync returns an empty Set[M] that is backed by a sync.Map, making it safe for concurrent use. +// NewSyncMap returns an empty Set[M] that is backed by a sync.Map, making it safe for concurrent use. // Please read the documentation for [sync.Map] to understand the behavior of modifying the map. -func NewSync[M comparable]() Set[M] { - return &syncMap[M]{ +func NewSyncMap[M comparable]() *SyncMap[M] { + return &SyncMap[M]{ m: sync.Map{}, } } -// NewSyncFrom returns a new Set[M] filled with the values from the sequence and is backed by a sync.Mao, making it safe +// NewSyncMapFrom returns a new Set[M] filled with the values from the sequence and is backed by a sync.Mao, making it safe // for concurrent use. Please read the documentation for [sync.Map] to understand the behavior of modifying the map. -func NewSyncFrom[M comparable](seq iter.Seq[M]) Set[M] { - s := NewSync[M]() +func NewSyncMapFrom[M comparable](seq iter.Seq[M]) *SyncMap[M] { + s := NewSyncMap[M]() for x := range seq { s.Add(x) } return s } -// NewSyncWith returns a new Set[M] filled with the values provided and is backed by a sync.Mao, making it safe +// NewSyncMapWith returns a new Set[M] filled with the values provided and is backed by a sync.Mao, making it safe // for concurrent use. Please read the documentation for [sync.Map] to understand the behavior of modifying the map. -func NewSyncWith[M comparable](m ...M) Set[M] { - return NewSyncFrom(slices.Values(m)) +func NewSyncMapWith[M comparable](m ...M) *SyncMap[M] { + return NewSyncMapFrom(slices.Values(m)) } -func (s *syncMap[M]) Contains(m M) bool { +func (s *SyncMap[M]) Contains(m M) bool { _, ok := s.m.Load(m) return ok } -func (s *syncMap[M]) Clear() int { +func (s *SyncMap[M]) Clear() int { var n int s.m.Range(func(_, _ interface{}) bool { n++ @@ -53,12 +53,12 @@ func (s *syncMap[M]) Clear() int { return n } -func (s *syncMap[M]) Add(m M) bool { +func (s *SyncMap[M]) Add(m M) bool { _, loaded := s.m.LoadOrStore(m, struct{}{}) return !loaded } -func (s *syncMap[M]) Pop() (M, bool) { +func (s *SyncMap[M]) Pop() (M, bool) { var m M var ok bool @@ -73,12 +73,12 @@ func (s *syncMap[M]) Pop() (M, bool) { return m, ok } -func (s *syncMap[M]) Remove(m M) bool { +func (s *SyncMap[M]) Remove(m M) bool { _, ok := s.m.LoadAndDelete(m) return ok } -func (s *syncMap[M]) Cardinality() int { +func (s *SyncMap[M]) Cardinality() int { if s == nil { return 0 } @@ -92,26 +92,26 @@ func (s *syncMap[M]) Cardinality() int { // Iterator yields all elements in the set. It is safe to call concurrently with other methods, but the order and // behavior is undefined, as per [sync.Map]'s `Range`. -func (s *syncMap[M]) Iterator(yield func(M) bool) { +func (s *SyncMap[M]) Iterator(yield func(M) bool) { s.m.Range(func(key, _ interface{}) bool { return yield(key.(M)) }) } -func (s *syncMap[M]) Clone() Set[M] { - return NewSyncFrom(s.Iterator) +func (s *SyncMap[M]) Clone() Set[M] { + return NewSyncMapFrom(s.Iterator) } -func (s *syncMap[M]) NewEmpty() Set[M] { - return NewSync[M]() +func (s *SyncMap[M]) NewEmpty() Set[M] { + return NewSyncMap[M]() } -func (s *syncMap[M]) String() string { +func (s *SyncMap[M]) String() string { var m M return fmt.Sprintf("SyncSet[%T](%v)", m, slices.Collect(s.Iterator)) } -func (s *syncMap[M]) MarshalJSON() ([]byte, error) { +func (s *SyncMap[M]) MarshalJSON() ([]byte, error) { v := slices.Collect(s.Iterator) if len(v) == 0 { return []byte("[]"), nil @@ -124,7 +124,7 @@ func (s *syncMap[M]) MarshalJSON() ([]byte, error) { return d, nil } -func (s *syncMap[M]) UnmarshalJSON(d []byte) error { +func (s *SyncMap[M]) UnmarshalJSON(d []byte) error { var x []M if err := json.Unmarshal(d, &x); err != nil { return fmt.Errorf("unmarshaling sync set: %w", err) From 7e3ca2a986cc942f009420f00681b8902a9c54df Mon Sep 17 00:00:00 2001 From: Edward Muller Date: Thu, 27 Mar 2025 21:25:42 -0700 Subject: [PATCH 2/3] Update sync.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- sync.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync.go b/sync.go index 20ab575..d624ec4 100644 --- a/sync.go +++ b/sync.go @@ -22,7 +22,7 @@ func NewSyncMap[M comparable]() *SyncMap[M] { } } -// NewSyncMapFrom returns a new Set[M] filled with the values from the sequence and is backed by a sync.Mao, making it safe +// NewSyncMapFrom returns a new Set[M] filled with the values from the sequence and is backed by a sync.Map, making it safe // for concurrent use. Please read the documentation for [sync.Map] to understand the behavior of modifying the map. func NewSyncMapFrom[M comparable](seq iter.Seq[M]) *SyncMap[M] { s := NewSyncMap[M]() From 46d7582a0dd3b4b5e595176781837a8b09b06284 Mon Sep 17 00:00:00 2001 From: Edward Muller Date: Thu, 27 Mar 2025 21:25:51 -0700 Subject: [PATCH 3/3] Update sync.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- sync.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sync.go b/sync.go index d624ec4..a8d10f0 100644 --- a/sync.go +++ b/sync.go @@ -32,7 +32,7 @@ func NewSyncMapFrom[M comparable](seq iter.Seq[M]) *SyncMap[M] { return s } -// NewSyncMapWith returns a new Set[M] filled with the values provided and is backed by a sync.Mao, making it safe +// NewSyncMapWith returns a new Set[M] filled with the values provided and is backed by a sync.Map, making it safe // for concurrent use. Please read the documentation for [sync.Map] to understand the behavior of modifying the map. func NewSyncMapWith[M comparable](m ...M) *SyncMap[M] { return NewSyncMapFrom(slices.Values(m))