Skip to content

Commit 0c7c260

Browse files
author
Tom Godkin
committed
Ensure filter iters do not repeatedly call exhausted delegates
1 parent 7e945cc commit 0c7c260

File tree

2 files changed

+51
-10
lines changed

2 files changed

+51
-10
lines changed

iter/filter.go

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,27 @@ import "github.com/BooleanCat/go-functional/option"
44

55
// FilterIter implements `Filter`. See `Filter`'s documentation.
66
type FilterIter[T any] struct {
7-
iter Iterator[T]
8-
fun func(T) bool
7+
iter Iterator[T]
8+
fun func(T) bool
9+
exhausted bool
910
}
1011

1112
// Filter instantiates a `FilterIter` that selectively yields only results that
1213
// cause the provided function to return `true`.
1314
func Filter[T any](iter Iterator[T], fun func(T) bool) *FilterIter[T] {
14-
return &FilterIter[T]{iter, fun}
15+
return &FilterIter[T]{iter, fun, false}
1516
}
1617

1718
// Next implements the Iterator interface for `Filter`.
1819
func (iter *FilterIter[T]) Next() option.Option[T] {
20+
if iter.exhausted {
21+
return option.None[T]()
22+
}
23+
1924
for {
2025
value, ok := iter.iter.Next().Value()
2126
if !ok {
27+
iter.exhausted = true
2228
return option.None[T]()
2329
}
2430

@@ -34,21 +40,28 @@ var _ Iterator[struct{}] = new(FilterIter[struct{}])
3440
// cause the provided function to return `false`.
3541
func Exclude[T any](iter Iterator[T], fun func(T) bool) *FilterIter[T] {
3642
inverse := func(t T) bool { return !fun(t) }
37-
return &FilterIter[T]{iter, inverse}
43+
return &FilterIter[T]{iter, inverse, false}
3844
}
3945

4046
type FilterMapIter[T any, U any] struct {
41-
itr Iterator[T]
42-
fn func(T) option.Option[U]
47+
iter Iterator[T]
48+
fn func(T) option.Option[U]
49+
exhausted bool
4350
}
4451

45-
func (flt *FilterMapIter[T, U]) Next() option.Option[U] {
52+
func (iter *FilterMapIter[T, U]) Next() option.Option[U] {
53+
if iter.exhausted {
54+
return option.None[U]()
55+
}
56+
4657
for {
47-
val, ok := flt.itr.Next().Value()
58+
val, ok := iter.iter.Next().Value()
4859
if !ok {
60+
iter.exhausted = true
4961
return option.None[U]()
5062
}
51-
result := flt.fn(val)
63+
64+
result := iter.fn(val)
5265
if result.IsSome() {
5366
return result
5467
}
@@ -61,5 +74,5 @@ var _ Iterator[struct{}] = new(FilterMapIter[struct{}, struct{}])
6174
// it allows the user to filter elements by returning a None variant and to transform
6275
// elements by returning a Some variant.
6376
func FilterMap[T any, U any](itr Iterator[T], fun func(T) option.Option[U]) Iterator[U] {
64-
return &FilterMapIter[T, U]{itr, fun}
77+
return &FilterMapIter[T, U]{itr, fun, false}
6578
}

iter/filter_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"testing"
66

77
"github.com/BooleanCat/go-functional/internal/assert"
8+
"github.com/BooleanCat/go-functional/internal/fakes"
89
"github.com/BooleanCat/go-functional/iter"
910
"github.com/BooleanCat/go-functional/iter/filters"
1011
"github.com/BooleanCat/go-functional/option"
@@ -65,13 +66,31 @@ func TestFilterEmpty(t *testing.T) {
6566
assert.True(t, evens.Next().IsNone())
6667
}
6768

69+
func TestFilterExhausted(t *testing.T) {
70+
delegate := new(fakes.Iterator[int])
71+
ones := iter.Filter[int](delegate, func(_ int) bool { return true })
72+
73+
assert.True(t, ones.Next().IsNone())
74+
assert.True(t, ones.Next().IsNone())
75+
assert.Equal(t, delegate.NextCallCount(), 1)
76+
}
77+
6878
func TestExclude(t *testing.T) {
6979
isEven := func(a int) bool { return a%2 == 0 }
7080
evens := iter.Exclude[int](iter.Count(), isEven)
7181
assert.Equal(t, evens.Next().Unwrap(), 1)
7282
assert.Equal(t, evens.Next().Unwrap(), 3)
7383
}
7484

85+
func TestExcludeExhausted(t *testing.T) {
86+
delegate := new(fakes.Iterator[int])
87+
ones := iter.Exclude[int](delegate, func(_ int) bool { return false })
88+
89+
assert.True(t, ones.Next().IsNone())
90+
assert.True(t, ones.Next().IsNone())
91+
assert.Equal(t, delegate.NextCallCount(), 1)
92+
}
93+
7594
func TestFilterMap(t *testing.T) {
7695
selectEvenAndDouble := func(x int) option.Option[int] {
7796
if x%2 > 0 {
@@ -106,3 +125,12 @@ func TestFilterMapEmpty(t *testing.T) {
106125

107126
assert.Empty(t, iter.Collect(fltMapEmpty))
108127
}
128+
129+
func TestFilterMapExhausted(t *testing.T) {
130+
delegate := new(fakes.Iterator[int])
131+
ones := iter.FilterMap[int](delegate, func(_ int) option.Option[int] { return option.Some(1) })
132+
133+
assert.True(t, ones.Next().IsNone())
134+
assert.True(t, ones.Next().IsNone())
135+
assert.Equal(t, delegate.NextCallCount(), 1)
136+
}

0 commit comments

Comments
 (0)