Skip to content

Commit d939914

Browse files
committed
feat: composable iterator transforms
1 parent e74e256 commit d939914

File tree

4 files changed

+346
-0
lines changed

4 files changed

+346
-0
lines changed

iterator/iter.go

+134
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
package iterator
2+
3+
import (
4+
"iter"
5+
)
6+
7+
func Map[T, V any](it iter.Seq[T], fn func(T) V) iter.Seq[V] {
8+
return func(yield func(V) bool) {
9+
for t := range it {
10+
if !yield(fn(t)) {
11+
break
12+
}
13+
}
14+
}
15+
}
16+
17+
func Map2[T, V, W any](it iter.Seq[T], fn func(T) (V, W)) iter.Seq2[V, W] {
18+
return func(yield func(V, W) bool) {
19+
for t := range it {
20+
if !yield(fn(t)) {
21+
break
22+
}
23+
}
24+
}
25+
}
26+
27+
func Filter[T any](it iter.Seq[T], fn func(T) bool) iter.Seq[T] {
28+
return func(yield func(T) bool) {
29+
for t := range it {
30+
if fn(t) && !yield(t) {
31+
break
32+
}
33+
}
34+
}
35+
}
36+
37+
func Reduce[T, V any](it iter.Seq[T], fn func(V, T) V, init V) V {
38+
acc := init
39+
for t := range it {
40+
acc = fn(acc, t)
41+
}
42+
43+
return acc
44+
}
45+
46+
// Zip returns an iterator that yields pairs of elements from the two input iterators, terminating when either
47+
// iterator is exhausted
48+
func Zip[T, U any](it iter.Seq[T], other iter.Seq[U]) iter.Seq2[T, U] {
49+
return func(yield func(T, U) bool) {
50+
next, stop := iter.Pull(other)
51+
defer stop()
52+
for t := range it {
53+
u, ok := next()
54+
if !ok || !yield(t, u) {
55+
break
56+
}
57+
}
58+
}
59+
}
60+
61+
// ZipWith returns an iterator that yields the results of applying a function to pairs of elements from the two input
62+
// iterators, terminating when either iterator is exhausted
63+
func ZipWith[T, U, V any](it iter.Seq[T], other iter.Seq[U], fn func(T, U) V) iter.Seq[V] {
64+
return func(yield func(V) bool) {
65+
next, stop := iter.Pull(other)
66+
defer stop()
67+
for t := range it {
68+
u, ok := next()
69+
if !ok || !yield(fn(t, u)) {
70+
break
71+
}
72+
}
73+
}
74+
}
75+
76+
func Indexed[T any](it iter.Seq[T]) iter.Seq2[int, T] {
77+
return func(yield func(int, T) bool) {
78+
i := 0
79+
for t := range it {
80+
if !yield(i, t) {
81+
break
82+
}
83+
i++
84+
}
85+
}
86+
}
87+
88+
func Concat[T any](its ...iter.Seq[T]) iter.Seq[T] {
89+
return func(yield func(T) bool) {
90+
for _, it := range its {
91+
for t := range it {
92+
if !yield(t) {
93+
break
94+
}
95+
}
96+
}
97+
}
98+
}
99+
100+
func Compact[T comparable](it iter.Seq[T]) iter.Seq[T] {
101+
return func(yield func(T) bool) {
102+
var last T
103+
var started bool
104+
for t := range it {
105+
if started && t == last {
106+
continue
107+
}
108+
109+
if !yield(t) {
110+
break
111+
}
112+
last = t
113+
started = true
114+
}
115+
}
116+
}
117+
118+
func CompactFunc[T any](it iter.Seq[T], eq func(T, T) bool) iter.Seq[T] {
119+
return func(yield func(T) bool) {
120+
var last T
121+
var started bool
122+
for t := range it {
123+
if started && eq(t, last) {
124+
continue
125+
}
126+
127+
if !yield(t) {
128+
break
129+
}
130+
last = t
131+
started = true
132+
}
133+
}
134+
}

