From 6bcb8cbd9273f8a2af2d1e816b401f78056c9d70 Mon Sep 17 00:00:00 2001 From: Edward Muller Date: Thu, 20 Mar 2025 20:51:13 -0700 Subject: [PATCH] New helpers and small cleanups * Additional helpers: * Map - maps the items in the set and returns them as a new set * MapTo - maps the items in the set into a provided set * MapToSlice - maps the items in the set and returns them as a slice * Filter - filters the items in the set based on the function and returns a new set * Reduce - reduces the items in the set to a single value * ReduceRight - reduces the items in the ordered set, in backwards order, to a single value * ForEach - calls the provided function with each member of the set * ForEachRight - calls the provided function for each item in the ordered set, in backwards order. * Cleaned up some Examples so they now use New{Ordered,}With instead of New{Ordered,} and multiple Adds #minor --- CHANGELOG.MD | 17 +++ README.MD | 8 ++ examples_test.go | 280 ++++++++++++++++++++++++++--------------------- ordered_set.go | 17 +++ set.go | 66 +++++++++++ 5 files changed, 265 insertions(+), 123 deletions(-) diff --git a/CHANGELOG.MD b/CHANGELOG.MD index 54c05c3..8b2c327 100644 --- a/CHANGELOG.MD +++ b/CHANGELOG.MD @@ -2,6 +2,23 @@ ## Unreleased +## v0.8.0 + +* Additional helpers: + * Map - maps the items in the set and returns them as a new set + * MapTo - maps the items in the set into a provided set + * MapToSlice - maps the items in the set and returns them as a slice + * Filter - filters the items in the set based on the function and returns a new set + * Reduce - reduces the items in the set to a single value + * ReduceRight - reduces the items in the ordered set, in backwards order, to a single value + * ForEach - calls the provided function with each member of the set + * ForEachRight - calls the provided function for each item in the ordered set, in backwards order. +* Cleaned up some Examples so they now use New{Ordered,}With instead of New{Ordered,} and multiple Adds + +## v0.7.1 + +* cmp.Diff examples + ## v0.7.0 * Added `set.Pop() (M, bool)`, which returns and removes a random element from the set. diff --git a/README.MD b/README.MD index c1c3751..4893c36 100644 --- a/README.MD +++ b/README.MD @@ -71,6 +71,12 @@ 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.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. +* `sets.Reduce(aSet, X, func(X, K) X { return ... }) X` : Reduces the set to a single value. +* `sets.ForEach(aSet, func(v V))` : calls the provided function with each set member. ## OrderedSet Helpers @@ -80,6 +86,8 @@ These helpers work on all OrderedSet types. * `sets.IsSorted(aOrderedSet)` : Returns true if the OrderedSet is sorted in ascending order. * `sets.Reverse(aOrderedSet)` : Returns a new OrderedSet with the elements in the reverse order of the original OrderedSet. * `sets.Sorted(aOrderedSet)` : Return a copy of aOrderedSet with the elements sorted in ascending order. Does not modify the original set. +* `sets.ReduceRight(aSet, X, func(X, K) X { return ... }) X` : Reduces the set to a single value in reverse order. +* `sets.ForEachRight(aSet, func(K) { ... })` : calls the provided function with each set member in reverse order. ## Custom Set Types diff --git a/examples_test.go b/examples_test.go index 972afe5..dd5c022 100644 --- a/examples_test.go +++ b/examples_test.go @@ -150,10 +150,7 @@ func ExampleOrderedSet() { } func ExampleElements() { - ints := New[int]() - ints.Add(5) - ints.Add(3) - ints.Add(2) + ints := NewWith(5, 3, 2) // []T is returned elements := Elements(ints) @@ -167,9 +164,8 @@ func ExampleElements() { } func ExampleAppendSeq() { - ints := New[int]() - ints.Add(5) - ints.Add(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})) fmt.Println(added) @@ -177,10 +173,8 @@ func ExampleAppendSeq() { } func ExampleRemoveSeq() { - ints := New[int]() - ints.Add(5) - ints.Add(3) - ints.Add(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})) fmt.Println(removed) @@ -188,13 +182,8 @@ func ExampleRemoveSeq() { } func ExampleUnion() { - a := New[int]() - a.Add(5) - a.Add(3) - - b := New[int]() - b.Add(3) - b.Add(2) + a := NewWith(5, 3) + b := NewWith(3, 2) c := Union(a, b) out := make([]int, 0, c.Cardinality()) @@ -212,13 +201,8 @@ func ExampleUnion() { } func ExampleIntersection() { - a := New[int]() - a.Add(5) - a.Add(3) - - b := New[int]() - b.Add(3) - b.Add(2) + a := NewWith(5, 3) + b := NewWith(3, 2) c := Intersection(a, b) out := make([]int, 0, c.Cardinality()) @@ -233,13 +217,8 @@ func ExampleIntersection() { } func ExampleDifference() { - a := New[int]() - a.Add(5) - a.Add(3) - - b := New[int]() - b.Add(3) - b.Add(2) + a := NewWith(5, 3) + b := NewWith(3, 2) c := Difference(a, b) out := make([]int, 0, c.Cardinality()) @@ -254,13 +233,8 @@ func ExampleDifference() { } func ExampleSymmetricDifference() { - a := New[int]() - a.Add(5) - a.Add(3) - - b := New[int]() - b.Add(3) - b.Add(2) + a := NewWith(5, 3) + b := NewWith(3, 2) c := SymmetricDifference(a, b) for i := range c.Iterator { @@ -272,14 +246,8 @@ func ExampleSymmetricDifference() { } func ExampleSubset() { - a := New[int]() - a.Add(5) - a.Add(3) - - b := New[int]() - b.Add(5) - b.Add(3) - b.Add(2) + a := NewWith(5, 3) + b := NewWith(5, 3, 2) if Subset(a, b) { fmt.Println("a is a subset of b") @@ -294,14 +262,8 @@ func ExampleSubset() { } func ExampleSuperset() { - a := New[int]() - a.Add(5) - a.Add(3) - - b := New[int]() - b.Add(5) - b.Add(3) - b.Add(2) + a := NewWith(5, 3) + b := NewWith(5, 3, 2) if !Superset(a, b) { fmt.Println("a is not a superset of b") @@ -316,13 +278,8 @@ func ExampleSuperset() { } func ExampleEqual() { - a := New[int]() - a.Add(5) - a.Add(3) - - b := New[int]() - b.Add(5) - b.Add(3) + a := NewWith(5, 3) + b := NewWith(5, 3) if Equal(a, b) { fmt.Println("a and b are equal") @@ -369,13 +326,8 @@ func ExampleContainsSeq() { } func ExampleDisjoint() { - a := New[int]() - a.Add(5) - a.Add(3) - - b := New[int]() - b.Add(2) - b.Add(4) + a := NewWith(5, 3) + b := NewWith(2, 4) if Disjoint(a, b) { fmt.Println("a and b are disjoint") @@ -391,15 +343,8 @@ func ExampleDisjoint() { } func ExampleEqualOrdered() { - a := NewOrdered[int]() - a.Add(5) - a.Add(3) - a.Add(1) - - b := NewOrdered[int]() - b.Add(5) - b.Add(3) - b.Add(1) + a := NewOrderedWith(5, 3, 1) + b := NewOrderedWith(5, 3, 1) if EqualOrdered(a, b) { fmt.Println("a and b are equal") @@ -423,10 +368,7 @@ func ExampleEqualOrdered() { } func ExampleMin() { - ints := New[int]() - ints.Add(3) - ints.Add(2) - ints.Add(5) + ints := NewWith(3, 2, 5) min := Min(ints) fmt.Println(min) @@ -434,10 +376,7 @@ func ExampleMin() { } func ExampleMax() { - ints := New[int]() - ints.Add(3) - ints.Add(5) - ints.Add(2) + ints := NewWith(3, 5, 2) max := Max(ints) fmt.Println(max) @@ -445,10 +384,7 @@ func ExampleMax() { } func ExampleIsSorted() { - ints := NewOrdered[int]() - ints.Add(2) - ints.Add(3) - ints.Add(5) + ints := NewOrderedWith(2, 3, 5) if IsSorted(ints) { fmt.Println("ints is sorted") @@ -470,10 +406,7 @@ func ExampleIsSorted() { } func ExampleReverse() { - ints := NewOrdered[int]() - ints.Add(2) - ints.Add(3) - ints.Add(5) + ints := NewOrderedWith(2, 3, 5) reversed := Reverse(ints) for i := range reversed.Iterator { @@ -486,10 +419,7 @@ func ExampleReverse() { } func ExampleSorted() { - ints := NewOrdered[int]() - ints.Add(2) - ints.Add(5) - ints.Add(3) + ints := NewOrderedWith(2, 5, 3) sorted := Sorted(ints) for i := range sorted.Iterator { @@ -502,8 +432,7 @@ func ExampleSorted() { } func ExampleChunk() { - ints := NewOrdered[int]() - AppendSeq(ints, slices.Values([]int{1, 2, 3, 4, 5})) + ints := NewOrderedWith(1, 2, 3, 4, 5) // this example test won't work with an unordered set // as the order of the chunks is based on the order of @@ -527,8 +456,7 @@ func ExampleChunk() { } func ExampleIter2() { - ints := NewOrdered[int]() - AppendSeq(ints, slices.Values([]int{1, 2, 3, 4, 5})) + ints := NewOrderedWith(1, 2, 3, 4, 5) // this example test won't work with an unordered set // as the iter2 function relies on the order of the set @@ -546,7 +474,7 @@ func ExampleIter2() { } func Example_json() { - set := NewOrderedFrom(slices.Values([]float32{1.0, 1.2, 1.3, 1.4, 1.5})) + set := NewOrderedWith(1.0, 1.2, 1.3, 1.4, 1.5) b, err := json.Marshal(set) if err != nil { fmt.Println(err) @@ -565,24 +493,21 @@ func Example_json() { } func ExampleNewWith() { - m := []string{"a", "b", "c", "b"} - set := NewWith(m...) + set := NewWith("a", "b", "c", "b") fmt.Println(set.Cardinality()) // Output: 3 } func ExampleNewLockedWith() { - m := []string{"a", "b", "c", "b"} - set := NewLockedWith(m...) + set := NewLockedWith("a", "b", "c", "b") fmt.Println(set.Cardinality()) // Output: 3 } func ExampleNewOrderedWith() { - m := []string{"a", "b", "c", "b"} - set := NewOrderedWith(m...) + set := NewOrderedWith("a", "b", "c", "b") fmt.Println(set.Cardinality()) for i := range set.Iterator { @@ -597,8 +522,7 @@ func ExampleNewOrderedWith() { } func ExampleNewLockedOrderedWith() { - m := []string{"a", "b", "c", "b"} - set := NewLockedOrderedWith(m...) + set := NewLockedOrderedWith("a", "b", "c", "b") fmt.Println(set.Cardinality()) for i := range set.Iterator { @@ -613,8 +537,7 @@ func ExampleNewLockedOrderedWith() { } func ExampleNewSyncWith() { - m := []string{"a", "b", "c", "b"} - set := NewSyncWith(m...) + set := NewSyncWith("a", "b", "c", "b") fmt.Println(set.Cardinality()) // Output: 3 @@ -748,11 +671,7 @@ func ExampleNewSyncFrom() { } func ExampleNewLockedWrapping() { - set := New[string]() - set.Add("a") - set.Add("b") - set.Add("c") - set.Add("b") + set := NewWith("a", "b", "c", "b") wrapped := NewLockedWrapping(set) // wrapped is safe for concurrent use @@ -762,11 +681,7 @@ func ExampleNewLockedWrapping() { } func ExampleNewLockedOrderedWrapping() { - set := NewOrdered[string]() - set.Add("a") - set.Add("b") - set.Add("c") - set.Add("b") + set := NewOrderedWith("a", "b", "c", "b") wrapped := NewLockedOrderedWrapping(set) // wrapped is safe for concurrent use @@ -789,3 +704,122 @@ func ExampleIsEmpty() { // set is empty // set is not empty } + +func ExampleMap() { + set := NewWith(1, 2, 3) + + mapped := Map(set, func(i int) int { + return i * 2 + }) + for i := range mapped.Iterator { + fmt.Println(i) + } + + mapped2 := Map(set, func(i int) string { + return fmt.Sprintf("%d", i) + }) + for i := range mapped2.Iterator { + fmt.Println(i) + } + // Unordered output: + // 2 + // 4 + // 6 + // 1 + // 2 + // 3 +} + +func ExampleMapTo() { + set := NewOrderedWith(3, 1, 2) + + dest := New[string]() + MapTo(set, dest, func(i int) string { + return fmt.Sprintf("%d=%d*2", i*2, i) + }) + for i := range dest.Iterator { + fmt.Println(i) + } + // Unordered output: + // 6=3*2 + // 2=1*2 + // 4=2*2 +} + +func ExampleMapToSlice() { + set := NewWith(3, 1, 2) + + mapped := MapToSlice(set, func(i int) string { + return fmt.Sprintf("%d=%d*2", i*2, i) + }) + for _, i := range mapped { + fmt.Println(i) + } + // Unordered output: + // 6=3*2 + // 2=1*2 + // 4=2*2 +} + +func ExampleFilter() { + set := NewWith(3, 0, 1, 2, 4) + + filtered := Filter(set, func(i int) bool { + return i > 2 + }) + for i := range filtered.Iterator { + fmt.Println(i) + } + // Unordered output: + // 3 + // 4 +} + +func ExampleReduce() { + set := NewWith(3, 1, 2) + + sum := Reduce(set, 0, func(agg, v int) int { + return agg + v + }) + fmt.Println(sum) + // Output: 6 +} + +func ExampleReduceRight() { + set := NewOrderedWith(3, 1, 2) + + sum := ReduceRight(set, 0, func(agg, v int) int { + fmt.Println(v) + return agg + v + }) + fmt.Println(sum) + // Output: + // 2 + // 1 + // 3 + // 6 +} + +func ExampleForEach() { + set := NewWith(3, 1, 2) + + ForEach(set, func(i int) { + fmt.Println(i) + }) + // Unordered output: + // 1 + // 2 + // 3 +} + +func ExampleForEachRight() { + set := NewOrderedWith(3, 1, 2) + + ForEachRight(set, func(i int) { + fmt.Println(i) + }) + // Output: + // 2 + // 1 + // 3 +} diff --git a/ordered_set.go b/ordered_set.go index c80fb0c..357c772 100644 --- a/ordered_set.go +++ b/ordered_set.go @@ -81,3 +81,20 @@ func Sorted[K cmp.Ordered](s OrderedSet[K]) OrderedSet[K] { out.Sort() return out } + +// ReduceRight reduces the set from right to left using the given function. "initial" is the initial value of the +// accumulator. The function is called with the accumulator and the element backwards. The result of the function is the +// new accumulator value. The final accumulator value is returned. +func ReduceRight[K cmp.Ordered, O any](s OrderedSet[K], initial O, fn func(agg O, k K) O) O { + out := initial + for _, k := range s.Backwards { + out = fn(out, k) + } + return out +} + +func ForEachRight[K cmp.Ordered](s OrderedSet[K], fn func(k K)) { + for _, k := range s.Backwards { + fn(k) + } +} diff --git a/set.go b/set.go index 01fb632..5664d8b 100644 --- a/set.go +++ b/set.go @@ -245,3 +245,69 @@ func Chunk[K comparable](s Set[K], n int) iter.Seq[Set[K]] { 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]() + for k := range s.Iterator { + m.Add(f(k)) + } + return m +} + +// MapTo applies the function to each element in the set and adds the results to the destination set. +func MapTo[K comparable, V comparable](s Set[K], d Set[V], f func(K) V) { + for k := range s.Iterator { + d.Add(f(k)) + } +} + +// MapToSlice applies the function to each element in the set and returns a slice with the results. +func MapToSlice[K comparable, V any](s Set[K], f func(K) V) []V { + o := make([]V, 0, s.Cardinality()) + for v := range s.Iterator { + o = append(o, f(v)) + } + return o +} + +// Filter applies the function to each element in the set and returns a new set with the elements for which the function +// returns true. +func Filter[K comparable](s Set[K], f func(K) bool) Set[K] { + m := s.NewEmpty() + for k := range s.Iterator { + if f(k) { + m.Add(k) + } + } + return m +} + +// FilterTo applies the function to each element in the set and adds the elements for which the function returns true to +// the destination set. +func FilterTo[K comparable](s Set[K], d Set[K], f func(K) bool) { + s.Iterator(func(k K) bool { + if f(k) { + d.Add(k) + } + return true + }) +} + +// Reduce applies the function to each element in the set and returns the accumulated value. "initial" is the initial +// value of the accumulator. The function is called with the accumulator and each element in turn. The result of the +// function is the new accumulator value. The final accumulator value is returned. +func Reduce[K comparable, O any](s Set[K], initial O, f func(agg O, k K) O) O { + v := initial + for k := range s.Iterator { + v = f(v, k) + } + return v +} + +// ForEach calls the function with each element in the set. +func ForEach[K comparable](s Set[K], f func(K)) { + for k := range s.Iterator { + f(k) + } +}