Skip to content

Commit

Permalink
feat: implement basic set of helpers for iterators
Browse files Browse the repository at this point in the history
  • Loading branch information
rdmrcv committed Nov 28, 2024
1 parent efef0ff commit de57e3e
Show file tree
Hide file tree
Showing 25 changed files with 4,706 additions and 0 deletions.
1,018 changes: 1,018 additions & 0 deletions pkg/la/README.md

Large diffs are not rendered by default.

91 changes: 91 additions & 0 deletions pkg/la/collect.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package la

import "iter"

// Reduce reduces an iter.Seq to a value which is the accumulated result of
// running each element in the iter.Seq through accumulator, where each
// successive invocation is supplied with the return value of the previous.
//
// Since this function requires collecting all values from the iterator – in many
// cases, it will be better to use [lo.Reduce] after [slices.Collect] on the
// iterator instead of this function.
func Reduce[T any, R any](collection iter.Seq[T], accumulator func(agg R, item T) R, initial R) R {
for v := range collection {
initial = accumulator(initial, v)
}

return initial
}

// Reduce2 reduces an iter.Seq2 to a value which is the accumulated result of
// running each element in the iter.Seq2 through accumulator, where each
// successive invocation is supplied with the return value of the previous.
//
// Since this function requires collecting all values from the iterator – in many
// cases, it will be better to use [lo.Reduce] after [Tuples] and
// [slices.Collect] on the iterator instead of this function.
func Reduce2[K, V any, R any](collection iter.Seq2[K, V], accumulator func(agg R, key K, val V) R, initial R) R {
for k, v := range collection {
initial = accumulator(initial, k, v)
}

return initial
}

// ForEach iterates over elements of an iter.Seq and invokes iteratee for each element.
func ForEach[T any](collection iter.Seq[T], iteratee func(item T)) {
for v := range collection {
iteratee(v)
}
}

// ForEach2 iterates over elements of an iter.Seq2 and invokes iteratee for each element.
func ForEach2[K, V any](collection iter.Seq2[K, V], iteratee func(key K, val V)) {
for k, v := range collection {
iteratee(k, v)
}
}

// ForEachWhile iterates over elements of an iter.Seq and invokes iteratee for
// each element, the returned value decides to continue or break, like do
// while().
func ForEachWhile[T any](collection iter.Seq[T], iteratee func(item T) (goon bool)) {
for v := range collection {
if !iteratee(v) {
break
}
}
}

// ForEachWhile2 iterates over elements of an iter.Seq and invokes iteratee for
// each element, the returned value decides to continue or break, like do
// while().
func ForEachWhile2[K, V any](collection iter.Seq2[K, V], iteratee func(key K, val V) (goon bool)) {
for k, v := range collection {
if !iteratee(k, v) {
break
}
}
}

// KeyValues create two parallel iterators where the first yields keys and the
// second – values of the original iterator.
//
// To achieve that, it is necessary to walk through an original iterator twice,
// so if your iterator is not support that – avoid this function.
func KeyValues[K any, V any](in iter.Seq2[K, V]) (iter.Seq[K], iter.Seq[V]) {
return func(yield func(K) bool) {
for k := range in {
if !yield(k) {
return
}
}
},
func(yield func(V) bool) {
for _, v := range in {
if !yield(v) {
return
}
}
}
}
95 changes: 95 additions & 0 deletions pkg/la/collect_example_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package la

import (
"fmt"
"github.com/samber/lo"
"math"
"slices"
)

func ExampleReduce() {
result := Reduce(slices.Values([]int64{1, 2, 3, 4}), func(agg int64, item int64) int64 {
return agg + item
}, 0)

fmt.Printf("%v", result)
// Output: 10
}

func ExampleReduce2() {
result := Reduce2(Enumerate(slices.Values([]int64{1, 2, 3, 4})), func(agg int64, k int, item int64) int64 {
if k%2 == 0 {
return agg + item
}

return agg
}, 0)

fmt.Printf("%v", result)
// Output: 4
}

func ExampleForEach() {
ForEach(slices.Values([]int64{1, 2, 3, 4}), func(x int64) {
fmt.Println(x)
})

// Output:
// 1
// 2
// 3
// 4
}

