Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changes/20251117162958.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:sparkles: `[collection]` Added execute a function on each element of a function
1 change: 1 addition & 0 deletions changes/20251117163045.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:zap: `[collection]` Support `iter.Seq` for most operations on slices (map, filter) to increase performance
1 change: 1 addition & 0 deletions changes/20251117163100.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
:sparkles: `[collection]` Added set operations on slices
46 changes: 34 additions & 12 deletions utils/collection/conditions.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package collection

import (
"iter"
"slices"

"github.com/ARM-software/golang-utils/utils/commonerrors"
)

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

// Any returns true if there is at least one element of the slice which is true.
func Any(slice []bool) bool {
if len(slice) == 0 {
return AnySequence(slices.Values(slice))
}

// AnySequence returns true if there is at least one element of the slice which is true.
func AnySequence(seq iter.Seq[bool]) bool {
if seq == nil {
return false
}
for i := range slice {
if slice[i] {
for e := range seq {
if e {
return true
}
}
Expand All @@ -136,17 +144,31 @@ func AnyTrue(values ...bool) bool {
return Any(values)
}

// All returns true if all items of the slice are true.
func All(slice []bool) bool {
if len(slice) == 0 {
return false
}
for i := range slice {
if !slice[i] {
return false
// 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.
func AnyFalseSequence(eq iter.Seq[bool]) bool {
hasElements := false
for e := range eq {
hasElements = true
if !e {
return true
}
}
return true
return !hasElements
}

// AnyFalse returns whether there is a value set to false
func AnyFalse(values ...bool) bool {
return AnyFalseSequence(slices.Values(values))
}

// AllSequence returns true if all items of the sequence are true.
func AllSequence(seq iter.Seq[bool]) bool {
return !AnyFalseSequence(seq)
}

// All returns true if all items of the slice are true.
func All(slice []bool) bool {
return AllSequence(slices.Values(slice))
}

// AllTrue returns whether all values are true.
Expand Down
8 changes: 8 additions & 0 deletions utils/collection/conditions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,14 @@ func TestAnyTrue(t *testing.T) {
assert.True(t, AnyTrue(true, true, true, true, true, true, true, true, true, false, true, true, true, true, true, true, true, true, true, true))
}

func TestAnyFalse(t *testing.T) {
assert.True(t, AnyFalse())
assert.True(t, AnyFalse(false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false))
assert.True(t, AnyFalse(false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false))
assert.False(t, AnyFalse(true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true))
assert.True(t, AnyFalse(true, true, true, true, true, true, true, true, true, false, true, true, true, true, true, true, true, true, true, true))
}

func TestAll(t *testing.T) {
assert.False(t, All([]bool{}))
assert.False(t, All([]bool{false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false}))
Expand Down
179 changes: 150 additions & 29 deletions utils/collection/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,15 @@
package collection

import (
"iter"
"slices"
"strings"

mapset "github.com/deckarep/golang-set/v2"
"go.uber.org/atomic"

"github.com/ARM-software/golang-utils/utils/commonerrors"
"github.com/ARM-software/golang-utils/utils/safecast"
)

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

// FindInSequence searches a collection for an element satisfying the predicate.
func FindInSequence[E any](elements iter.Seq[E], predicate Predicate[E]) (int, bool) {
if elements == nil {
return -1, false
}
idx := atomic.NewUint64(0)
for e := range elements {
if predicate(e) {
return safecast.ToInt(idx.Load()), true
}
idx.Inc()
}

return -1, false
}

// FindInSlice finds if any values val are present in the slice and if so returns the first index.
// if strict, it checks for an exact match; otherwise it discards whitespaces and case.
func FindInSlice(strict bool, slice []string, val ...string) (int, bool) {
Expand Down Expand Up @@ -62,6 +83,46 @@ func UniqueEntries[T comparable](slice []T) []T {
return subSet.ToSlice()
}

// Unique returns all the unique values contained in a sequence.
func Unique[T comparable](s iter.Seq[T]) []T {
return UniqueEntries(slices.Collect(s))
}

// Union returns the union of two slices (only unique values are returned).
func Union[T comparable](slice1, slice2 []T) []T {
subSet := mapset.NewSet[T]()
_ = subSet.Append(slice1...)
_ = subSet.Append(slice2...)
return subSet.ToSlice()
}

// Intersection returns the intersection of two slices (only unique values are returned).
func Intersection[T comparable](slice1, slice2 []T) []T {
subSet1 := mapset.NewSet[T]()
subSet2 := mapset.NewSet[T]()
_ = subSet1.Append(slice1...)
_ = subSet2.Append(slice2...)
return subSet1.Intersect(subSet2).ToSlice()
}

// Difference returns the Difference between slice1 and slice2 (only unique values are returned).
func Difference[T comparable](slice1, slice2 []T) []T {
subSet1 := mapset.NewSet[T]()
subSet2 := mapset.NewSet[T]()
_ = subSet1.Append(slice1...)
_ = subSet2.Append(slice2...)
return subSet1.Difference(subSet2).ToSlice()
}

// SymmetricDifference returns the symmetric difference between slice1 and slice2 (only unique values are returned).
func SymmetricDifference[T comparable](slice1, slice2 []T) []T {
subSet1 := mapset.NewSet[T]()
subSet2 := mapset.NewSet[T]()
_ = subSet1.Append(slice1...)
_ = subSet2.Append(slice2...)
return subSet1.SymmetricDifference(subSet2).ToSlice()
}

// AnyFunc returns whether there is at least one element of slice s for which f() returns true.
func AnyFunc[S ~[]E, E any](s S, f func(E) bool) bool {
conditions := NewConditions(len(s))
Expand All @@ -73,17 +134,50 @@ func AnyFunc[S ~[]E, E any](s S, f func(E) bool) bool {

type FilterFunc[E any] func(E) bool

type Predicate[E any] = FilterFunc[E]

// 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.
func Filter[S ~[]E, E any](s S, f FilterFunc[E]) (result S) {
result = make(S, 0, len(s))
func Filter[S ~[]E, E any](s S, f FilterFunc[E]) S {
return slices.Collect[E](FilterSequence[E](slices.Values(s), f))
}

for i := range s {
if f(s[i]) {
result = append(result, s[i])
// 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.
func FilterSequence[E any](s iter.Seq[E], f Predicate[E]) (result iter.Seq[E]) {
return func(yield func(E) bool) {
for v := range s {
if f(v) {
if !yield(v) {
return
}
}
}
}
}

return result
// ForEachValues iterates over values and executes the passed function on each of them.
func ForEachValues[E any](f func(E), values ...E) {
ForEach(values, f)
}

// ForEach iterates over elements and executes the passed function on each element.
func ForEach[S ~[]E, E any](s S, f func(E)) {
_ = Each[E](slices.Values(s), func(e E) error {
f(e)
return nil
})
}

// Each iterates over a sequence and executes the passed function against each element.
// If passed func returns an error, the iteration stops and the error is returned, unless it is EOF.
func Each[T any](s iter.Seq[T], f func(T) error) error {
for e := range s {
err := f(e)
if err != nil {
err = commonerrors.Ignore(err, commonerrors.ErrEOF)
return err
}
}
return nil
}

type MapFunc[T1, T2 any] func(T1) T2
Expand All @@ -95,15 +189,28 @@ func IdentityMapFunc[T any]() MapFunc[T, T] {
}
}

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

for i := range s {
result[i] = f(s[i])
// 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.
func MapSequenceWithError[T1 any, T2 any](s iter.Seq[T1], f MapWithErrorFunc[T1, T2]) iter.Seq[T2] {
return func(yield func(T2) bool) {
for v := range s {
mapped, subErr := f(v)
if subErr != nil || !yield(mapped) {
return
}
}
}
}

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

// 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.
Expand All @@ -122,10 +229,32 @@ func MapWithError[T1 any, T2 any](s []T1, f MapWithErrorFunc[T1, T2]) (result []
return
}

// OppositeFunc returns the opposite of a FilterFunc.
func OppositeFunc[E any](f FilterFunc[E]) FilterFunc[E] { return func(e E) bool { return !f(e) } }

// Reject is the opposite of Filter and returns the elements of collection for which the filtering function f returns false.
// This is functionally equivalent to slices.DeleteFunc but it returns a new slice.
func Reject[S ~[]E, E any](s S, f FilterFunc[E]) S {
return Filter(s, func(e E) bool { return !f(e) })
return Filter(s, OppositeFunc[E](f))
}

// RejectSequence is the opposite of FilterSequence and returns the elements of collection for which the filtering function f returns false.
func RejectSequence[E any](s iter.Seq[E], f FilterFunc[E]) iter.Seq[E] {
return FilterSequence(s, OppositeFunc[E](f))
}

// Reduce runs a reducer function f over all elements in the array, in ascending-index order, and accumulates them into a single value.
func Reduce[T1, T2 any](s []T1, accumulator T2, f ReduceFunc[T1, T2]) T2 {
return ReducesSequence[T1, T2](slices.Values(s), accumulator, f)
}

// ReducesSequence runs a reducer function f over all elements of a sequence, in ascending-index order, and accumulates them into a single value.
func ReducesSequence[T1, T2 any](s iter.Seq[T1], accumulator T2, f ReduceFunc[T1, T2]) (result T2) {
result = accumulator
for e := range s {
result = f(result, e)
}
return
}

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

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

// Reduce runs a reducer function f over all elements in the array, in ascending-index order, and accumulates them into a single value.
func Reduce[T1, T2 any](s []T1, accumulator T2, f ReduceFunc[T1, T2]) (result T2) {
result = accumulator
for i := range s {
result = f(result, s[i])
}
return
// AllFunc returns whether f returns true for all the elements of slice s.
func AllFunc[S ~[]E, E any](s S, f func(E) bool) bool {
return AllTrueSequence(slices.Values(s), f)
}

// AllTrueSequence returns whether f returns true for all the elements in a sequence.
func AllTrueSequence[E any](s iter.Seq[E], f func(E) bool) bool {
return AllSequence(MapSequence[E, bool](s, f))
}

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

// AllFunc returns whether f returns true for all the elements of slice s.
func AllFunc[S ~[]E, E any](s S, f func(E) bool) bool {
conditions := NewConditions(len(s))
for i := range s {
conditions.Add(f(s[i]))
}
return conditions.All()
}

// AllNotEmpty returns whether all elements of the slice are not empty.
// If strict, then whitespaces are considered as empty strings
func AllNotEmpty(strict bool, slice []string) bool {
Expand Down
Loading
Loading