Skip to content

Commit ea71631

Browse files
committed
Implement Count, Take, Lift and Collect for rangefunc
1 parent 266f7b0 commit ea71631

File tree

7 files changed

+253
-5
lines changed

7 files changed

+253
-5
lines changed

v2/internal/assert/assert.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package assert
2+
3+
import "testing"
4+
5+
func Equal[T comparable](t *testing.T, a, b T) {
6+
t.Helper()
7+
8+
if a != b {
9+
t.Errorf("expected `%v` to equal `%v`", a, b)
10+
}
11+
}
12+
13+
func True(t *testing.T, b bool) {
14+
t.Helper()
15+
16+
if !b {
17+
t.Error("expected `false` to be `true`")
18+
}
19+
}
20+
21+
func False(t *testing.T, b bool) {
22+
t.Helper()
23+
24+
if b {
25+
t.Error("expected `true` to be `false`")
26+
}
27+
}
28+
29+
func Nil(t *testing.T, v interface{}) {
30+
t.Helper()
31+
32+
if v != nil {
33+
t.Errorf("expected `%v` to equal `nil`", v)
34+
}
35+
}
36+
37+
func NotNil(t *testing.T, v interface{}) {
38+
t.Helper()
39+
40+
if v == nil {
41+
t.Error("expected `nil` not to equal `nil`")
42+
}
43+
}
44+
45+
func SliceEqual[T comparable](t *testing.T, a, b []T) {
46+
t.Helper()
47+
48+
if len(a) != len(b) {
49+
t.Errorf("expected `%v` to equal `%v` but lengths differ", a, b)
50+
return
51+
}
52+
53+
for i, v := range a {
54+
if v != b[i] {
55+
t.Errorf("expected `%v` to equal `%v`", a, b)
56+
}
57+
}
58+
}
59+
60+
func Empty[S any, T []S | ~string](t *testing.T, items T) {
61+
t.Helper()
62+
63+
if len(items) != 0 {
64+
t.Errorf("expected `%v` to be empty", items)
65+
}
66+
}

v2/iter/count.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package iter
2+
3+
import "iter"
4+
5+
// Count yields an infinite sequence of integers, starting from 0.
6+
func Count() Iterator[int] {
7+
return Iterator[int](iter.Seq[int](func(yield func(int) bool) {
8+
for i := 0; ; i++ {
9+
if !yield(i) {
10+
return
11+
}
12+
}
13+
}))
14+
}

v2/iter/count_test.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package iter_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/BooleanCat/go-functional/v2/internal/assert"
8+
"github.com/BooleanCat/go-functional/v2/iter"
9+
)
10+
11+
func ExampleCount() {
12+
for i := range iter.Count().Take(3) {
13+
fmt.Println(i)
14+
}
15+
16+
// Output:
17+
// 0
18+
// 1
19+
// 2
20+
}
21+
22+
func TestCount(t *testing.T) {
23+
numbers := iter.Count().Take(3).Collect()
24+
assert.SliceEqual(t, []int{0, 1, 2}, numbers)
25+
}

v2/iter/iter.go

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,33 @@ package iter
22

33
import "iter"
44

5-
type Iterator[T any] iter.Seq[T]
5+
// Iterator is a wrapper around the iter.Seq type that allowed for method
6+
// chaining of iterators found in this package.
7+
type Iterator[V any] iter.Seq[V]
8+
9+
// Lift yields all items in the provided slice.
10+
func Lift[V any](slice []V) Iterator[V] {
11+
return Iterator[V](iter.Seq[V](func(yield func(V) bool) {
12+
for _, item := range slice {
13+
if !yield(item) {
14+
return
15+
}
16+
}
17+
}))
18+
}
19+
20+
// Collect consumes an iterator and returns a slice of all items yielded.
21+
func Collect[V any](iter Iterator[V]) []V {
22+
collection := make([]V, 0)
23+
24+
for item := range iter {
25+
collection = append(collection, item)
26+
}
27+
28+
return collection
29+
}
30+
31+
// Collect is a convenience method for chaining [Collect] after an iterator.
32+
func (iter Iterator[V]) Collect() []V {
33+
return Collect[V](iter)
34+
}

