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
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,22 @@ itx.FromMap(map[int]string{1: "one", 2: "two", 3: "three"}).Exclude(isOne)
> [!TIP]
> The [filter package](it/filter/filter.go) contains some simple, pre-defined predicate functions.

### FilterError & ExcludeError

Similar to [Filter and Exclude](#filter), these functions filter values from an iterator using a
predicate that may return an error.

```go
isFoo := func(s string) (bool, error) { return s == "foo", nil }

values, err := it.TryCollect(it.FilterError(slices.Values([]string{"foo", "bar"}), isFoo))
values, err := it.TryCollect(it.ExcludeError(slices.Values([]string{"foo", "bar"}), isFoo))

// Chainable
values, err := it.TryCollect(itx.FromSlice([]int{"foo", "bar"}).FilterError(isFoo))
values, err := it.TryCollect(itx.FromSlice([]int{"foo", "bar"}).ExcludeError(isFoo))
```

### Integers

Integers yields all integers in the range [start, stop) with the given step.
Expand Down
32 changes: 32 additions & 0 deletions it/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,31 @@ func Exclude2[V, W any](delegate func(func(V, W) bool), predicate func(V, W) boo
return Filter2(delegate, not2(predicate))
}

// FilterError yields values from an iterator that satisfy a predicate where
// the predicate can return an error.
func FilterError[V any](delegate func(func(V) bool), predicate func(V) (bool, error)) iter.Seq2[V, error] {
return func(yield func(V, error) bool) {
for value := range delegate {
if ok, err := predicate(value); err != nil {
var zero V
if !yield(zero, err) {
return
}
} else if ok {
if !yield(value, nil) {
return
}
}
}
}
}

// ExcludeError yields values from an iterator that do not satisfy a predicate
// where the predicate can return an error.
func ExcludeError[V any](delegate func(func(V) bool), predicate func(V) (bool, error)) iter.Seq2[V, error] {
return FilterError(delegate, notError(predicate))
}

func not[V any](predicate func(V) bool) func(V) bool {
return func(value V) bool {
return !predicate(value)
Expand All @@ -49,3 +74,10 @@ func not2[V, W any](predicate func(V, W) bool) func(V, W) bool {
return !predicate(k, v)
}
}

func notError[V any](predicate func(V) (bool, error)) func(V) (bool, error) {
return func(value V) (bool, error) {
ok, err := predicate(value)
return !ok, err
}
}
63 changes: 63 additions & 0 deletions it/filter_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package it_test

import (
"errors"
"fmt"
"maps"
"slices"
Expand Down Expand Up @@ -96,3 +97,65 @@ func TestExclude2Empty(t *testing.T) {

assert.Equal(t, len(maps.Collect(it.Exclude2(it.Exhausted2[int, int](), filter.Passthrough2))), 0)
}

func ExampleFilterError() {
isFoo := func(s string) (bool, error) { return s == "foo", nil }

values := slices.Values([]string{"foo", "bar", "foo"})

foos, err := it.TryCollect(it.FilterError(values, isFoo))
fmt.Println(foos, err)

// Output: [foo foo] <nil>
}

func TestFilterErrorYieldsFalse(t *testing.T) {
t.Parallel()

passthrough := func(s string) (bool, error) { return true, nil }

seq := it.FilterError(slices.Values([]string{"foo", "bar", "foo"}), passthrough)

seq(func(v string, _ error) bool {
return false
})
}

func TestFilterErrorError(t *testing.T) {
t.Parallel()

alwaysError := func(string) (bool, error) { return false, errors.New("oops") }

_, err := it.TryCollect(it.FilterError(slices.Values([]string{"foo"}), alwaysError))
assert.Equal(t, err.Error(), "oops")
}

func TestFilterErrorErrorSometimes(t *testing.T) {
t.Parallel()

sometimesError := func(s string) (bool, error) {
if s == "foo" {
return true, errors.New("oops")
}

return true, nil
}

values, errs := it.Collect2(it.FilterError(slices.Values([]string{"foo", "bar"}), sometimesError))
assert.SliceEqual(t, values, []string{"", "bar"})

assert.Equal(t, len(errs), 2)
assert.Equal(t, errs[0].Error(), "oops")
assert.Equal(t, errs[1], nil)
}

func ExampleExcludeError() {
isFoo := func(s string) (bool, error) { return s == "foo", nil }

values := slices.Values([]string{"foo", "bar", "foo"})

bars, err := it.TryCollect(it.ExcludeError(values, isFoo))
fmt.Println(bars, err)

// Output: [bar] <nil>
}
12 changes: 12 additions & 0 deletions it/itx/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,15 @@ func (iterator Iterator2[V, W]) Filter(predicate func(V, W) bool) Iterator2[V, W
func (iterator Iterator2[V, W]) Exclude(predicate func(V, W) bool) Iterator2[V, W] {
return Iterator2[V, W](it.Exclude2(iterator, predicate))
}

// FilterError is a convenience method for chaining [it.FilterError] on
// [Iterator]s.
func (iterator Iterator[V]) FilterError(predicate func(V) (bool, error)) Iterator2[V, error] {
return Iterator2[V, error](it.FilterError(iterator, predicate))
}

// ExcludeError is a convenience method for chaining [it.ExcludeError] on
// [Iterator]s.
func (iterator Iterator[V]) ExcludeError(predicate func(V) (bool, error)) Iterator2[V, error] {
return Iterator2[V, error](it.ExcludeError(iterator, predicate))
}
23 changes: 23 additions & 0 deletions it/itx/filter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package itx_test
import (
"fmt"

"github.com/BooleanCat/go-functional/v2/it"
"github.com/BooleanCat/go-functional/v2/it/filter"
"github.com/BooleanCat/go-functional/v2/it/itx"
)
Expand Down Expand Up @@ -49,3 +50,25 @@ func ExampleIterator2_Exclude() {

// Output: 3 three
}

func ExampleIterator_FilterError() {
isEven := func(n int) (bool, error) { return n%2 == 0, nil }

evens, err := it.TryCollect(itx.FromSlice([]int{1, 2, 3, 4, 5}).FilterError(isEven))
if err == nil {
fmt.Println(evens)
}

// Output: [2 4]
}

func ExampleIterator_ExcludeError() {
isEven := func(n int) (bool, error) { return n%2 == 0, nil }

odds, err := it.TryCollect(itx.FromSlice([]int{1, 2, 3, 4, 5}).ExcludeError(isEven))
if err == nil {
fmt.Println(odds)
}

// Output: [1 3 5]
}