iterator/iter_test.go

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package iterator
2+
3+
import (
4+
"gotest.tools/v3/assert"
5+
"maps"
6+
"slices"
7+
"strconv"
8+
"strings"
9+
"testing"
10+
)
11+
12+
func TestMap(t *testing.T) {
13+
it := slices.Values([]int{1, 2, 3, 4})
14+
toString := func(n int) string {
15+
return strconv.Itoa(n)
16+
}
17+
18+
result := slices.Collect(Map(it, toString))
19+
expected := []string{"1", "2", "3", "4"}
20+
21+
assert.DeepEqual(t, result, expected)
22+
23+
}
24+
25+
func TestMap2(t *testing.T) {
26+
it := slices.Values([]string{"a", "three whole words", "two tokens"})
27+
28+
results := maps.Collect(Map2(it, func(str string) (int, int) {
29+
words := strings.Fields(str)
30+
return len(words), len(str)
31+
}))
32+
33+
expected := map[int]int{1: 1, 2: 10, 3: 17}
34+
assert.DeepEqual(t, results, expected)
35+
}
36+
37+
func TestFilter(t *testing.T) {
38+
it := slices.Values([]int{1, 2, 3, 4, 5})
39+
isEven := func(n int) bool {
40+
return n%2 == 0
41+
}
42+
43+
result := slices.Collect(Filter(it, isEven))
44+
assert.DeepEqual(t, result, []int{2, 4})
45+
}
46+
47+
func TestReduce(t *testing.T) {
48+
it := slices.Values([]int{1, 2, 3, 4})
49+
sum := func(acc, n int) int {
50+
return acc + n
51+
}
52+
53+
result := Reduce(it, sum, 0)
54+
assert.Equal(t, result, 10)
55+
}
56+
57+
func TestZip(t *testing.T) {
58+
it1 := slices.Values([]int{1, 2, 3})
59+
it2 := slices.Values([]string{"a", "b", "c"})
60+
61+
result := maps.Collect(Zip(it1, it2))
62+
expected := map[int]string{1: "a", 2: "b", 3: "c"}
63+
64+
assert.DeepEqual(t, result, expected)
65+
}
66+
67+
func TestZipWith(t *testing.T) {
68+
it1 := slices.Values([]int{1, 2, 3})
69+
it2 := slices.Values([]string{"c", "b", "a"})
70+
71+
result := slices.Collect(ZipWith(it1, it2, func(n int, s string) string {
72+
return strings.Repeat(s, n)
73+
}))
74+
expected := []string{"c", "bb", "aaa"}
75+
76+
assert.DeepEqual(t, result, expected)
77+
}
78+
79+
func TestIndexed(t *testing.T) {
80+
it := slices.Values([]string{"c", "b", "a"})
81+
result := maps.Collect(Indexed(it))
82+
83+
expected := map[int]string{0: "c", 1: "b", 2: "a"}
84+
assert.DeepEqual(t, result, expected)
85+
}
86+
87+
func TestConcat(t *testing.T) {
88+
it1 := slices.Values([]int{1, 2})
89+
it2 := slices.Values([]int{3, 4})
90+
91+
result := slices.Collect(Concat(it1, it2))
92+
93+
expected := []int{1, 2, 3, 4}
94+
assert.DeepEqual(t, result, expected)
95+
}
96+
97+
func TestCompact(t *testing.T) {
98+
it := slices.Values([]int{2, 2, 1, 3, 3, 3})
99+
result := slices.Collect(Compact(it))
100+
101+
expected := []int{2, 1, 3}
102+
assert.DeepEqual(t, result, expected)
103+
}
104+
105+
func TestCompactFunc(t *testing.T) {
106+
it := slices.Values([]int{2, 7, 1, 9, 9, 9})
107+
result := slices.Collect(CompactFunc(it, func(a, b int) bool {
108+
return a%5 == b%5
109+
}))
110+
111+
expected := []int{2, 1, 9}
112+
assert.DeepEqual(t, result, expected)
113+
}

iterator2/iter2.go

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package iterator2
2+
3+
import "iter"
4+
5+
func Map[T, U, V, W any](it iter.Seq2[T, U], fn func(T, U) (V, W)) iter.Seq2[V, W] {
6+
return func(yield func(V, W) bool) {
7+
for t, u := range it {
8+
if !yield(fn(t, u)) {
9+
break
10+
}
11+
}
12+
}
13+
}
14+
15+
func Map1[T, U, V any](it iter.Seq2[T, U], fn func(T, U) V) iter.Seq[V] {
16+
return func(yield func(V) bool) {
17+
for t, u := range it {
18+
if !yield(fn(t, u)) {
19+
break
20+
}
21+
}
22+
}
23+
}
24+
25+
func Filter[T, U any](it iter.Seq2[T, U], fn func(T, U) bool) iter.Seq2[T, U] {
26+
return func(yield func(T, U) bool) {
27+
for t, u := range it {
28+
if fn(t, u) && !yield(t, u) {
29+
break
30+
}
31+
}
32+
}
33+
}
34+
35+
func Reduce[T, U, V any](it iter.Seq2[T, U], fn func(V, T, U) V, init V) V {
36+
acc := init
37+
for t, u := range it {
38+
acc = fn(acc, t, u)
39+
}
40+
41+
return acc
42+
}

iterator2/iter2_test.go

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package iterator2
2+
3+
import (
4+
"go-exp/functions/hof"
5+
"gotest.tools/v3/assert"
6+
"maps"
7+
"slices"
8+
"strconv"
9+
"strings"
10+
"testing"
11+
)
12+
13+
func TestMap(t *testing.T) {
14+
it := slices.All([]int{1, 2, 3, 4})
15+
toString := func(i, n int) (string, string) {
16+
return strconv.Itoa(i), strconv.Itoa(n)
17+
}
18+
19+
result := maps.Collect(Map(it, toString))
20+
expected := map[string]string{"0": "1", "1": "2", "2": "3", "3": "4"}
21+
22+
assert.DeepEqual(t, result, expected)
23+
24+
}
25+
26+
func TestMap1(t *testing.T) {
27+
it := slices.All([]int{1, 2, 3, 4})
28+
29+
result := slices.Collect(Map1(it, func(i, n int) string {
30+
return strconv.Itoa(n)
31+
}))
32+
expected := []string{"1", "2", "3", "4"}
33+
34+
assert.DeepEqual(t, result, expected)
35+
}
36+
37+
func TestFilter(t *testing.T) {
38+
it := slices.All([]int{1, 2, 3, 4, 5})
39+
isEven := func(n int) bool {
40+
return n%2 == 0
41+
}
42+
43+
result := maps.Collect(Filter(it, hof.LiftArity1Left[int, int, bool](isEven)))
44+
expected := map[int]int{1: 2, 3: 4}
45+
46+
assert.DeepEqual(t, result, expected)
47+
}
48+
49+
func TestReduce(t *testing.T) {
50+
it := slices.All([]string{"d", "c", "b", "a"})
51+
expand := func(acc string, i int, s string) string {
52+
return acc + strings.Repeat(s, i)
53+
}
54+
55+
result := Reduce(it, expand, "")
56+
assert.Equal(t, result, "cbbaaa")
57+
}

0 commit comments

Comments
 (0)