From 6084d9c29be522c67f35ed0b395d2281b19fd1d2 Mon Sep 17 00:00:00 2001 From: Edward Muller Date: Sat, 29 Mar 2025 13:00:19 -0700 Subject: [PATCH] Ergonomics, doc updates, simplify locking * Rename `New{MapWith,MapFrom,}` to `New{With,From,}` for ergonomic reasons. Sorry about the thrash. * Since the types are exposed, started to add godocs for each of the methods for the exposed types. * Removed the sync.Cond on the locked types. NFI why I had it there as it's not needed. --- CHANGELOG.MD | 6 +++ README.MD | 2 +- examples_test.go | 74 ++++++++++++++-------------- locked.go | 93 +++++++++++++++-------------------- locked_ordered.go | 122 ++++++++++++++++++++-------------------------- locker.go | 5 +- map.go | 35 +++++++++---- ordered.go | 26 ++++++++-- set.go | 2 +- set_test.go | 16 +++--- sync.go | 1 + 11 files changed, 196 insertions(+), 186 deletions(-) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 1a394bb..e6e23b1 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -1,5 +1,11 @@ # Changelog +## v0.10.0 + +* Rename `New{MapWith,MapFrom,}` to `New{With,From,}` for ergonomic reasons. Sorry about the thrash. +* Since the types are exposed, started to add godocs for each of the methods for the exposed types. +* Removed the sync.Cond on the locked types. NFI why I had it there as it's not needed. + ## v0.9.1 * Small staticcheck fixes diff --git a/README.MD b/README.MD index 06f5215..bce0475 100644 --- a/README.MD +++ b/README.MD @@ -26,7 +26,7 @@ go get github.com/freeformz/sets * Multiple set implementations: * `New()` -> Map based set; * `NewLocked()` -> Map based that uses a lock to be concurrency safe; - * `NewSync()` -> sync.Map based (concurrency safe); + * `NewSyncMap()` -> sync.Map based (concurrency safe); * `NewOrdered()` -> ordered set (uses a map for indexes and a slice for order); * `NewLockedOrdered()` -> ordered set that is concurrency safe. * `set` package functions align with standard lib packages like `slices` and `maps`. diff --git a/examples_test.go b/examples_test.go index 68c12a9..b1b1528 100644 --- a/examples_test.go +++ b/examples_test.go @@ -9,7 +9,7 @@ import ( ) func ExampleSet() { - ints := NewMap[int]() + ints := New[int]() ints.Add(5) ints.Add(1) ints.Add(9) @@ -150,7 +150,7 @@ func ExampleOrderedSet() { } func ExampleElements() { - ints := NewMapWith(5, 3, 2) + ints := NewWith(5, 3, 2) // []T is returned elements := Elements(ints) @@ -164,7 +164,7 @@ func ExampleElements() { } func ExampleAppendSeq() { - ints := NewMapWith(5, 3) + ints := NewWith(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 := NewMapWith(5, 3, 2) + ints := NewWith(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 := NewMapWith(5, 3) - b := NewMapWith(3, 2) + a := NewWith(5, 3) + b := NewWith(3, 2) c := Union(a, b) out := make([]int, 0, c.Cardinality()) @@ -201,8 +201,8 @@ func ExampleUnion() { } func ExampleIntersection() { - a := NewMapWith(5, 3) - b := NewMapWith(3, 2) + a := NewWith(5, 3) + b := NewWith(3, 2) c := Intersection(a, b) out := make([]int, 0, c.Cardinality()) @@ -217,8 +217,8 @@ func ExampleIntersection() { } func ExampleDifference() { - a := NewMapWith(5, 3) - b := NewMapWith(3, 2) + a := NewWith(5, 3) + b := NewWith(3, 2) c := Difference(a, b) out := make([]int, 0, c.Cardinality()) @@ -233,8 +233,8 @@ func ExampleDifference() { } func ExampleSymmetricDifference() { - a := NewMapWith(5, 3) - b := NewMapWith(3, 2) + a := NewWith(5, 3) + b := NewWith(3, 2) c := SymmetricDifference(a, b) for i := range c.Iterator { @@ -246,8 +246,8 @@ func ExampleSymmetricDifference() { } func ExampleSubset() { - a := NewMapWith(5, 3) - b := NewMapWith(5, 3, 2) + a := NewWith(5, 3) + b := NewWith(5, 3, 2) if Subset(a, b) { fmt.Println("a is a subset of b") @@ -262,8 +262,8 @@ func ExampleSubset() { } func ExampleSuperset() { - a := NewMapWith(5, 3) - b := NewMapWith(5, 3, 2) + a := NewWith(5, 3) + b := NewWith(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 := NewMapWith(5, 3) - b := NewMapWith(5, 3) + a := NewWith(5, 3) + b := NewWith(5, 3) if Equal(a, b) { fmt.Println("a and b are equal") @@ -303,7 +303,7 @@ func ExampleEqual() { } func ExampleContainsSeq() { - ints := NewMap[int]() + ints := New[int]() if ContainsSeq(ints, slices.Values([]int{})) { fmt.Println("Empty set contains empty sequence") } @@ -326,8 +326,8 @@ func ExampleContainsSeq() { } func ExampleDisjoint() { - a := NewMapWith(5, 3) - b := NewMapWith(2, 4) + a := NewWith(5, 3) + b := NewWith(2, 4) if Disjoint(a, b) { fmt.Println("a and b are disjoint") @@ -368,7 +368,7 @@ func ExampleEqualOrdered() { } func ExampleMin() { - ints := NewMapWith(3, 2, 5) + ints := NewWith(3, 2, 5) min := Min(ints) fmt.Println(min) @@ -376,7 +376,7 @@ func ExampleMin() { } func ExampleMax() { - ints := NewMapWith(3, 5, 2) + ints := NewWith(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 ExampleNewMapWith() { - set := NewMapWith("a", "b", "c", "b") +func ExampleNewWith() { + set := NewWith("a", "b", "c", "b") fmt.Println(set.Cardinality()) // Output: 3 @@ -543,8 +543,8 @@ func ExampleNewSyncMapWith() { // Output: 3 } -func ExampleNewMap() { - set := NewMap[string]() +func ExampleNew() { + set := New[string]() set.Add("a") set.Add("b") set.Add("c") @@ -614,9 +614,9 @@ func ExampleNewSyncMap() { // Output: 3 } -func ExampleNewMapFrom() { +func ExampleNewFrom() { m := []string{"a", "b", "c", "b"} - set := NewMapFrom(slices.Values(m)) + set := NewFrom(slices.Values(m)) fmt.Println(set.Cardinality()) // Output: 3 @@ -671,7 +671,7 @@ func ExampleNewSyncMapFrom() { } func ExampleNewLockedWrapping() { - set := NewMapWith("a", "b", "c", "b") + set := NewWith("a", "b", "c", "b") wrapped := NewLockedWrapping(set) // wrapped is safe for concurrent use @@ -691,7 +691,7 @@ func ExampleNewLockedOrderedWrapping() { } func ExampleIsEmpty() { - set := NewMap[int]() + set := New[int]() if IsEmpty(set) { fmt.Println("set is empty") } @@ -706,7 +706,7 @@ func ExampleIsEmpty() { } func ExampleMapBy() { - set := NewMapWith(1, 2, 3) + set := NewWith(1, 2, 3) mapped := MapBy(set, func(i int) int { return i * 2 @@ -733,7 +733,7 @@ func ExampleMapBy() { func ExampleMapTo() { set := NewOrderedWith(3, 1, 2) - dest := NewMap[string]() + dest := New[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 := NewMapWith(3, 1, 2) + set := NewWith(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 := NewMapWith(3, 0, 1, 2, 4) + set := NewWith(3, 0, 1, 2, 4) filtered := Filter(set, func(i int) bool { return i > 2 @@ -776,7 +776,7 @@ func ExampleFilter() { } func ExampleReduce() { - set := NewMapWith(3, 1, 2) + set := NewWith(3, 1, 2) sum := Reduce(set, 0, func(agg, v int) int { return agg + v @@ -801,7 +801,7 @@ func ExampleReduceRight() { } func ExampleForEach() { - set := NewMapWith(3, 1, 2) + set := NewWith(3, 1, 2) ForEach(set, func(i int) { fmt.Println(i) diff --git a/locked.go b/locked.go index 40fd78b..920cd60 100644 --- a/locked.go +++ b/locked.go @@ -8,23 +8,22 @@ import ( "sync" ) +// Locked is a concurrency safe wrapper around a Set[M]. It uses a read-write lock to allow multiple readers to access +// the set concurrently, but only one writer at a time. The set is not ordered and does not guarantee the order of +// elements when iterating over them. It is safe for concurrent use. type Locked[M comparable] struct { set Set[M] sync.RWMutex - *sync.Cond - iterating bool } var _ Set[int] = new(Locked[int]) -// NewLocked returns an empty Set[M] that is safe for concurrent use. +// NewLocked returns an empty *Locked[M] that is safe for concurrent use. func NewLocked[M comparable]() *Locked[M] { - l := &Locked[M]{set: NewMap[M]()} - l.Cond = sync.NewCond(&l.RWMutex) - return l + return &Locked[M]{set: New[M]()} } -// NewLockedFrom returns a new Set[M] filled with the values from the sequence. +// NewLockedFrom returns a new *Locked[M] filled with the values from the sequence. func NewLockedFrom[M comparable](seq iter.Seq[M]) *Locked[M] { s := NewLocked[M]() for x := range seq { @@ -33,7 +32,7 @@ func NewLockedFrom[M comparable](seq iter.Seq[M]) *Locked[M] { return s } -// NewLockedWith the values provides. Duplicates are removed. +// NewLockedWith returns a *Locked[M] with the values provided. func NewLockedWith[M comparable](m ...M) *Locked[M] { return NewLockedFrom(slices.Values(m)) } @@ -41,7 +40,7 @@ func NewLockedWith[M comparable](m ...M) *Locked[M] { // 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 _, ok := set.(locker); ok { + if _, ok := set.(Locker); ok { return set } @@ -51,89 +50,82 @@ func NewLockedWrapping[M comparable](set Set[M]) Set[M] { return lset } +// Contains returns true if the set contains the element. func (s *Locked[M]) Contains(m M) bool { - s.RWMutex.RLock() - defer s.RWMutex.RUnlock() + s.RLock() + defer s.RUnlock() return s.set.Contains(m) } +// Clear the set and returns the number of elements removed. func (s *Locked[M]) Clear() int { - s.Cond.L.Lock() - if s.iterating { - s.Cond.Wait() - } - defer s.Cond.L.Unlock() + s.Lock() + defer s.Unlock() return s.set.Clear() } +// Add an element to the set. Returns true if the element was added, false if it was already present. func (s *Locked[M]) Add(m M) bool { - s.Cond.L.Lock() - if s.iterating { - s.Cond.Wait() - } - defer s.Cond.L.Unlock() + s.Lock() + defer s.Unlock() return s.set.Add(m) } +// Remove an element from the set. Returns true if the element was removed, false if it was not present. func (s *Locked[M]) Remove(m M) bool { - s.Cond.L.Lock() - if s.iterating { - s.Cond.Wait() - } - defer s.Cond.L.Unlock() + s.Lock() + defer s.Unlock() return s.set.Remove(m) } +// Cardinality returns the number of elements in the set. func (s *Locked[M]) Cardinality() int { - if s == nil { - return 0 - } - s.RWMutex.RLock() - defer s.RWMutex.RUnlock() + s.RLock() + defer s.RUnlock() return s.set.Cardinality() } -// 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. +// Iterator yields all elements in the set. It holds a read lock for the duration of iteration. Calling any method that +// modifies the set while iteration is happening will block until the iteration is complete. func (s *Locked[M]) Iterator(yield func(M) bool) { - s.Cond.L.Lock() - s.iterating = true - defer func() { - s.iterating = false - s.Cond.Broadcast() - s.Cond.L.Unlock() - }() + s.RLock() + defer s.RUnlock() s.set.Iterator(yield) } +// Clone returns a new set of the same underlying type. func (s *Locked[M]) Clone() Set[M] { + s.RLock() + defer s.RUnlock() return NewLockedFrom(s.Iterator) } +// NewEmpty returns a new empty set of the same underlying type. func (s *Locked[M]) NewEmpty() Set[M] { return NewLocked[M]() } +// Pop removes and returns an element from the set. If the set is empty, it returns the zero value of M and false. func (s *Locked[M]) Pop() (M, bool) { - s.L.Lock() - if s.iterating { - s.Wait() - } - defer s.L.Unlock() + s.Lock() + defer s.Unlock() return s.set.Pop() } +// String returns a string representation of the set. It returns a string of the form LockedSet[T](). func (s *Locked[M]) String() string { s.RLock() defer s.RUnlock() return "Locked" + s.set.String() } +// MarshalJSON implements json.Marshaler. It will marshal the set into a JSON array of the elements in the set. If the +// set is empty an empty JSON array is returned. func (s *Locked[M]) MarshalJSON() ([]byte, error) { s.RLock() defer s.RUnlock() @@ -150,19 +142,14 @@ func (s *Locked[M]) MarshalJSON() ([]byte, error) { return d, nil } -// UnmarshalJSON implements json.Unmarshaler. It will unmarshal the JSON data into the set. +// UnmarshalJSON implements json.Unmarshaler. It expects a JSON array of the elements in the set. If the set is empty, +// it returns an empty set. If the JSON is invalid, it returns an error. 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.Unlock() if s.set == nil { - s.set = NewMap[M]() + s.set = New[M]() } um, ok := s.set.(json.Unmarshaler) if !ok { diff --git a/locked_ordered.go b/locked_ordered.go index 60cbd43..edfb9e6 100644 --- a/locked_ordered.go +++ b/locked_ordered.go @@ -9,23 +9,20 @@ import ( "sync" ) +// LockedOrdered is a concurrency safe wrapper around an OrderedSet[M]. It uses a read-write lock to allow multiple readers. type LockedOrdered[M cmp.Ordered] struct { set OrderedSet[M] sync.RWMutex - *sync.Cond - iterating bool } var _ Set[int] = new(LockedOrdered[int]) -// NewLockedOrdered returns an empty OrderedSet[M] instance that is safe for concurrent use. +// NewLockedOrdered returns an empty *LockedOrdered[M] instance that is safe for concurrent use. func NewLockedOrdered[M cmp.Ordered]() *LockedOrdered[M] { - set := &LockedOrdered[M]{set: NewOrdered[M]()} - set.Cond = sync.NewCond(&set.RWMutex) - return set + return &LockedOrdered[M]{set: NewOrdered[M]()} } -// NewLockedOrderedFrom returns a new OrderedSet[M] instance filled with the values from the sequence. The set is safe +// NewLockedOrderedFrom returns a new *LockedOrdered[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]) *LockedOrdered[M] { s := NewLockedOrdered[M]() @@ -35,15 +32,15 @@ func NewLockedOrderedFrom[M cmp.Ordered](seq iter.Seq[M]) *LockedOrdered[M] { return s } -// NewOrderedWith the values provides. Duplicates are removed. +// NewLockedOrderedWith returns a *LockedOrdered[M] with the values provided. 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. +// NewLockedOrderedWrapping returns an OrderedSet[M]. If the set is already a locked set, then it is just returned as +// is. If the 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 _, ok := set.(locker); ok { + if _, ok := set.(Locker); ok { return set } lset := NewLockedOrdered[M]() @@ -51,119 +48,105 @@ func NewLockedOrderedWrapping[M cmp.Ordered](set OrderedSet[M]) OrderedSet[M] { return lset } +// Contains returns true if the set contains the element. func (s *LockedOrdered[M]) Contains(m M) bool { s.RLock() defer s.RUnlock() return s.set.Contains(m) } +// Clear the set and returns the number of elements removed. func (s *LockedOrdered[M]) Clear() int { - s.L.Lock() - if s.iterating { - s.Wait() - } - defer s.L.Unlock() + s.Lock() + defer s.Unlock() return s.set.Clear() } +// Add an element to the set. Returns true if the element was added, false if it was already present. func (s *LockedOrdered[M]) Add(m M) bool { - s.L.Lock() - if s.iterating { - s.Wait() - } - defer s.L.Unlock() + s.Lock() + defer s.Unlock() return s.set.Add(m) } +// Remove an element from the set. Returns true if the element was removed, false if it was not present. func (s *LockedOrdered[M]) Remove(m M) bool { - s.L.Lock() - if s.iterating { - s.Wait() - } - defer s.L.Unlock() + s.Lock() + defer s.Unlock() return s.set.Remove(m) } +// Cardinality returns the number of elements in the set. func (s *LockedOrdered[M]) Cardinality() int { - if s == nil { - return 0 - } s.RLock() defer s.RUnlock() + return s.set.Cardinality() } -// 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. +// Iterator yields all elements in the set in order. It holds a read lock for the duration of iteration. Calling any +// method that modifies the set while iteration is happening will block until the iteration is complete. func (s *LockedOrdered[M]) Iterator(yield func(M) bool) { - s.L.Lock() - s.iterating = true - defer func() { - s.iterating = false - s.Broadcast() - s.L.Unlock() - }() + s.RLock() + defer s.RUnlock() s.set.Iterator(yield) } +// Clone returns a new set of the same underlying type. func (s *LockedOrdered[M]) Clone() Set[M] { + s.RLock() + defer s.RUnlock() return NewLockedOrderedFrom(s.Iterator) } -// Ordered iteration yields the index and value of each element in the set in order. +// Ordered iteration yields the index and value of each element in the set in order. It holds a read lock for the +// duration of iteration. Calling any method that modifies the set while iteration is happening will block until the +// iteration is complete. func (s *LockedOrdered[M]) Ordered(yield func(int, M) bool) { - s.L.Lock() - s.iterating = true - defer func() { - s.iterating = false - s.Broadcast() - s.L.Unlock() - }() + s.RLock() + defer s.RUnlock() s.set.Ordered(yield) } +// Backwards iteration yields the index and value of each element in the set in reverse order. It holds a read lock for +// the duration of iteration. Calling any method that modifies the set while iteration is happening will block until the +// iteration is complete. func (s *LockedOrdered[M]) Backwards(yield func(int, M) bool) { - s.L.Lock() - s.iterating = true - defer func() { - s.iterating = false - s.Broadcast() - s.L.Unlock() - }() + s.RLock() + defer s.RUnlock() s.set.Backwards(yield) } +// NewEmptyOrdered returns a new empty ordered set of the same underlying type. func (s *LockedOrdered[M]) NewEmptyOrdered() OrderedSet[M] { return NewLockedOrdered[M]() } +// NewEmpty returns a new empty set of the same underlying type. func (s *LockedOrdered[M]) NewEmpty() Set[M] { return NewLockedOrdered[M]() } +// Pop removes and returns an element from the set. If the set is empty, it returns the zero value of M and false. func (s *LockedOrdered[M]) Pop() (M, bool) { - s.L.Lock() - if s.iterating { - s.Wait() - } - defer s.L.Unlock() + s.Lock() + defer s.Unlock() return s.set.Pop() } +// Sort the set in ascending order. func (s *LockedOrdered[M]) Sort() { - s.L.Lock() - if s.iterating { - s.Wait() - } - defer s.L.Unlock() + s.Lock() + defer s.Unlock() s.set.Sort() } +// At returns the element at the index. If the index is out of bounds, the second return value is false. func (s *LockedOrdered[M]) At(i int) (M, bool) { s.RLock() defer s.RUnlock() @@ -171,6 +154,7 @@ func (s *LockedOrdered[M]) At(i int) (M, bool) { return s.set.At(i) } +// Index returns the index of the element in the set, or -1 if not present. func (s *LockedOrdered[M]) Index(m M) int { s.RLock() defer s.RUnlock() @@ -178,6 +162,7 @@ func (s *LockedOrdered[M]) Index(m M) int { return s.set.Index(m) } +// String returns a string representation of the set. It returns a string of the form LockedOrderedSet[T](). func (s *LockedOrdered[M]) String() string { s.RLock() defer s.RUnlock() @@ -185,6 +170,8 @@ func (s *LockedOrdered[M]) String() string { return "Locked" + s.set.String() } +// MarshalJSON implements json.Marshaler. It will marshal the set to JSON. It returns a JSON array of the elements in +// the set. If the set is empty, it returns an empty JSON array. func (s *LockedOrdered[M]) MarshalJSON() ([]byte, error) { s.RLock() defer s.RUnlock() @@ -202,15 +189,10 @@ func (s *LockedOrdered[M]) MarshalJSON() ([]byte, error) { return d, nil } -// UnmarshalJSON implements json.Unmarshaler. It will unmarshal the JSON data into the set. +// UnmarshalJSON implements json.Unmarshaler. It expects a JSON array of the elements in the set. If the set is empty, +// it returns an empty set. If the JSON is invalid, it returns an error. 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.Unlock() if s.set == nil { diff --git a/locker.go b/locker.go index 3a00ba0..b285357 100644 --- a/locker.go +++ b/locker.go @@ -1,10 +1,9 @@ package sets -type locker interface { +// Locker interface used to determine if a locked implementation is being used. +type Locker interface { Lock() Unlock() RLock() RUnlock() - Wait() - Broadcast() } diff --git a/map.go b/map.go index 27061e1..68d7d0c 100644 --- a/map.go +++ b/map.go @@ -8,38 +8,42 @@ import ( "slices" ) +// Map is the default set implementation based on top of go's map type. It is not ordered and does not guarantee +// the order of elements when iterating over them. It is not safe for concurrent use. type Map[M comparable] struct { set map[M]struct{} } var _ Set[int] = new(Map[int]) -// NewMap returns an empty Set[M] instance. -func NewMap[M comparable]() *Map[M] { +// New returns an empty *Map[M] instance. +func New[M comparable]() *Map[M] { return &Map[M]{ set: make(map[M]struct{}), } } -// 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]() +// NewFrom returns a new *Map[M] filled with the values from the sequence. +func NewFrom[M comparable](seq iter.Seq[M]) *Map[M] { + s := New[M]() for x := range seq { s.Add(x) } return s } -// NewMapWith the values provides. Duplicates are removed. -func NewMapWith[M comparable](m ...M) *Map[M] { - return NewMapFrom(slices.Values(m)) +// NewWith returns a new *Map[M] with the values provided. +func NewWith[M comparable](m ...M) *Map[M] { + return NewFrom(slices.Values(m)) } +// Contains returns true if the set contains the element. func (s *Map[M]) Contains(m M) bool { _, ok := s.set[m] return ok } +// Clear the set and returns the number of elements removed. func (s *Map[M]) Clear() int { n := len(s.set) for k := range s.set { @@ -48,6 +52,7 @@ func (s *Map[M]) Clear() int { return n } +// Add an element to the set. Returns true if the element was added, false if it was already present. func (s *Map[M]) Add(m M) bool { if s.Contains(m) { return false @@ -56,6 +61,7 @@ func (s *Map[M]) Add(m M) bool { return true } +// Remove an element from the set. Returns true if the element was removed, false if it was not present. func (s *Map[M]) Remove(m M) bool { if !s.Contains(m) { return false @@ -64,6 +70,7 @@ func (s *Map[M]) Remove(m M) bool { return true } +// Cardinality returns the number of elements in the set. func (s *Map[M]) Cardinality() int { return len(s.set) } @@ -77,14 +84,17 @@ func (s *Map[M]) Iterator(yield func(M) bool) { } } +// Clones the set. Returns a new set of the same underlying type. func (s *Map[M]) Clone() Set[M] { - return NewMapFrom(s.Iterator) + return NewFrom(s.Iterator) } +// NewEmpty set of the same underlying type. func (s *Map[M]) NewEmpty() Set[M] { - return NewMap[M]() + return New[M]() } +// Pop removes and returns an element from the set. If the set is empty, it returns the zero value of M and false. func (s *Map[M]) Pop() (M, bool) { for k := range s.set { delete(s.set, k) @@ -94,11 +104,14 @@ func (s *Map[M]) Pop() (M, bool) { return m, false } +// String representation of the set. It returns a string of the form Set[T](). func (s *Map[M]) String() string { var m M return fmt.Sprintf("Set[%T](%v)", m, slices.Collect(maps.Keys(s.set))) } +// MarshalJSON marshals the set to JSON. It returns a JSON array of the elements in the set. If the set is empty, it +// returns an empty JSON array. func (s *Map[M]) MarshalJSON() ([]byte, error) { v := slices.Collect(s.Iterator) if len(v) == 0 { @@ -112,6 +125,8 @@ func (s *Map[M]) MarshalJSON() ([]byte, error) { return d, nil } +// UnmarshalJSON unmarshals the set from JSON. It expects a JSON array of the elements in the set. If the set is empty, +// it returns an empty set. If the JSON is invalid, it returns an error. func (s *Map[M]) UnmarshalJSON(d []byte) error { var um []M if err := json.Unmarshal(d, &um); err != nil { diff --git a/ordered.go b/ordered.go index 728fb30..3df1d97 100644 --- a/ordered.go +++ b/ordered.go @@ -8,6 +8,7 @@ import ( "slices" ) +// Ordered sets maintains the order that the elements were added in. type Ordered[M cmp.Ordered] struct { idx map[M]int values []M @@ -15,7 +16,7 @@ type Ordered[M cmp.Ordered] struct { var _ OrderedSet[int] = new(Ordered[int]) -// NewOrdered returns an empty OrderedSet[M]. +// NewOrdered returns an empty *Ordered[M]. func NewOrdered[M cmp.Ordered]() *Ordered[M] { return &Ordered[M]{ idx: make(map[M]int), @@ -23,7 +24,7 @@ func NewOrdered[M cmp.Ordered]() *Ordered[M] { } } -// NewOrderedFrom returns a new OrderedSet[M] filled with the values from the sequence. +// NewOrderedFrom returns a new *Ordered[M] filled with the values from the sequence. func NewOrderedFrom[M cmp.Ordered](seq iter.Seq[M]) *Ordered[M] { s := NewOrdered[M]() for x := range seq { @@ -32,16 +33,18 @@ func NewOrderedFrom[M cmp.Ordered](seq iter.Seq[M]) *Ordered[M] { return s } -// NewOrderedWith the values provides. Duplicates are removed. +// NewOrderedWith returns a new *Ordered[M] with the values provided. func NewOrderedWith[M cmp.Ordered](m ...M) *Ordered[M] { return NewOrderedFrom(slices.Values(m)) } +// Contains returns true if the set contains the element. func (s *Ordered[M]) Contains(m M) bool { _, ok := s.idx[m] return ok } +// Clear the set and returns the number of elements removed. func (s *Ordered[M]) Clear() int { n := len(s.values) for k := range s.idx { @@ -51,6 +54,8 @@ func (s *Ordered[M]) Clear() int { return n } +// Add an element to the set. Returns true if the element was added, false if it was already present. Elements are added +// to the end of the ordered set. func (s *Ordered[M]) Add(m M) bool { if s.Contains(m) { return false @@ -60,6 +65,7 @@ func (s *Ordered[M]) Add(m M) bool { return true } +// Remove an element from the set. Returns true if the element was removed, false if it was not present. func (s *Ordered[M]) Remove(m M) bool { if !s.Contains(m) { return false @@ -73,6 +79,7 @@ func (s *Ordered[M]) Remove(m M) bool { return true } +// Cardinality returns the number of elements in the set. func (s *Ordered[M]) Cardinality() int { if s == nil { return 0 @@ -89,6 +96,7 @@ func (s *Ordered[M]) Iterator(yield func(M) bool) { } } +// Clone returns a copy of the set. The underlying type is the same as the original set. func (s *Ordered[M]) Clone() Set[M] { return NewOrderedFrom(s.Iterator) } @@ -102,6 +110,7 @@ func (s *Ordered[M]) Ordered(yield func(int, M) bool) { } } +// Backwards iteration yields the index and value of each element in the set in reverse order. 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]) { @@ -110,14 +119,17 @@ func (s *Ordered[M]) Backwards(yield func(int, M) bool) { } } +// NewEmptyOrdered returns a new empty ordered set of the same underlying type. func (s *Ordered[M]) NewEmptyOrdered() OrderedSet[M] { return NewOrdered[M]() } +// NewEmpty returns a new empty set of the same underlying type. func (s *Ordered[M]) NewEmpty() Set[M] { return NewOrdered[M]() } +// Pop removes and returns an element from the set. If the set is empty, it returns the zero value of M and false. func (s *Ordered[M]) Pop() (M, bool) { for k := range s.idx { s.Remove(k) @@ -127,6 +139,7 @@ func (s *Ordered[M]) Pop() (M, bool) { return m, false } +// Sort the set in ascending order. func (s *Ordered[M]) Sort() { slices.Sort(s.values) for i, v := range s.values { @@ -134,6 +147,7 @@ func (s *Ordered[M]) Sort() { } } +// At returns the element at the index. If the index is out of bounds, the second return value is false. func (s *Ordered[M]) At(i int) (M, bool) { var zero M if i < 0 || i >= len(s.values) { @@ -142,6 +156,7 @@ func (s *Ordered[M]) At(i int) (M, bool) { return s.values[i], true } +// Index returns the index of the element in the set, or -1 if not present. func (s *Ordered[M]) Index(m M) int { i, ok := s.idx[m] if !ok { @@ -150,11 +165,14 @@ func (s *Ordered[M]) Index(m M) int { return i } +// String returns a string representation of the set. It returns a string of the form OrderedSet[T](). func (s *Ordered[M]) String() string { var m M return fmt.Sprintf("OrderedSet[%T](%v)", m, s.values) } +// MarshalJSON implements json.Marshaler. It will marshal the set into a JSON array of the elements in the set. If the +// set is empty an empty JSON array is returned. func (s *Ordered[M]) MarshalJSON() ([]byte, error) { if len(s.values) == 0 { return []byte("[]"), nil @@ -167,6 +185,8 @@ func (s *Ordered[M]) MarshalJSON() ([]byte, error) { return d, nil } +// UnmarshalJSON implements json.Unmarshaler. It expects a JSON array of the elements in the set. If the set is empty, +// it returns an empty set. If the JSON is invalid, it returns an error. func (s *Ordered[M]) UnmarshalJSON(d []byte) error { s.Clear() if s.values == nil { diff --git a/set.go b/set.go index 1556690..ba09faa 100644 --- a/set.go +++ b/set.go @@ -248,7 +248,7 @@ func IsEmpty[K comparable](s Set[K]) bool { // 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]() + m := New[V]() for k := range s.Iterator { m.Add(f(k)) } diff --git a/set_test.go b/set_test.go index 4df4e16..00637bd 100644 --- a/set_test.go +++ b/set_test.go @@ -28,7 +28,7 @@ func TestMap(t *testing.T) { t.Parallel() setStateMachine := &SetStateMachine{ - set: NewMap[int](), + set: New[int](), stateI: make(map[int]int), } rapid.Check(t, func(t *rapid.T) { @@ -433,7 +433,7 @@ func testSetConcurrency(t *testing.T, set Set[int]) { finished.Done() }, func(base int) { - other := NewMap[int]() + other := New[int]() for i := range (base + 1) * 100 { other.Add(i) } @@ -577,7 +577,7 @@ func TestChunk_Ordered(t *testing.T) { func TestChunk(t *testing.T) { t.Parallel() - s := NewMap[int]() + s := New[int]() for i := range 22 { s.Add(i) } @@ -694,7 +694,7 @@ func TestLocked_JSON(t *testing.T) { func TestMap_JSON(t *testing.T) { t.Parallel() - set := NewMap[Foo]() + set := New[Foo]() set.Add(&foo{}) set.Add(&bar{}) d, err := json.Marshal(set) @@ -707,7 +707,7 @@ func TestMap_JSON(t *testing.T) { t.Fatalf("expected error: %v", err) } - set2 := NewMap[foo]() + set2 := New[foo]() set2.Add(foo{Baz: "bar"}) set2.Add(foo{Baz: "foo"}) @@ -720,7 +720,7 @@ func TestMap_JSON(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - set3 := NewMap[*foo]() + set3 := New[*foo]() set3.Add(&foo{Baz: "bar"}) set3.Add(&foo{Baz: "foo"}) d, err = json.Marshal(set3) @@ -732,7 +732,7 @@ func TestMap_JSON(t *testing.T) { t.Fatalf("unexpected error: %v", err) } - set4 := NewMap[chan foo]() + set4 := New[chan foo]() set4.Add(make(chan foo)) set4.Add(make(chan foo)) // see comparison rules for channels @@ -749,7 +749,7 @@ func TestMap_JSON(t *testing.T) { Set *Map[int] } - b := Bar{Set: NewMap[int]()} + b := Bar{Set: New[int]()} b.Set.Add(1) b.Set.Add(2) diff --git a/sync.go b/sync.go index a8d10f0..5051b41 100644 --- a/sync.go +++ b/sync.go @@ -8,6 +8,7 @@ import ( "sync" ) +// SyncMap is a concurrency safe set type that uses a sync.Map. type SyncMap[M comparable] struct { m sync.Map }