Skip to content

Commit

Permalink
Merge branch 'add_uniq'
Browse files Browse the repository at this point in the history
  • Loading branch information
BooleanCat committed Dec 18, 2024
2 parents 9abdc48 + e61d8d3 commit b5f639d
Show file tree
Hide file tree
Showing 3 changed files with 92 additions and 0 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,23 @@ values, err := it.TryCollect(itx.FromSlice([]int{"foo", "bar"}).FilterError(isFo
values, err := it.TryCollect(itx.FromSlice([]int{"foo", "bar"}).ExcludeError(isFoo))
```

### FilterUnique

FilterUnique yields only the unique values from an iterator.

```go
values := it.FilterUnique(slices.Values([]int{1, 2, 2, 3, 3, 3, 4}))
```

<!-- prettier-ignore -->
> [!WARNING]
> Unique values are stored in memory until the iterator is exhausted. Large iterators with
> many unique values may use a large amount of memory.
<!-- prettier-ignore -->
> [!NOTE]
> The `itx` package does not contain `FilterUnique` due to limitations with Go's type system.
### Integers

Integers yields all integers in the range [start, stop) with the given step.
Expand Down
23 changes: 23 additions & 0 deletions it/filter_unique.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package it

import "iter"

// FilterUnique yields all the unique values from an iterator.
//
// Note: All unique values seen from an iterator are stored in memory.
func FilterUnique[V comparable](iterator func(func(V) bool)) iter.Seq[V] {
return func(yield func(V) bool) {
seen := make(map[V]struct{})

for value := range iterator {
if _, ok := seen[value]; ok {
continue
}

seen[value] = struct{}{}
if !yield(value) {
return
}
}
}
}
52 changes: 52 additions & 0 deletions it/filter_unique_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package it_test

import (
"fmt"
"slices"
"testing"

"github.com/BooleanCat/go-functional/v2/internal/assert"
"github.com/BooleanCat/go-functional/v2/it"
)

func ExampleFilterUnique() {
for number := range it.FilterUnique(slices.Values([]int{1, 2, 2, 3, 3, 3, 4})) {
fmt.Println(number)
}

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

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

assert.Empty[int](t, slices.Collect(it.Exhausted[int]()))
}

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

iterator := it.FilterUnique(slices.Values([]int{100, 200, 300}))

iterator(func(v int) bool {
return false
})
}

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

numbers := slices.Collect(it.FilterUnique(slices.Values([]int{1, 2, 3})))
assert.SliceEqual(t, []int{1, 2, 3}, numbers)
}

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

strings := slices.Collect(it.FilterUnique(slices.Values([]string{"hello", "world", "hello", "world", "hello"})))
assert.SliceEqual(t, []string{"hello", "world"}, strings)
}

0 comments on commit b5f639d

Please sign in to comment.