Skip to content

Commit b5f639d

Browse files
committed
Merge branch 'add_uniq'
2 parents 9abdc48 + e61d8d3 commit b5f639d

File tree

3 files changed

+92
-0
lines changed

3 files changed

+92
-0
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,23 @@ values, err := it.TryCollect(itx.FromSlice([]int{"foo", "bar"}).FilterError(isFo
591591
values, err := it.TryCollect(itx.FromSlice([]int{"foo", "bar"}).ExcludeError(isFoo))
592592
```
593593

594+
### FilterUnique
595+
596+
FilterUnique yields only the unique values from an iterator.
597+
598+
```go
599+
values := it.FilterUnique(slices.Values([]int{1, 2, 2, 3, 3, 3, 4}))
600+
```
601+
602+
<!-- prettier-ignore -->
603+
> [!WARNING]
604+
> Unique values are stored in memory until the iterator is exhausted. Large iterators with
605+
> many unique values may use a large amount of memory.
606+
607+
<!-- prettier-ignore -->
608+
> [!NOTE]
609+
> The `itx` package does not contain `FilterUnique` due to limitations with Go's type system.
610+
594611
### Integers
595612

596613
Integers yields all integers in the range [start, stop) with the given step.

it/filter_unique.go

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package it
2+
3+
import "iter"
4+
5+
// FilterUnique yields all the unique values from an iterator.
6+
//
7+
// Note: All unique values seen from an iterator are stored in memory.
8+
func FilterUnique[V comparable](iterator func(func(V) bool)) iter.Seq[V] {
9+
return func(yield func(V) bool) {
10+
seen := make(map[V]struct{})
11+
12+
for value := range iterator {
13+
if _, ok := seen[value]; ok {
14+
continue
15+
}
16+
17+
seen[value] = struct{}{}
18+
if !yield(value) {
19+
return
20+
}
21+
}
22+
}
23+
}

it/filter_unique_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package it_test
2+
3+
import (
4+
"fmt"
5+
"slices"
6+
"testing"
7+
8+
"github.com/BooleanCat/go-functional/v2/internal/assert"
9+
"github.com/BooleanCat/go-functional/v2/it"
10+
)
11+
12+
func ExampleFilterUnique() {
13+
for number := range it.FilterUnique(slices.Values([]int{1, 2, 2, 3, 3, 3, 4})) {
14+
fmt.Println(number)
15+
}
16+
17+
// Output:
18+
// 1
19+
// 2
20+
// 3
21+
// 4
22+
}
23+
24+
func TestFilterUniqueEmpty(t *testing.T) {
25+
t.Parallel()
26+
27+
assert.Empty[int](t, slices.Collect(it.Exhausted[int]()))
28+
}
29+
30+
func TestFilterUniqueYieldFalse(t *testing.T) {
31+
t.Parallel()
32+
33+
iterator := it.FilterUnique(slices.Values([]int{100, 200, 300}))
34+
35+
iterator(func(v int) bool {
36+
return false
37+
})
38+
}
39+
40+
func TestFilterUniqueWithNoDuplicates(t *testing.T) {
41+
t.Parallel()
42+
43+
numbers := slices.Collect(it.FilterUnique(slices.Values([]int{1, 2, 3})))
44+
assert.SliceEqual(t, []int{1, 2, 3}, numbers)
45+
}
46+
47+
func TestFilterUniqueWithDuplicates(t *testing.T) {
48+
t.Parallel()
49+
50+
strings := slices.Collect(it.FilterUnique(slices.Values([]string{"hello", "world", "hello", "world", "hello"})))
51+
assert.SliceEqual(t, []string{"hello", "world"}, strings)
52+
}

0 commit comments

Comments
 (0)