func ExampleForEach2() {
ForEach2(Enumerate(slices.Values([]int64{1, 2, 3, 4})), func(k int, x int64) {
fmt.Printf("%d %d\n", k, x)
})

// Output:
// 0 1
// 1 2
// 2 3
// 3 4
}

func ExampleForEachWhile() {
ForEachWhile(slices.Values([]int64{1, 2, -math.MaxInt, 4}), func(x int64) bool {
if x < 0 {
return false
}
fmt.Println(x)
return true
})

// Output:
// 1
// 2
}

func ExampleForEachWhile2() {
ForEachWhile2(Enumerate(slices.Values([]int64{1, 2, -math.MaxInt, 4})), func(k int, x int64) bool {
if x < 0 {
return false
}
fmt.Printf("%d %d\n", k, x)
return true
})

// Output:
// 0 1
// 1 2
}

func ExampleKeyValues() {
kv := FromTuples([]lo.Tuple2[string, int]{
{"foo", 1},
{"bar", 2},
{"baz", 3},
})

keys, values := KeyValues(kv)

fmt.Printf("%v %v", Collect(keys), Collect(values))
// Output: [foo bar baz] [1 2 3]
}
114 changes: 114 additions & 0 deletions pkg/la/collect_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package la

import (
"github.com/stretchr/testify/assert"
"slices"
"testing"
)

func TestReduce(t *testing.T) {
t.Parallel()
is := assert.New(t)

result1 := Reduce(slices.Values([]int{1, 2, 3, 4}), func(agg int, item int) int {
return agg + item
}, 0)
result2 := Reduce(slices.Values([]int{1, 2, 3, 4}), func(agg int, item int) int {
return agg + item
}, 10)

is.Equal(result1, 10)
is.Equal(result2, 20)
}

func TestReduce2(t *testing.T) {
t.Parallel()
is := assert.New(t)

result1 := Reduce2(Enumerate(slices.Values([]int{1, 2, 3, 4})), func(agg int, _ int, item int) int {
return agg + item
}, 0)
result2 := Reduce2(Enumerate(slices.Values([]int{1, 2, 3, 4})), func(agg int, _ int, item int) int {
return agg + item
}, 10)

is.Equal(result1, 10)
is.Equal(result2, 20)
}

func TestForEach(t *testing.T) {
t.Parallel()
is := assert.New(t)

// check of callback is called for every element and in proper order

callParams1 := []string{}

ForEach(slices.Values([]string{"a", "b", "c"}), func(item string) {
callParams1 = append(callParams1, item)
})

is.ElementsMatch([]string{"a", "b", "c"}, callParams1)
}

func TestForEach2(t *testing.T) {
t.Parallel()
is := assert.New(t)

// check of callback is called for every element and in proper order

callParams1 := []string{}
callParams2 := []int{}

ForEach2(Enumerate(slices.Values([]string{"a", "b", "c"})), func(i int, item string) {
callParams1 = append(callParams1, item)
callParams2 = append(callParams2, i)
})

is.ElementsMatch([]string{"a", "b", "c"}, callParams1)
is.ElementsMatch([]int{0, 1, 2}, callParams2)
is.IsIncreasing(callParams2)
}

func TestForEachWhile(t *testing.T) {
t.Parallel()
is := assert.New(t)

// check of callback is called for every element and in proper order

var callParams1 []string

ForEachWhile(slices.Values([]string{"a", "b", "c"}), func(item string) bool {
if item == "c" {
return false
}
callParams1 = append(callParams1, item)

return true
})

is.ElementsMatch([]string{"a", "b"}, callParams1)
}

func TestForEachWhile2(t *testing.T) {
t.Parallel()
is := assert.New(t)

// check of callback is called for every element and in proper order

var callParams1 []string
var callParams2 []int

ForEachWhile2(Enumerate(slices.Values([]string{"a", "b", "c"})), func(i int, item string) bool {
if item == "c" {
return false
}
callParams1 = append(callParams1, item)
callParams2 = append(callParams2, i)
return true
})

is.ElementsMatch([]string{"a", "b"}, callParams1)
is.ElementsMatch([]int{0, 1}, callParams2)
is.IsIncreasing(callParams2)
}
Loading

0 comments on commit de57e3e

Please sign in to comment.