Skip to content

Commit 0d97c86

Browse files
committed
[collection] Added execute a function on each element of a function
1 parent 87d5e0a commit 0d97c86

File tree

7 files changed

+294
-42
lines changed

7 files changed

+294
-42
lines changed

changes/20251117162958.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:sparkles: `[collection]` Added execute a function on each element of a function

changes/20251117163045.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:zap: `[collection]` Support `iter.Seq` for most operations on slices (map, filter) to increase performance

changes/20251117163100.feature

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
:sparkles: `[collection]` Added set operations on slices

utils/collection/conditions.go

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package collection
22

33
import (
4+
"iter"
5+
"slices"
6+
47
"github.com/ARM-software/golang-utils/utils/commonerrors"
58
)
69

@@ -120,11 +123,16 @@ func (c *Conditions) OneHot() bool {
120123

121124
// Any returns true if there is at least one element of the slice which is true.
122125
func Any(slice []bool) bool {
123-
if len(slice) == 0 {
126+
return AnySequence(slices.Values(slice))
127+
}
128+
129+
// AnySequence returns true if there is at least one element of the slice which is true.
130+
func AnySequence(seq iter.Seq[bool]) bool {
131+
if seq == nil {
124132
return false
125133
}
126-
for i := range slice {
127-
if slice[i] {
134+
for e := range seq {
135+
if e {
128136
return true
129137
}
130138
}
@@ -136,17 +144,31 @@ func AnyTrue(values ...bool) bool {
136144
return Any(values)
137145
}
138146

139-
// All returns true if all items of the slice are true.
140-
func All(slice []bool) bool {
141-
if len(slice) == 0 {
142-
return false
143-
}
144-
for i := range slice {
145-
if !slice[i] {
146-
return false
147+
// AnyFalseSequence returns true if there is at least one element of the sequence which is false.
148+
func AnyFalseSequence(eq iter.Seq[bool]) bool {
149+
hasElements := false
150+
for e := range eq {
151+
hasElements = true
152+
if !e {
153+
return true
147154
}
148155
}
149-
return true
156+
return !hasElements
157+
}
158+
159+
// AnyFalse returns whether there is a value set to false
160+
func AnyFalse(values ...bool) bool {
161+
return AnyFalseSequence(slices.Values(values))
162+
}
163+
164+
// AllSequence returns true if all items of the sequence are true.
165+
func AllSequence(seq iter.Seq[bool]) bool {
166+
return !AnyFalseSequence(seq)
167+
}
168+
169+
// All returns true if all items of the slice are true.
170+
func All(slice []bool) bool {
171+
return AllSequence(slices.Values(slice))
150172
}
151173

152174
// AllTrue returns whether all values are true.

utils/collection/conditions_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,14 @@ func TestAnyTrue(t *testing.T) {
2525
assert.True(t, AnyTrue(true, true, true, true, true, true, true, true, true, false, true, true, true, true, true, true, true, true, true, true))
2626
}
2727

28+
func TestAnyFalse(t *testing.T) {
29+
assert.True(t, AnyFalse())
30+
assert.True(t, AnyFalse(false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false))
31+
assert.True(t, AnyFalse(false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false))
32+
assert.False(t, AnyFalse(true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true))
33+
assert.True(t, AnyFalse(true, true, true, true, true, true, true, true, true, false, true, true, true, true, true, true, true, true, true, true))
34+
}
35+
2836
func TestAll(t *testing.T) {
2937
assert.False(t, All([]bool{}))
3038
assert.False(t, All([]bool{false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false}))

utils/collection/search.go

Lines changed: 149 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,14 @@
55
package collection
66

77
import (
8+
"iter"
89
"slices"
910
"strings"
1011

12+
"github.com/ARM-software/golang-utils/utils/commonerrors"
13+
"github.com/ARM-software/golang-utils/utils/safecast"
1114
mapset "github.com/deckarep/golang-set/v2"
15+
"go.uber.org/atomic"
1216
)
1317

1418
// Find looks for an element in a slice. If found it will
@@ -20,6 +24,22 @@ func Find(slice *[]string, val string) (int, bool) {
2024
return FindInSlice(true, *slice, val)
2125
}
2226

27+
// FindInSequence searches a collection for an element satisfying the predicate.
28+
func FindInSequence[E any](elements iter.Seq[E], predicate Predicate[E]) (int, bool) {
29+
if elements == nil {
30+
return -1, false
31+
}
32+
idx := atomic.NewUint64(0)
33+
for e := range elements {
34+
if predicate(e) {
35+
return safecast.ToInt(idx.Load()), true
36+
}
37+
idx.Inc()
38+
}
39+
40+
return -1, false
41+
}
42+
2343
// FindInSlice finds if any values val are present in the slice and if so returns the first index.
2444
// if strict, it checks for an exact match; otherwise it discards whitespaces and case.
2545
func FindInSlice(strict bool, slice []string, val ...string) (int, bool) {
@@ -62,6 +82,46 @@ func UniqueEntries[T comparable](slice []T) []T {
6282
return subSet.ToSlice()
6383
}
6484

85+
// Unique returns all the unique values contained in a sequence.
86+
func Unique[T comparable](s iter.Seq[T]) []T {
87+
return UniqueEntries(slices.Collect(s))
88+
}
89+
90+
// Union returns the union of two slices (only unique values are returned).
91+
func Union[T comparable](slice1, slice2 []T) []T {
92+
subSet := mapset.NewSet[T]()
93+
_ = subSet.Append(slice1...)
94+
_ = subSet.Append(slice2...)
95+
return subSet.ToSlice()
96+
}
97+
98+
// Intersection returns the intersection of two slices (only unique values are returned).
99+
func Intersection[T comparable](slice1, slice2 []T) []T {
100+
subSet1 := mapset.NewSet[T]()
101+
subSet2 := mapset.NewSet[T]()
102+
_ = subSet1.Append(slice1...)
103+
_ = subSet2.Append(slice2...)
104+
return subSet1.Intersect(subSet2).ToSlice()
105+
}
106+
107+
// Difference returns the Difference between slice1 and slice2 (only unique values are returned).
108+
func Difference[T comparable](slice1, slice2 []T) []T {
109+
subSet1 := mapset.NewSet[T]()
110+
subSet2 := mapset.NewSet[T]()
111+
_ = subSet1.Append(slice1...)
112+
_ = subSet2.Append(slice2...)
113+
return subSet1.Difference(subSet2).ToSlice()
114+
}
115+
116+
// SymmetricDifference returns the symmetric difference between slice1 and slice2 (only unique values are returned).
117+
func SymmetricDifference[T comparable](slice1, slice2 []T) []T {
118+
subSet1 := mapset.NewSet[T]()
119+
subSet2 := mapset.NewSet[T]()
120+
_ = subSet1.Append(slice1...)
121+
_ = subSet2.Append(slice2...)
122+
return subSet1.SymmetricDifference(subSet2).ToSlice()
123+
}
124+
65125
// AnyFunc returns whether there is at least one element of slice s for which f() returns true.
66126
func AnyFunc[S ~[]E, E any](s S, f func(E) bool) bool {
67127
conditions := NewConditions(len(s))
@@ -73,17 +133,50 @@ func AnyFunc[S ~[]E, E any](s S, f func(E) bool) bool {
73133

74134
type FilterFunc[E any] func(E) bool
75135

136+
type Predicate[E any] = FilterFunc[E]
137+
76138
// Filter returns a new slice that contains elements from the input slice which return true when they’re passed as a parameter to the provided filtering function f.
77-
func Filter[S ~[]E, E any](s S, f FilterFunc[E]) (result S) {
78-
result = make(S, 0, len(s))
139+
func Filter[S ~[]E, E any](s S, f FilterFunc[E]) S {
140+
return slices.Collect[E](FilterSequence[E](slices.Values(s), f))
141+
}
79142

80-
for i := range s {
81-
if f(s[i]) {
82-
result = append(result, s[i])
143+
// FilterSequence returns a new sequence that contains elements from the input sequence which return true when they’re passed as a parameter to the provided filtering function f.
144+
func FilterSequence[E any](s iter.Seq[E], f Predicate[E]) (result iter.Seq[E]) {
145+
return func(yield func(E) bool) {
146+
for v := range s {
147+
if f(v) {
148+
if !yield(v) {
149+
return
150+
}
151+
}
83152
}
84153
}
154+
}
155+
156+
// ForEachValues iterates over values and executes the passed function on each of them.
157+
func ForEachValues[E any](f func(E), values ...E) {
158+
ForEach(values, f)
159+
}
160+
161+
// ForEach iterates over elements and executes the passed function on each element.
162+
func ForEach[S ~[]E, E any](s S, f func(E)) {
163+
_ = Each[E](slices.Values(s), func(e E) error {
164+
f(e)
165+
return nil
166+
})
167+
}
85168

86-
return result
169+
// Each iterates over a sequence and executes the passed function against each element.
170+
// If passed func returns an error, the iteration stops and the error is returned, unless it is EOF.
171+
func Each[T any](s iter.Seq[T], f func(T) error) error {
172+
for e := range s {
173+
err := f(e)
174+
if err != nil {
175+
err = commonerrors.Ignore(err, commonerrors.ErrEOF)
176+
return err
177+
}
178+
}
179+
return nil
87180
}
88181

89182
type MapFunc[T1, T2 any] func(T1) T2
@@ -95,15 +188,28 @@ func IdentityMapFunc[T any]() MapFunc[T, T] {
95188
}
96189
}
97190

98-
// Map creates a new slice and populates it with the results of calling the provided function on every element in input slice.
99-
func Map[T1 any, T2 any](s []T1, f MapFunc[T1, T2]) (result []T2) {
100-
result = make([]T2, len(s))
191+
// MapSequence creates a new sequences and populates it with the results of calling the provided function on every element of the input sequence.
192+
func MapSequence[T1 any, T2 any](s iter.Seq[T1], f MapFunc[T1, T2]) iter.Seq[T2] {
193+
return MapSequenceWithError[T1, T2](s, func(t1 T1) (T2, error) {
194+
return f(t1), nil
195+
})
196+
}
101197

102-
for i := range s {
103-
result[i] = f(s[i])
198+
// MapSequenceWithError creates a new sequences and populates it with the results of calling the provided function on every element of the input sequence. If an error happens, the mapping stops.
199+
func MapSequenceWithError[T1 any, T2 any](s iter.Seq[T1], f MapWithErrorFunc[T1, T2]) iter.Seq[T2] {
200+
return func(yield func(T2) bool) {
201+
for v := range s {
202+
mapped, subErr := f(v)
203+
if subErr != nil || !yield(mapped) {
204+
return
205+
}
206+
}
104207
}
208+
}
105209

106-
return result
210+
// Map creates a new slice and populates it with the results of calling the provided function on every element in input slice.
211+
func Map[T1 any, T2 any](s []T1, f MapFunc[T1, T2]) []T2 {
212+
return slices.Collect[T2](MapSequence[T1, T2](slices.Values(s), f))
107213
}
108214

109215
// MapWithError creates a new slice and populates it with the results of calling the provided function on every element in input slice. If an error happens, the mapping stops and the error returned.
@@ -122,10 +228,32 @@ func MapWithError[T1 any, T2 any](s []T1, f MapWithErrorFunc[T1, T2]) (result []
122228
return
123229
}
124230

231+
// OppositeFunc returns the opposite of a FilterFunc.
232+
func OppositeFunc[E any](f FilterFunc[E]) FilterFunc[E] { return func(e E) bool { return !f(e) } }
233+
125234
// Reject is the opposite of Filter and returns the elements of collection for which the filtering function f returns false.
126235
// This is functionally equivalent to slices.DeleteFunc but it returns a new slice.
127236
func Reject[S ~[]E, E any](s S, f FilterFunc[E]) S {
128-
return Filter(s, func(e E) bool { return !f(e) })
237+
return Filter(s, OppositeFunc[E](f))
238+
}
239+
240+
// RejectSequence is the opposite of FilterSequence and returns the elements of collection for which the filtering function f returns false.
241+
func RejectSequence[E any](s iter.Seq[E], f FilterFunc[E]) iter.Seq[E] {
242+
return FilterSequence(s, OppositeFunc[E](f))
243+
}
244+
245+
// Reduce runs a reducer function f over all elements in the array, in ascending-index order, and accumulates them into a single value.
246+
func Reduce[T1, T2 any](s []T1, accumulator T2, f ReduceFunc[T1, T2]) T2 {
247+
return ReducesSequence[T1, T2](slices.Values(s), accumulator, f)
248+
}
249+
250+
// ReducesSequence runs a reducer function f over all elements of a sequence, in ascending-index order, and accumulates them into a single value.
251+
func ReducesSequence[T1, T2 any](s iter.Seq[T1], accumulator T2, f ReduceFunc[T1, T2]) (result T2) {
252+
result = accumulator
253+
for e := range s {
254+
result = f(result, e)
255+
}
256+
return
129257
}
130258

131259
func match[E any](e E, matches []FilterFunc[E]) *Conditions {
@@ -148,13 +276,14 @@ func MatchAll[E any](e E, matches ...FilterFunc[E]) bool {
148276

149277
type ReduceFunc[T1, T2 any] func(T2, T1) T2
150278

151-
// Reduce runs a reducer function f over all elements in the array, in ascending-index order, and accumulates them into a single value.
152-
func Reduce[T1, T2 any](s []T1, accumulator T2, f ReduceFunc[T1, T2]) (result T2) {
153-
result = accumulator
154-
for i := range s {
155-
result = f(result, s[i])
156-
}
157-
return
279+
// AllFunc returns whether f returns true for all the elements of slice s.
280+
func AllFunc[S ~[]E, E any](s S, f func(E) bool) bool {
281+
return AllTrueSequence(slices.Values(s), f)
282+
}
283+
284+
// AllTrueSequence returns whether f returns true for all the elements in a sequence.
285+
func AllTrueSequence[E any](s iter.Seq[E], f func(E) bool) bool {
286+
return AllSequence(MapSequence[E, bool](s, f))
158287
}
159288

160289
// AnyEmpty returns whether there is one entry in the slice which is empty.
@@ -164,15 +293,6 @@ func AnyEmpty(strict bool, slice []string) bool {
164293
return found
165294
}
166295

167-
// AllFunc returns whether f returns true for all the elements of slice s.
168-
func AllFunc[S ~[]E, E any](s S, f func(E) bool) bool {
169-
conditions := NewConditions(len(s))
170-
for i := range s {
171-
conditions.Add(f(s[i]))
172-
}
173-
return conditions.All()
174-
}
175-
176296
// AllNotEmpty returns whether all elements of the slice are not empty.
177297
// If strict, then whitespaces are considered as empty strings
178298
func AllNotEmpty(strict bool, slice []string) bool {

0 commit comments

Comments
 (0)