Skip to content

Commit 135f74e

Browse files
authored
[collection] Many additions to the collection module (#748)
… <!-- Copyright (C) 2020-2022 Arm Limited or its affiliates and Contributors. All rights reserved. SPDX-License-Identifier: Apache-2.0 --> ### Description - added set operations - make use of Sequences for quicker operations on collections - added a for each method ### Test Coverage <!-- Please put an `x` in the correct box e.g. `[x]` to indicate the testing coverage of this change. --> - [x] This change is covered by existing or additional automated tests. - [ ] Manual testing has been performed (and evidence provided) as automated testing was not feasible. - [ ] Additional tests are not required for this change (e.g. documentation update).
1 parent 0f98eea commit 135f74e

File tree

7 files changed

+295
-42
lines changed

7 files changed

+295
-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. If the sequence is empty, it also returns true.
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: 150 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@
55
package collection
66

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

1112
mapset "github.com/deckarep/golang-set/v2"
13+
"go.uber.org/atomic"
14+
15+
"github.com/ARM-software/golang-utils/utils/commonerrors"
16+
"github.com/ARM-software/golang-utils/utils/safecast"
1217
)
1318

1419
// Find looks for an element in a slice. If found it will
@@ -20,6 +25,22 @@ func Find(slice *[]string, val string) (int, bool) {
2025
return FindInSlice(true, *slice, val)
2126
}
2227

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

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

74135
type FilterFunc[E any] func(E) bool
75136

137+
type Predicate[E any] = FilterFunc[E]
138+
76139
// 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))
140+
func Filter[S ~[]E, E any](s S, f FilterFunc[E]) S {
141+
return slices.Collect[E](FilterSequence[E](slices.Values(s), f))
142+
}
79143

80-
for i := range s {
81-
if f(s[i]) {
82-
result = append(result, s[i])
144+
// 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.
145+
func FilterSequence[E any](s iter.Seq[E], f Predicate[E]) (result iter.Seq[E]) {
146+
return func(yield func(E) bool) {
147+
for v := range s {
148+
if f(v) {
149+
if !yield(v) {
150+
return
151+
}
152+
}
83153
}
84154
}
155+
}
85156

86-
return result
157+
// ForEachValues iterates over values and executes the passed function on each of them.
158+
func ForEachValues[E any](f func(E), values ...E) {
159+
ForEach(values, f)
160+
}
161+
162+
// ForEach iterates over elements and executes the passed function on each element.
163+
func ForEach[S ~[]E, E any](s S, f func(E)) {
164+
_ = Each[E](slices.Values(s), func(e E) error {
165+
f(e)
166+
return nil
167+
})
168+
}
169+
170+
// Each iterates over a sequence and executes the passed function against each element.
171+
// If passed func returns an error, the iteration stops and the error is returned, unless it is EOF.
172+
func Each[T any](s iter.Seq[T], f func(T) error) error {
173+
for e := range s {
174+
err := f(e)
175+
if err != nil {
176+
err = commonerrors.Ignore(err, commonerrors.ErrEOF)
177+
return err
178+
}
179+
}
180+
return nil
87181
}
88182

89183
type MapFunc[T1, T2 any] func(T1) T2
@@ -95,15 +189,28 @@ func IdentityMapFunc[T any]() MapFunc[T, T] {
95189
}
96190
}
97191

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))
192+
// MapSequence creates a new sequences and populates it with the results of calling the provided function on every element of the input sequence.
193+
func MapSequence[T1 any, T2 any](s iter.Seq[T1], f MapFunc[T1, T2]) iter.Seq[T2] {
194+
return MapSequenceWithError[T1, T2](s, func(t1 T1) (T2, error) {
195+
return f(t1), nil
196+
})
197+
}
101198

102-
for i := range s {
103-
result[i] = f(s[i])
199+
// 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.
200+
func MapSequenceWithError[T1 any, T2 any](s iter.Seq[T1], f MapWithErrorFunc[T1, T2]) iter.Seq[T2] {
201+
return func(yield func(T2) bool) {
202+
for v := range s {
203+
mapped, subErr := f(v)
204+
if subErr != nil || !yield(mapped) {
205+
return
206+
}
207+
}
104208
}
209+
}
105210

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

109216
// 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 +229,32 @@ func MapWithError[T1 any, T2 any](s []T1, f MapWithErrorFunc[T1, T2]) (result []
122229
return
123230
}
124231

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

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

149278
type ReduceFunc[T1, T2 any] func(T2, T1) T2
150279

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
280+
// AllFunc returns whether f returns true for all the elements of slice s.
281+
func AllFunc[S ~[]E, E any](s S, f func(E) bool) bool {
282+
return AllTrueSequence(slices.Values(s), f)
283+
}
284+
285+
// AllTrueSequence returns whether f returns true for all the elements in a sequence.
286+
func AllTrueSequence[E any](s iter.Seq[E], f func(E) bool) bool {
287+
return AllSequence(MapSequence[E, bool](s, f))
158288
}
159289

160290
// AnyEmpty returns whether there is one entry in the slice which is empty.
@@ -164,15 +294,6 @@ func AnyEmpty(strict bool, slice []string) bool {
164294
return found
165295
}
166296

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-
176297
// AllNotEmpty returns whether all elements of the slice are not empty.
177298
// If strict, then whitespaces are considered as empty strings
178299
func AllNotEmpty(strict bool, slice []string) bool {

0 commit comments

Comments
 (0)