v2/iter/iter_test.go

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,54 @@
11
package iter_test
22

3-
import "testing"
3+
import (
4+
"fmt"
5+
"testing"
46

5-
func TestNothing(t *testing.T) {
6-
if false {
7-
t.Errorf("This should not happen")
7+
"github.com/BooleanCat/go-functional/v2/internal/assert"
8+
"github.com/BooleanCat/go-functional/v2/iter"
9+
)
10+
11+
func ExampleLift() {
12+
for i := range iter.Lift([]int{1, 2}) {
13+
fmt.Println(i)
14+
}
15+
16+
// Output:
17+
// 1
18+
// 2
19+
}
20+
21+
func TestLift(t *testing.T) {
22+
number := 0
23+
24+
for i := range iter.Lift([]int{1, 2}) {
25+
assert.Equal(t, number+1, i)
26+
number++
27+
}
28+
}
29+
30+
func TestLiftEmpty(t *testing.T) {
31+
for _ = range iter.Lift([]int{}) {
32+
t.Error("expected no iteration")
833
}
934
}
35+
36+
func ExampleCollect() {
37+
fmt.Println(iter.Collect(iter.Lift([]int{1, 2})))
38+
// Output: [1 2]
39+
}
40+
41+
func ExampleCollect_method() {
42+
fmt.Println(iter.Lift([]int{1, 2}).Collect())
43+
// Output: [1 2]
44+
}
45+
46+
func TestCollect(t *testing.T) {
47+
numbers := iter.Lift([]int{1, 2, 3}).Collect()
48+
assert.SliceEqual(t, []int{1, 2, 3}, numbers)
49+
}
50+
51+
func TestCollectEmpty(t *testing.T) {
52+
numbers := iter.Lift([]int{}).Collect()
53+
assert.Empty[int](t, numbers)
54+
}

v2/iter/take.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package iter
2+
3+
import "iter"
4+
5+
// Take limits the number of elements yielded by a delegate iterator to a
6+
// maximum limit.
7+
func Take[V any](delegate Iterator[V], limit int) Iterator[V] {
8+
return Iterator[V](iter.Seq[V](func(yield func(V) bool) {
9+
next, stop := iter.Pull(iter.Seq[V](delegate))
10+
defer stop()
11+
12+
for {
13+
limit--
14+
15+
if limit < 0 {
16+
return
17+
}
18+
19+
v, ok := next()
20+
if !ok || !yield(v) {
21+
return
22+
}
23+
}
24+
}))
25+
}
26+
27+
// Take is a convenience method for chaining [Take] after an iterator.
28+
func (iter Iterator[V]) Take(limit int) Iterator[V] {
29+
return Take[V](iter, limit)
30+
}

v2/iter/take_test.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package iter_test
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
7+
"github.com/BooleanCat/go-functional/v2/internal/assert"
8+
"github.com/BooleanCat/go-functional/v2/iter"
9+
)
10+
11+
func ExampleTake() {
12+
for i := range iter.Take(iter.Lift([]int{1, 2, 3}), 2) {
13+
fmt.Println(i)
14+
}
15+
16+
// Output:
17+
// 1
18+
// 2
19+
}
20+
21+
func ExampleTake_method() {
22+
for i := range iter.Lift([]int{1, 2, 3}).Take(2) {
23+
fmt.Println(i)
24+
}
25+
26+
// Output:
27+
// 1
28+
// 2
29+
}
30+
31+
func TestTake(t *testing.T) {
32+
numbers := iter.Lift([]int{1, 2, 3}).Take(2).Collect()
33+
assert.SliceEqual(t, []int{1, 2}, numbers)
34+
}
35+
36+
func TestTakeEmpty(t *testing.T) {
37+
numbers := iter.Lift([]int{}).Take(2).Collect()
38+
assert.Empty[int](t, numbers)
39+
}

0 commit comments

Comments
 (0)