1
0
mirror of https://github.com/IBM/fp-go.git synced 2025-12-23 23:51:14 +02:00

Compare commits

...

1 Commits

Author SHA1 Message Date
Dr. Carsten Leue
49227551b6 fix: more iter methods
Signed-off-by: Dr. Carsten Leue <carsten.leue@de.ibm.com>
2025-12-22 15:03:47 +01:00
11 changed files with 2382 additions and 0 deletions

View File

@@ -34,6 +34,8 @@ import (
// 3. Filtering to keep only pairs where the boolean (tail) is true
// 4. Extracting the original values (head) from the filtered pairs
//
// RxJS Equivalent: Similar to combining [zip] with [filter] - https://rxjs.dev/api/operators/zip
//
// Type Parameters:
// - U: The type of elements in the sequence to be filtered
//

96
v2/iterator/iter/cycle.go Normal file
View File

@@ -0,0 +1,96 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package iter
// Cycle creates a sequence that repeats the elements of the input sequence indefinitely.
//
// This function takes a finite sequence and creates an infinite sequence by cycling through
// all elements repeatedly. When the end of the input sequence is reached, it starts over
// from the beginning, continuing this pattern forever.
//
// RxJS Equivalent: [repeat] - https://rxjs.dev/api/operators/repeat
//
// WARNING: This creates an INFINITE sequence for non-empty inputs. It must be used with
// operations that limit the output (such as Take, First, or early termination in iteration)
// to avoid infinite loops.
//
// If the input sequence is empty, Cycle returns an empty sequence immediately. It does NOT
// loop indefinitely - the result is simply an empty sequence.
//
// The operation is lazy - elements are only generated as they are consumed. The input sequence
// is re-iterated each time the cycle completes, so any side effects in the source sequence
// will be repeated.
//
// Type Parameters:
// - U: The type of elements in the sequence
//
// Parameters:
// - ma: The input sequence to cycle through. Should be finite.
//
// Returns:
// - An infinite sequence that repeats the elements of the input sequence
//
// Example - Basic cycling with Take:
//
// seq := From(1, 2, 3)
// cycled := Cycle(seq)
// result := Take[int](7)(cycled)
// // yields: 1, 2, 3, 1, 2, 3, 1
//
// Example - Cycling strings:
//
// seq := From("A", "B", "C")
// cycled := Cycle(seq)
// result := Take[string](5)(cycled)
// // yields: "A", "B", "C", "A", "B"
//
// Example - Using with First:
//
// seq := From(10, 20, 30)
// cycled := Cycle(seq)
// first := First(cycled)
// // returns: Some(10)
//
// Example - Combining with filter and take:
//
// seq := From(1, 2, 3, 4, 5)
// cycled := Cycle(seq)
// evens := MonadFilter(cycled, func(x int) bool { return x%2 == 0 })
// result := Take[int](5)(evens)
// // yields: 2, 4, 2, 4, 2 (cycles through even numbers)
//
// Example - Empty sequence (returns empty, does not loop):
//
// seq := Empty[int]()
// cycled := Cycle(seq)
// result := Take[int](10)(cycled)
// // yields: nothing (empty sequence, terminates immediately)
func Cycle[U any](ma Seq[U]) Seq[U] {
return func(yield func(U) bool) {
for {
isEmpty := true
for u := range ma {
if !yield(u) {
return
}
isEmpty = false
}
if isEmpty {
return
}
}
}
}

View File

@@ -0,0 +1,611 @@
// Copyright (c) 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package iter
import (
"fmt"
"testing"
N "github.com/IBM/fp-go/v2/number"
O "github.com/IBM/fp-go/v2/option"
"github.com/stretchr/testify/assert"
)
// TestCycleBasic tests basic Cycle functionality with Take
func TestCycleBasic(t *testing.T) {
t.Run("cycles through integer sequence", func(t *testing.T) {
seq := From(1, 2, 3)
cycled := Cycle(seq)
result := toSlice(Take[int](7)(cycled))
assert.Equal(t, []int{1, 2, 3, 1, 2, 3, 1}, result)
})
t.Run("cycles through string sequence", func(t *testing.T) {
seq := From("A", "B", "C")
cycled := Cycle(seq)
result := toSlice(Take[string](8)(cycled))
assert.Equal(t, []string{"A", "B", "C", "A", "B", "C", "A", "B"}, result)
})
t.Run("cycles through single element", func(t *testing.T) {
seq := From(42)
cycled := Cycle(seq)
result := toSlice(Take[int](5)(cycled))
assert.Equal(t, []int{42, 42, 42, 42, 42}, result)
})
t.Run("cycles through two elements", func(t *testing.T) {
seq := From(true, false)
cycled := Cycle(seq)
result := toSlice(Take[bool](6)(cycled))
assert.Equal(t, []bool{true, false, true, false, true, false}, result)
})
t.Run("takes exact multiple of cycle length", func(t *testing.T) {
seq := From(1, 2, 3)
cycled := Cycle(seq)
result := toSlice(Take[int](9)(cycled))
assert.Equal(t, []int{1, 2, 3, 1, 2, 3, 1, 2, 3}, result)
})
t.Run("takes less than one cycle", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
cycled := Cycle(seq)
result := toSlice(Take[int](3)(cycled))
assert.Equal(t, []int{1, 2, 3}, result)
})
}
// TestCycleEmpty tests Cycle with empty sequences
func TestCycleEmpty(t *testing.T) {
t.Run("cycles empty sequence produces nothing", func(t *testing.T) {
seq := Empty[int]()
cycled := Cycle(seq)
result := toSlice(Take[int](10)(cycled))
assert.Empty(t, result)
})
t.Run("cycles empty string sequence", func(t *testing.T) {
seq := Empty[string]()
cycled := Cycle(seq)
result := toSlice(Take[string](5)(cycled))
assert.Empty(t, result)
})
t.Run("take zero from cycled sequence", func(t *testing.T) {
seq := From(1, 2, 3)
cycled := Cycle(seq)
result := toSlice(Take[int](0)(cycled))
assert.Empty(t, result)
})
}
// TestCycleWithComplexTypes tests Cycle with complex data types
func TestCycleWithComplexTypes(t *testing.T) {
type Person struct {
Name string
Age int
}
t.Run("cycles structs", func(t *testing.T) {
seq := From(
Person{"Alice", 30},
Person{"Bob", 25},
)
cycled := Cycle(seq)
result := toSlice(Take[Person](5)(cycled))
expected := []Person{
{"Alice", 30},
{"Bob", 25},
{"Alice", 30},
{"Bob", 25},
{"Alice", 30},
}
assert.Equal(t, expected, result)
})
t.Run("cycles pointers", func(t *testing.T) {
p1 := &Person{"Alice", 30}
p2 := &Person{"Bob", 25}
seq := From(p1, p2)
cycled := Cycle(seq)
result := toSlice(Take[*Person](4)(cycled))
assert.Equal(t, []*Person{p1, p2, p1, p2}, result)
})
t.Run("cycles slices", func(t *testing.T) {
seq := From([]int{1, 2}, []int{3, 4})
cycled := Cycle(seq)
result := toSlice(Take[[]int](5)(cycled))
expected := [][]int{{1, 2}, {3, 4}, {1, 2}, {3, 4}, {1, 2}}
assert.Equal(t, expected, result)
})
}
// TestCycleWithFirst tests Cycle with First operation
func TestCycleWithFirst(t *testing.T) {
t.Run("gets first element from cycled sequence", func(t *testing.T) {
seq := From(10, 20, 30)
cycled := Cycle(seq)
first := First(cycled)
assert.Equal(t, O.Of(10), first)
})
t.Run("gets first from single element cycle", func(t *testing.T) {
seq := From(42)
cycled := Cycle(seq)
first := First(cycled)
assert.Equal(t, O.Of(42), first)
})
t.Run("gets none from empty cycle", func(t *testing.T) {
seq := Empty[int]()
cycled := Cycle(seq)
first := First(cycled)
assert.Equal(t, O.None[int](), first)
})
}
// TestCycleWithChainedOperations tests Cycle with other operations
func TestCycleWithChainedOperations(t *testing.T) {
t.Run("cycle then map", func(t *testing.T) {
seq := From(1, 2, 3)
cycled := Cycle(seq)
mapped := MonadMap(cycled, N.Mul(10))
result := toSlice(Take[int](7)(mapped))
assert.Equal(t, []int{10, 20, 30, 10, 20, 30, 10}, result)
})
t.Run("cycle then filter", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
cycled := Cycle(seq)
filtered := MonadFilter(cycled, func(x int) bool { return x%2 == 0 })
result := toSlice(Take[int](6)(filtered))
assert.Equal(t, []int{2, 4, 2, 4, 2, 4}, result)
})
t.Run("map then cycle", func(t *testing.T) {
seq := From(1, 2, 3)
mapped := MonadMap(seq, N.Mul(2))
cycled := Cycle(mapped)
result := toSlice(Take[int](7)(cycled))
assert.Equal(t, []int{2, 4, 6, 2, 4, 6, 2}, result)
})
t.Run("filter then cycle", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5, 6)
filtered := MonadFilter(seq, func(x int) bool { return x%2 == 0 })
cycled := Cycle(filtered)
result := toSlice(Take[int](7)(cycled))
assert.Equal(t, []int{2, 4, 6, 2, 4, 6, 2}, result)
})
t.Run("cycle with multiple takes", func(t *testing.T) {
seq := From(1, 2, 3)
cycled := Cycle(seq)
taken1 := Take[int](10)(cycled)
taken2 := Take[int](5)(taken1)
result := toSlice(taken2)
assert.Equal(t, []int{1, 2, 3, 1, 2}, result)
})
}
// TestCycleWithReplicate tests Cycle with Replicate
func TestCycleWithReplicate(t *testing.T) {
t.Run("cycles replicated values", func(t *testing.T) {
seq := Replicate(3, "X")
cycled := Cycle(seq)
result := toSlice(Take[string](7)(cycled))
assert.Equal(t, []string{"X", "X", "X", "X", "X", "X", "X"}, result)
})
t.Run("cycles single replicated value", func(t *testing.T) {
seq := Replicate(1, 99)
cycled := Cycle(seq)
result := toSlice(Take[int](5)(cycled))
assert.Equal(t, []int{99, 99, 99, 99, 99}, result)
})
}
// TestCycleWithMakeBy tests Cycle with MakeBy
func TestCycleWithMakeBy(t *testing.T) {
t.Run("cycles generated sequence", func(t *testing.T) {
seq := MakeBy(3, func(i int) int { return i * i })
cycled := Cycle(seq)
result := toSlice(Take[int](8)(cycled))
assert.Equal(t, []int{0, 1, 4, 0, 1, 4, 0, 1}, result)
})
t.Run("cycles single generated element", func(t *testing.T) {
seq := MakeBy(1, func(i int) int { return i + 10 })
cycled := Cycle(seq)
result := toSlice(Take[int](4)(cycled))
assert.Equal(t, []int{10, 10, 10, 10}, result)
})
}
// TestCycleWithPrependAppend tests Cycle with Prepend and Append
func TestCycleWithPrependAppend(t *testing.T) {
t.Run("cycle prepended sequence", func(t *testing.T) {
seq := From(2, 3)
prepended := Prepend(1)(seq)
cycled := Cycle(prepended)
result := toSlice(Take[int](7)(cycled))
assert.Equal(t, []int{1, 2, 3, 1, 2, 3, 1}, result)
})
t.Run("cycle appended sequence", func(t *testing.T) {
seq := From(1, 2)
appended := Append(3)(seq)
cycled := Cycle(appended)
result := toSlice(Take[int](7)(cycled))
assert.Equal(t, []int{1, 2, 3, 1, 2, 3, 1}, result)
})
}
// TestCycleWithFlatten tests Cycle with Flatten
func TestCycleWithFlatten(t *testing.T) {
t.Run("cycles flattened sequence", func(t *testing.T) {
nested := From(From(1, 2), From(3))
flattened := Flatten(nested)
cycled := Cycle(flattened)
result := toSlice(Take[int](7)(cycled))
assert.Equal(t, []int{1, 2, 3, 1, 2, 3, 1}, result)
})
}
// TestCycleWithChain tests Cycle with Chain
func TestCycleWithChain(t *testing.T) {
t.Run("cycles chained sequence", func(t *testing.T) {
seq := From(1, 2)
chained := MonadChain(seq, func(x int) Seq[int] {
return From(x, x*10)
})
cycled := Cycle(chained)
result := toSlice(Take[int](10)(cycled))
assert.Equal(t, []int{1, 10, 2, 20, 1, 10, 2, 20, 1, 10}, result)
})
}
// TestCycleEarlyTermination tests that Cycle respects early termination
func TestCycleEarlyTermination(t *testing.T) {
t.Run("terminates when yield returns false", func(t *testing.T) {
seq := From(1, 2, 3)
cycled := Cycle(seq)
count := 0
for v := range cycled {
count++
if v == 2 && count > 2 {
break
}
}
// Should have stopped at the second occurrence of 2
assert.Equal(t, 5, count) // 1, 2, 3, 1, 2
})
t.Run("take limits infinite cycle", func(t *testing.T) {
seq := From(1, 2, 3)
cycled := Cycle(seq)
taken := Take[int](100)(cycled)
result := toSlice(taken)
assert.Len(t, result, 100)
// Verify pattern repeats correctly
for i := 0; i < 100; i++ {
expected := (i % 3) + 1
assert.Equal(t, expected, result[i])
}
})
}
// TestCycleLargeSequence tests Cycle with larger sequences
func TestCycleLargeSequence(t *testing.T) {
t.Run("cycles large sequence", func(t *testing.T) {
data := make([]int, 10)
for i := range data {
data[i] = i
}
seq := From(data...)
cycled := Cycle(seq)
result := toSlice(Take[int](25)(cycled))
assert.Len(t, result, 25)
// Verify first cycle
for i := 0; i < 10; i++ {
assert.Equal(t, i, result[i])
}
// Verify second cycle
for i := 10; i < 20; i++ {
assert.Equal(t, i-10, result[i])
}
// Verify partial third cycle
for i := 20; i < 25; i++ {
assert.Equal(t, i-20, result[i])
}
})
}
// TestCycleWithReduce tests Cycle with Reduce (limited by Take)
func TestCycleWithReduce(t *testing.T) {
t.Run("reduces limited cycled sequence", func(t *testing.T) {
seq := From(1, 2, 3)
cycled := Cycle(seq)
limited := Take[int](10)(cycled)
sum := MonadReduce(limited, func(acc, x int) int { return acc + x }, 0)
// 1+2+3+1+2+3+1+2+3+1 = 19
assert.Equal(t, 19, sum)
})
}
// TestCycleEdgeCases tests edge cases
func TestCycleEdgeCases(t *testing.T) {
t.Run("cycle with very long take", func(t *testing.T) {
seq := From(1, 2)
cycled := Cycle(seq)
result := toSlice(Take[int](1000)(cycled))
assert.Len(t, result, 1000)
// Verify pattern
for i := 0; i < 1000; i++ {
expected := (i % 2) + 1
assert.Equal(t, expected, result[i])
}
})
t.Run("cycle single element many times", func(t *testing.T) {
seq := From(7)
cycled := Cycle(seq)
result := toSlice(Take[int](100)(cycled))
assert.Len(t, result, 100)
for _, v := range result {
assert.Equal(t, 7, v)
}
})
}
// Benchmark tests
func BenchmarkCycle(b *testing.B) {
seq := From(1, 2, 3, 4, 5)
cycled := Cycle(seq)
b.ResetTimer()
for i := 0; i < b.N; i++ {
taken := Take[int](100)(cycled)
for range taken {
}
}
}
func BenchmarkCycleSingleElement(b *testing.B) {
seq := From(42)
cycled := Cycle(seq)
b.ResetTimer()
for i := 0; i < b.N; i++ {
taken := Take[int](100)(cycled)
for range taken {
}
}
}
func BenchmarkCycleWithMap(b *testing.B) {
seq := From(1, 2, 3, 4, 5)
cycled := Cycle(seq)
b.ResetTimer()
for i := 0; i < b.N; i++ {
mapped := MonadMap(cycled, N.Mul(2))
taken := Take[int](100)(mapped)
for range taken {
}
}
}
func BenchmarkCycleWithFilter(b *testing.B) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
cycled := Cycle(seq)
b.ResetTimer()
for i := 0; i < b.N; i++ {
filtered := MonadFilter(cycled, func(x int) bool { return x%2 == 0 })
taken := Take[int](50)(filtered)
for range taken {
}
}
}
// Example tests for documentation
func ExampleCycle() {
seq := From(1, 2, 3)
cycled := Cycle(seq)
result := Take[int](7)(cycled)
for v := range result {
fmt.Printf("%d ", v)
}
// Output: 1 2 3 1 2 3 1
}
func ExampleCycle_singleElement() {
seq := From("X")
cycled := Cycle(seq)
result := Take[string](5)(cycled)
for v := range result {
fmt.Printf("%s ", v)
}
// Output: X X X X X
}
func ExampleCycle_withFirst() {
seq := From(10, 20, 30)
cycled := Cycle(seq)
first := First(cycled)
if value, ok := O.Unwrap(first); ok {
fmt.Printf("First: %d\n", value)
}
// Output: First: 10
}
func ExampleCycle_withFilter() {
seq := From(1, 2, 3, 4, 5)
cycled := Cycle(seq)
evens := MonadFilter(cycled, func(x int) bool { return x%2 == 0 })
result := Take[int](6)(evens)
for v := range result {
fmt.Printf("%d ", v)
}
// Output: 2 4 2 4 2 4
}
func ExampleCycle_withMap() {
seq := From(1, 2, 3)
cycled := Cycle(seq)
doubled := MonadMap(cycled, N.Mul(2))
result := Take[int](7)(doubled)
for v := range result {
fmt.Printf("%d ", v)
}
// Output: 2 4 6 2 4 6 2
}
func ExampleCycle_empty() {
seq := Empty[int]()
cycled := Cycle(seq)
result := Take[int](5)(cycled)
count := 0
for range result {
count++
}
fmt.Printf("Count: %d\n", count)
// Output: Count: 0
}
func ExampleCycle_exactMultiple() {
seq := From("A", "B", "C")
cycled := Cycle(seq)
result := Take[string](9)(cycled)
for v := range result {
fmt.Printf("%s ", v)
}
// Output: A B C A B C A B C
}
// TestCycleWithZip tests Cycle combined with Zip operator
func TestCycleWithZip(t *testing.T) {
t.Run("zip infinite cycled sequence with finite sequence", func(t *testing.T) {
// Create an infinite sequence by cycling
infinite := Cycle(From(1, 2, 3))
// Create a finite sequence
finite := From("a", "b", "c", "d", "e")
// Zip them together - should stop when finite sequence ends
zipped := MonadZip(infinite, finite)
// Convert to slice for verification
result := make([]struct {
num int
str string
}, 0)
for num, str := range zipped {
result = append(result, struct {
num int
str string
}{num, str})
}
// Should have 5 pairs (limited by finite sequence)
assert.Len(t, result, 5)
assert.Equal(t, 1, result[0].num)
assert.Equal(t, "a", result[0].str)
assert.Equal(t, 2, result[1].num)
assert.Equal(t, "b", result[1].str)
assert.Equal(t, 3, result[2].num)
assert.Equal(t, "c", result[2].str)
assert.Equal(t, 1, result[3].num) // Cycle repeats
assert.Equal(t, "d", result[3].str)
assert.Equal(t, 2, result[4].num)
assert.Equal(t, "e", result[4].str)
})
t.Run("zip finite sequence with infinite cycled sequence", func(t *testing.T) {
// Reverse order: finite first, infinite second
finite := From(10, 20, 30)
infinite := Cycle(From("X", "Y"))
zipped := MonadZip(finite, infinite)
result := make([]struct {
num int
str string
}, 0)
for num, str := range zipped {
result = append(result, struct {
num int
str string
}{num, str})
}
// Should have 3 pairs (limited by finite sequence)
assert.Len(t, result, 3)
assert.Equal(t, 10, result[0].num)
assert.Equal(t, "X", result[0].str)
assert.Equal(t, 20, result[1].num)
assert.Equal(t, "Y", result[1].str)
assert.Equal(t, 30, result[2].num)
assert.Equal(t, "X", result[2].str) // Cycle repeats
})
t.Run("zip two cycled sequences with take", func(t *testing.T) {
// Both sequences are infinite, so we need Take to limit
cycle1 := Cycle(From(1, 2))
cycle2 := Cycle(From("a", "b", "c"))
zipped := MonadZip(cycle1, cycle2)
// Use Take to limit the infinite result
count := 0
result := make([]struct {
num int
str string
}, 0)
for num, str := range zipped {
result = append(result, struct {
num int
str string
}{num, str})
count++
if count >= 7 {
break
}
}
assert.Len(t, result, 7)
// Verify the pattern
assert.Equal(t, 1, result[0].num)
assert.Equal(t, "a", result[0].str)
assert.Equal(t, 2, result[1].num)
assert.Equal(t, "b", result[1].str)
assert.Equal(t, 1, result[2].num) // cycle1 repeats
assert.Equal(t, "c", result[2].str)
assert.Equal(t, 2, result[3].num)
assert.Equal(t, "a", result[3].str) // cycle2 repeats
})
}

View File

@@ -23,6 +23,8 @@ import "github.com/IBM/fp-go/v2/option"
// contains at least one element, it returns Some(element). If the iterator is empty,
// it returns None. The function consumes only the first element of the iterator.
//
// RxJS Equivalent: [first] - https://rxjs.dev/api/operators/first
//
// Type Parameters:
// - U: The type of elements in the iterator
//

View File

@@ -81,6 +81,8 @@ func Of2[K, A any](k K, a A) Seq2[K, A] {
// MonadMap transforms each element in a sequence using the provided function.
// This is the monadic version that takes the sequence as the first parameter.
//
// RxJS Equivalent: [map] - https://rxjs.dev/api/operators/map
//
// Example:
//
// seq := From(1, 2, 3)
@@ -183,6 +185,8 @@ func MapWithKey[K, A, B any](f func(K, A) B) Operator2[K, A, B] {
// MonadFilter returns a sequence containing only elements that satisfy the predicate.
//
// RxJS Equivalent: [filter] - https://rxjs.dev/api/operators/filter
//
// Example:
//
// seq := From(1, 2, 3, 4, 5)
@@ -425,6 +429,8 @@ func FilterMapWithKey[K, A, B any](f func(K, A) Option[B]) Operator2[K, A, B] {
// MonadChain applies a function that returns a sequence to each element and flattens the results.
// This is the monadic bind operation (flatMap).
//
// RxJS Equivalent: [mergeMap/flatMap] - https://rxjs.dev/api/operators/mergeMap
//
// Example:
//
// seq := From(1, 2, 3)
@@ -461,6 +467,8 @@ func Chain[A, B any](f func(A) Seq[B]) Operator[A, B] {
// Flatten flattens a sequence of sequences into a single sequence.
//
// RxJS Equivalent: [mergeAll] - https://rxjs.dev/api/operators/mergeAll
//
// Example:
//
// nested := From(From(1, 2), From(3, 4), From(5))
@@ -563,6 +571,8 @@ func Replicate[A any](n int, a A) Seq[A] {
// MonadReduce reduces a sequence to a single value by applying a function to each element
// and an accumulator, starting with an initial value.
//
// RxJS Equivalent: [reduce] - https://rxjs.dev/api/operators/reduce
//
// Example:
//
// seq := From(1, 2, 3, 4, 5)
@@ -819,6 +829,8 @@ func Flap[B, A any](a A) Operator[func(A) B, B] {
// Prepend returns a function that adds an element to the beginning of a sequence.
//
// RxJS Equivalent: [startWith] - https://rxjs.dev/api/operators/startWith
//
// Example:
//
// seq := From(2, 3, 4)
@@ -832,6 +844,8 @@ func Prepend[A any](head A) Operator[A, A] {
// Append returns a function that adds an element to the end of a sequence.
//
// RxJS Equivalent: [endWith] - https://rxjs.dev/api/operators/endWith
//
// Example:
//
// seq := From(1, 2, 3)
@@ -846,6 +860,8 @@ func Append[A any](tail A) Operator[A, A] {
// MonadZip combines two sequences into a sequence of pairs.
// The resulting sequence stops when either input sequence is exhausted.
//
// RxJS Equivalent: [zip] - https://rxjs.dev/api/operators/zip
//
// Example:
//
// seqA := From(1, 2, 3)

105
v2/iterator/iter/scan.go Normal file
View File

@@ -0,0 +1,105 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package iter
// Scan applies an accumulator function over a sequence, emitting each intermediate result.
//
// This function is similar to Reduce, but instead of returning only the final accumulated value,
// it returns a sequence containing all intermediate accumulated values. Each element in the
// output sequence is the result of applying the accumulator function to the previous accumulated
// value and the current input element.
//
// The operation is lazy - intermediate values are computed only as they are consumed.
//
// RxJS Equivalent: [scan] - https://rxjs.dev/api/operators/scan
//
// Scan is useful for:
// - Computing running totals or cumulative sums
// - Tracking state changes over a sequence
// - Building up complex values incrementally
// - Generating sequences based on previous values
//
// Type Parameters:
// - FCT: The accumulator function type, must be ~func(V, U) V
// - U: The type of elements in the input sequence
// - V: The type of the accumulated value and elements in the output sequence
//
// Parameters:
// - f: The accumulator function that takes the current accumulated value and the next
// input element, returning the new accumulated value
// - initial: The initial accumulated value (not included in the output sequence)
//
// Returns:
// - An Operator that transforms a Seq[U] into a Seq[V] containing all intermediate
// accumulated values
//
// Example - Running sum:
//
// seq := From(1, 2, 3, 4, 5)
// runningSum := Scan(func(acc, x int) int { return acc + x }, 0)
// result := runningSum(seq)
// // yields: 1, 3, 6, 10, 15
//
// Example - Running product:
//
// seq := From(2, 3, 4)
// runningProduct := Scan(func(acc, x int) int { return acc * x }, 1)
// result := runningProduct(seq)
// // yields: 2, 6, 24
//
// Example - Building strings:
//
// seq := From("a", "b", "c")
// concat := Scan(func(acc, x string) string { return acc + x }, "")
// result := concat(seq)
// // yields: "a", "ab", "abc"
//
// Example - Tracking maximum:
//
// seq := From(3, 1, 4, 1, 5, 9, 2)
// maxSoFar := Scan(func(acc, x int) int {
// if x > acc { return x }
// return acc
// }, 0)
// result := maxSoFar(seq)
// // yields: 3, 3, 4, 4, 5, 9, 9
//
// Example - Empty sequence:
//
// seq := Empty[int]()
// runningSum := Scan(func(acc, x int) int { return acc + x }, 0)
// result := runningSum(seq)
// // yields: nothing (empty sequence)
//
// Example - Single element:
//
// seq := From(42)
// runningSum := Scan(func(acc, x int) int { return acc + x }, 10)
// result := runningSum(seq)
// // yields: 52
func Scan[FCT ~func(V, U) V, U, V any](f FCT, initial V) Operator[U, V] {
return func(s Seq[U]) Seq[V] {
return func(yield func(V) bool) {
current := initial
for u := range s {
current = f(current, u)
if !yield(current) {
return
}
}
}
}
}

View File

@@ -0,0 +1,407 @@
// Copyright (c) 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package iter
import (
"fmt"
"testing"
"github.com/stretchr/testify/assert"
)
// TestScanBasic tests basic Scan functionality
func TestScanBasic(t *testing.T) {
t.Run("running sum of integers", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
scanned := Scan(func(acc, x int) int { return acc + x }, 0)
result := toSlice(scanned(seq))
assert.Equal(t, []int{1, 3, 6, 10, 15}, result)
})
t.Run("running product", func(t *testing.T) {
seq := From(2, 3, 4)
scanned := Scan(func(acc, x int) int { return acc * x }, 1)
result := toSlice(scanned(seq))
assert.Equal(t, []int{2, 6, 24}, result)
})
t.Run("string concatenation", func(t *testing.T) {
seq := From("a", "b", "c")
scanned := Scan(func(acc, x string) string { return acc + x }, "")
result := toSlice(scanned(seq))
assert.Equal(t, []string{"a", "ab", "abc"}, result)
})
t.Run("string concatenation with separator", func(t *testing.T) {
seq := From("hello", "world", "test")
scanned := Scan(func(acc, x string) string {
if acc == "" {
return x
}
return acc + "-" + x
}, "")
result := toSlice(scanned(seq))
assert.Equal(t, []string{"hello", "hello-world", "hello-world-test"}, result)
})
t.Run("single element", func(t *testing.T) {
seq := From(42)
scanned := Scan(func(acc, x int) int { return acc + x }, 10)
result := toSlice(scanned(seq))
assert.Equal(t, []int{52}, result)
})
t.Run("two elements", func(t *testing.T) {
seq := From(5, 10)
scanned := Scan(func(acc, x int) int { return acc + x }, 0)
result := toSlice(scanned(seq))
assert.Equal(t, []int{5, 15}, result)
})
}
// TestScanEmpty tests Scan with empty sequences
func TestScanEmpty(t *testing.T) {
t.Run("empty integer sequence", func(t *testing.T) {
seq := Empty[int]()
scanned := Scan(func(acc, x int) int { return acc + x }, 0)
result := toSlice(scanned(seq))
assert.Empty(t, result)
})
t.Run("empty string sequence", func(t *testing.T) {
seq := Empty[string]()
scanned := Scan(func(acc, x string) string { return acc + x }, "start")
result := toSlice(scanned(seq))
assert.Empty(t, result)
})
}
// TestScanWithDifferentTypes tests Scan with different input/output types
func TestScanWithDifferentTypes(t *testing.T) {
t.Run("int to string accumulation", func(t *testing.T) {
seq := From(1, 2, 3)
scanned := Scan(func(acc string, x int) string {
return fmt.Sprintf("%s%d", acc, x)
}, "")
result := toSlice(scanned(seq))
assert.Equal(t, []string{"1", "12", "123"}, result)
})
t.Run("string to int length accumulation", func(t *testing.T) {
seq := From("a", "bb", "ccc")
scanned := Scan(func(acc int, x string) int {
return acc + len(x)
}, 0)
result := toSlice(scanned(seq))
assert.Equal(t, []int{1, 3, 6}, result)
})
t.Run("accumulate into slice", func(t *testing.T) {
seq := From(1, 2, 3)
scanned := Scan(func(acc []int, x int) []int {
return append(acc, x)
}, []int{})
result := toSlice(scanned(seq))
assert.Equal(t, [][]int{
{1},
{1, 2},
{1, 2, 3},
}, result)
})
}
// TestScanStateful tests Scan with stateful operations
func TestScanStateful(t *testing.T) {
t.Run("tracking maximum", func(t *testing.T) {
seq := From(3, 1, 4, 1, 5, 9, 2)
scanned := Scan(func(acc, x int) int {
if x > acc {
return x
}
return acc
}, 0)
result := toSlice(scanned(seq))
assert.Equal(t, []int{3, 3, 4, 4, 5, 9, 9}, result)
})
t.Run("tracking minimum", func(t *testing.T) {
seq := From(5, 3, 8, 1, 4, 2)
scanned := Scan(func(acc, x int) int {
if acc == 0 || x < acc {
return x
}
return acc
}, 0)
result := toSlice(scanned(seq))
assert.Equal(t, []int{5, 3, 3, 1, 1, 1}, result)
})
t.Run("counting occurrences", func(t *testing.T) {
seq := From(1, 2, 1, 3, 1, 2)
scanned := Scan(func(acc map[int]int, x int) map[int]int {
newMap := make(map[int]int)
for k, v := range acc {
newMap[k] = v
}
newMap[x]++
return newMap
}, map[int]int{})
result := toSlice(scanned(seq))
assert.Len(t, result, 6)
assert.Equal(t, 1, result[0][1])
assert.Equal(t, 1, result[1][2])
assert.Equal(t, 2, result[2][1])
assert.Equal(t, 3, result[4][1])
})
}
// TestScanWithComplexTypes tests Scan with complex data types
func TestScanWithComplexTypes(t *testing.T) {
type Point struct {
X, Y int
}
t.Run("accumulate points", func(t *testing.T) {
seq := From(Point{1, 0}, Point{0, 1}, Point{2, 2})
scanned := Scan(func(acc, p Point) Point {
return Point{acc.X + p.X, acc.Y + p.Y}
}, Point{0, 0})
result := toSlice(scanned(seq))
assert.Equal(t, []Point{
{1, 0},
{1, 1},
{3, 3},
}, result)
})
t.Run("accumulate struct fields", func(t *testing.T) {
type Data struct {
Value int
Count int
}
seq := From(5, 10, 15)
scanned := Scan(func(acc Data, x int) Data {
return Data{
Value: acc.Value + x,
Count: acc.Count + 1,
}
}, Data{0, 0})
result := toSlice(scanned(seq))
assert.Equal(t, []Data{
{5, 1},
{15, 2},
{30, 3},
}, result)
})
}
// TestScanWithChainedOperations tests Scan combined with other operations
func TestScanWithChainedOperations(t *testing.T) {
t.Run("scan then map", func(t *testing.T) {
seq := From(1, 2, 3, 4)
scanned := Scan(func(acc, x int) int { return acc + x }, 0)
mapped := MonadMap(scanned(seq), func(x int) int { return x * 2 })
result := toSlice(mapped)
assert.Equal(t, []int{2, 6, 12, 20}, result)
})
t.Run("map then scan", func(t *testing.T) {
seq := From(1, 2, 3, 4)
mapped := MonadMap(seq, func(x int) int { return x * 2 })
scanned := Scan(func(acc, x int) int { return acc + x }, 0)
result := toSlice(scanned(mapped))
assert.Equal(t, []int{2, 6, 12, 20}, result)
})
t.Run("scan then filter", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
scanned := Scan(func(acc, x int) int { return acc + x }, 0)
filtered := MonadFilter(scanned(seq), func(x int) bool { return x%2 == 0 })
result := toSlice(filtered)
assert.Equal(t, []int{6, 10}, result)
})
t.Run("scan then take", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
scanned := Scan(func(acc, x int) int { return acc + x }, 0)
taken := Take[int](3)(scanned(seq))
result := toSlice(taken)
assert.Equal(t, []int{1, 3, 6}, result)
})
}
// TestScanWithCycle tests Scan with infinite sequences
func TestScanWithCycle(t *testing.T) {
t.Run("scan cycled sequence with take", func(t *testing.T) {
seq := From(1, 2, 3)
cycled := Cycle(seq)
scanned := Scan(func(acc, x int) int { return acc + x }, 0)
taken := Take[int](10)(scanned(cycled))
result := toSlice(taken)
// 1, 3, 6, 7, 9, 12, 13, 15, 18, 19
assert.Equal(t, []int{1, 3, 6, 7, 9, 12, 13, 15, 18, 19}, result)
})
}
// TestScanEarlyTermination tests that Scan respects early termination
func TestScanEarlyTermination(t *testing.T) {
t.Run("terminates when yield returns false", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
scanned := Scan(func(acc, x int) int { return acc + x }, 0)
count := 0
for v := range scanned(seq) {
count++
if v >= 6 {
break
}
}
assert.Equal(t, 3, count) // Should stop at 6 (1+2+3)
})
}
// TestScanWithInitialValue tests different initial values
func TestScanWithInitialValue(t *testing.T) {
t.Run("non-zero initial value", func(t *testing.T) {
seq := From(1, 2, 3)
scanned := Scan(func(acc, x int) int { return acc + x }, 10)
result := toSlice(scanned(seq))
assert.Equal(t, []int{11, 13, 16}, result)
})
t.Run("negative initial value", func(t *testing.T) {
seq := From(1, 2, 3)
scanned := Scan(func(acc, x int) int { return acc + x }, -10)
result := toSlice(scanned(seq))
assert.Equal(t, []int{-9, -7, -4}, result)
})
t.Run("string initial value", func(t *testing.T) {
seq := From("a", "b", "c")
scanned := Scan(func(acc, x string) string { return acc + x }, "start:")
result := toSlice(scanned(seq))
assert.Equal(t, []string{"start:a", "start:ab", "start:abc"}, result)
})
}
// TestScanLargeSequence tests Scan with larger sequences
func TestScanLargeSequence(t *testing.T) {
t.Run("scan large sequence", func(t *testing.T) {
data := make([]int, 100)
for i := range data {
data[i] = i + 1
}
seq := From(data...)
scanned := Scan(func(acc, x int) int { return acc + x }, 0)
result := toSlice(scanned(seq))
assert.Len(t, result, 100)
// Sum of 1 to n is n*(n+1)/2
assert.Equal(t, 5050, result[99]) // Sum of 1 to 100
assert.Equal(t, 1, result[0])
assert.Equal(t, 3, result[1])
assert.Equal(t, 6, result[2])
})
}
// Benchmark tests
func BenchmarkScan(b *testing.B) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
scanned := Scan(func(acc, x int) int { return acc + x }, 0)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for range scanned(seq) {
}
}
}
func BenchmarkScanLarge(b *testing.B) {
data := make([]int, 1000)
for i := range data {
data[i] = i + 1
}
seq := From(data...)
scanned := Scan(func(acc, x int) int { return acc + x }, 0)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for range scanned(seq) {
}
}
}
// Example tests for documentation
func ExampleScan() {
seq := From(1, 2, 3, 4, 5)
runningSum := Scan(func(acc, x int) int { return acc + x }, 0)
result := runningSum(seq)
for v := range result {
fmt.Printf("%d ", v)
}
// Output: 1 3 6 10 15
}
func ExampleScan_runningProduct() {
seq := From(2, 3, 4)
runningProduct := Scan(func(acc, x int) int { return acc * x }, 1)
result := runningProduct(seq)
for v := range result {
fmt.Printf("%d ", v)
}
// Output: 2 6 24
}
func ExampleScan_stringConcatenation() {
seq := From("a", "b", "c")
concat := Scan(func(acc, x string) string { return acc + x }, "")
result := concat(seq)
for v := range result {
fmt.Printf("%s ", v)
}
// Output: a ab abc
}
func ExampleScan_trackingMaximum() {
seq := From(3, 1, 4, 1, 5, 9, 2)
maxSoFar := Scan(func(acc, x int) int {
if x > acc {
return x
}
return acc
}, 0)
result := maxSoFar(seq)
for v := range result {
fmt.Printf("%d ", v)
}
// Output: 3 3 4 4 5 9 9
}
func ExampleScan_empty() {
seq := Empty[int]()
runningSum := Scan(func(acc, x int) int { return acc + x }, 0)
result := runningSum(seq)
count := 0
for range result {
count++
}
fmt.Printf("Count: %d\n", count)
// Output: Count: 0
}

80
v2/iterator/iter/take.go Normal file
View File

@@ -0,0 +1,80 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package iter
import F "github.com/IBM/fp-go/v2/function"
// Take returns an operator that limits the number of elements in a sequence to at most n elements.
//
// This function creates a transformation that takes the first n elements from a sequence
// and discards the rest. If n is less than or equal to 0, it returns an empty sequence.
// If the input sequence has fewer than n elements, all elements are returned.
//
// The operation is lazy and only consumes elements from the source sequence as needed.
// Once n elements have been yielded, iteration stops immediately without consuming
// the remaining elements from the source.
//
// RxJS Equivalent: [take] - https://rxjs.dev/api/operators/take
//
// Type Parameters:
// - U: The type of elements in the sequence
//
// Parameters:
// - n: The maximum number of elements to take from the sequence
//
// Returns:
// - An Operator that transforms a Seq[U] by taking at most n elements
//
// Example - Take first 3 elements:
//
// seq := From(1, 2, 3, 4, 5)
// result := Take[int](3)(seq)
// // yields: 1, 2, 3
//
// Example - Take more than available:
//
// seq := From(1, 2)
// result := Take[int](5)(seq)
// // yields: 1, 2 (all available elements)
//
// Example - Take zero or negative:
//
// seq := From(1, 2, 3)
// result := Take[int](0)(seq)
// // yields: nothing (empty sequence)
//
// Example - Chaining with other operations:
//
// seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
// evens := MonadFilter(seq, func(x int) bool { return x%2 == 0 })
// result := Take[int](3)(evens)
// // yields: 2, 4, 6 (first 3 even numbers)
func Take[U any](n int) Operator[U, U] {
if n <= 0 {
return F.Constant1[Seq[U]](Empty[U]())
}
return func(s Seq[U]) Seq[U] {
return func(yield Predicate[U]) {
i := 0
for u := range s {
if i >= n || !yield(u) {
return
}
i += 1
}
}
}
}

View File

@@ -0,0 +1,463 @@
// Copyright (c) 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package iter
import (
"fmt"
"testing"
N "github.com/IBM/fp-go/v2/number"
"github.com/stretchr/testify/assert"
)
// TestTake tests basic Take functionality
func TestTake(t *testing.T) {
t.Run("takes first n elements from sequence", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
result := toSlice(Take[int](3)(seq))
assert.Equal(t, []int{1, 2, 3}, result)
})
t.Run("takes first element", func(t *testing.T) {
seq := From(10, 20, 30)
result := toSlice(Take[int](1)(seq))
assert.Equal(t, []int{10}, result)
})
t.Run("takes all elements when n equals length", func(t *testing.T) {
seq := From(1, 2, 3)
result := toSlice(Take[int](3)(seq))
assert.Equal(t, []int{1, 2, 3}, result)
})
t.Run("takes all elements when n exceeds length", func(t *testing.T) {
seq := From(1, 2, 3)
result := toSlice(Take[int](10)(seq))
assert.Equal(t, []int{1, 2, 3}, result)
})
t.Run("takes from string sequence", func(t *testing.T) {
seq := From("a", "b", "c", "d", "e")
result := toSlice(Take[string](3)(seq))
assert.Equal(t, []string{"a", "b", "c"}, result)
})
t.Run("takes from single element sequence", func(t *testing.T) {
seq := From(42)
result := toSlice(Take[int](1)(seq))
assert.Equal(t, []int{42}, result)
})
t.Run("takes from large sequence", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
result := toSlice(Take[int](5)(seq))
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
})
}
// TestTakeZeroOrNegative tests Take with zero or negative values
func TestTakeZeroOrNegative(t *testing.T) {
t.Run("returns empty sequence when n is zero", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
result := toSlice(Take[int](0)(seq))
assert.Empty(t, result)
})
t.Run("returns empty sequence when n is negative", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
result := toSlice(Take[int](-1)(seq))
assert.Empty(t, result)
})
t.Run("returns empty sequence when n is large negative", func(t *testing.T) {
seq := From("a", "b", "c")
result := toSlice(Take[string](-100)(seq))
assert.Empty(t, result)
})
}
// TestTakeEmpty tests Take with empty sequences
func TestTakeEmpty(t *testing.T) {
t.Run("returns empty from empty integer sequence", func(t *testing.T) {
seq := Empty[int]()
result := toSlice(Take[int](5)(seq))
assert.Empty(t, result)
})
t.Run("returns empty from empty string sequence", func(t *testing.T) {
seq := Empty[string]()
result := toSlice(Take[string](3)(seq))
assert.Empty(t, result)
})
t.Run("returns empty when taking zero from empty", func(t *testing.T) {
seq := Empty[int]()
result := toSlice(Take[int](0)(seq))
assert.Empty(t, result)
})
}
// TestTakeWithComplexTypes tests Take with complex data types
func TestTakeWithComplexTypes(t *testing.T) {
type Person struct {
Name string
Age int
}
t.Run("takes structs", func(t *testing.T) {
seq := From(
Person{"Alice", 30},
Person{"Bob", 25},
Person{"Charlie", 35},
Person{"David", 28},
)
result := toSlice(Take[Person](2)(seq))
expected := []Person{
{"Alice", 30},
{"Bob", 25},
}
assert.Equal(t, expected, result)
})
t.Run("takes pointers", func(t *testing.T) {
p1 := &Person{"Alice", 30}
p2 := &Person{"Bob", 25}
p3 := &Person{"Charlie", 35}
seq := From(p1, p2, p3)
result := toSlice(Take[*Person](2)(seq))
assert.Equal(t, []*Person{p1, p2}, result)
})
t.Run("takes slices", func(t *testing.T) {
seq := From([]int{1, 2}, []int{3, 4}, []int{5, 6}, []int{7, 8})
result := toSlice(Take[[]int](2)(seq))
expected := [][]int{{1, 2}, {3, 4}}
assert.Equal(t, expected, result)
})
}
// TestTakeWithChainedOperations tests Take with other sequence operations
func TestTakeWithChainedOperations(t *testing.T) {
t.Run("take after map", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
mapped := MonadMap(seq, N.Mul(2))
result := toSlice(Take[int](3)(mapped))
assert.Equal(t, []int{2, 4, 6}, result)
})
t.Run("take after filter", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
filtered := MonadFilter(seq, func(x int) bool { return x%2 == 0 })
result := toSlice(Take[int](3)(filtered))
assert.Equal(t, []int{2, 4, 6}, result)
})
t.Run("map after take", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
taken := Take[int](3)(seq)
result := toSlice(MonadMap(taken, N.Mul(10)))
assert.Equal(t, []int{10, 20, 30}, result)
})
t.Run("filter after take", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8)
taken := Take[int](6)(seq)
result := toSlice(MonadFilter(taken, func(x int) bool { return x%2 == 0 }))
assert.Equal(t, []int{2, 4, 6}, result)
})
t.Run("take after chain", func(t *testing.T) {
seq := From(1, 2, 3)
chained := MonadChain(seq, func(x int) Seq[int] {
return From(x, x*10)
})
result := toSlice(Take[int](4)(chained))
assert.Equal(t, []int{1, 10, 2, 20}, result)
})
t.Run("multiple takes", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
taken1 := Take[int](7)(seq)
taken2 := Take[int](3)(taken1)
result := toSlice(taken2)
assert.Equal(t, []int{1, 2, 3}, result)
})
}
// TestTakeWithReplicate tests Take with Replicate
func TestTakeWithReplicate(t *testing.T) {
t.Run("takes from replicated sequence", func(t *testing.T) {
seq := Replicate(10, 42)
result := toSlice(Take[int](3)(seq))
assert.Equal(t, []int{42, 42, 42}, result)
})
t.Run("takes all from short replicate", func(t *testing.T) {
seq := Replicate(2, "hello")
result := toSlice(Take[string](5)(seq))
assert.Equal(t, []string{"hello", "hello"}, result)
})
t.Run("takes zero from replicate", func(t *testing.T) {
seq := Replicate(5, 100)
result := toSlice(Take[int](0)(seq))
assert.Empty(t, result)
})
}
// TestTakeWithMakeBy tests Take with MakeBy
func TestTakeWithMakeBy(t *testing.T) {
t.Run("takes from generated sequence", func(t *testing.T) {
seq := MakeBy(10, func(i int) int { return i * i })
result := toSlice(Take[int](5)(seq))
assert.Equal(t, []int{0, 1, 4, 9, 16}, result)
})
t.Run("takes more than generated", func(t *testing.T) {
seq := MakeBy(3, func(i int) int { return i + 1 })
result := toSlice(Take[int](10)(seq))
assert.Equal(t, []int{1, 2, 3}, result)
})
}
// TestTakeWithPrependAppend tests Take with Prepend and Append
func TestTakeWithPrependAppend(t *testing.T) {
t.Run("take from prepended sequence", func(t *testing.T) {
seq := From(2, 3, 4, 5)
prepended := Prepend(1)(seq)
result := toSlice(Take[int](3)(prepended))
assert.Equal(t, []int{1, 2, 3}, result)
})
t.Run("take from appended sequence", func(t *testing.T) {
seq := From(1, 2, 3)
appended := Append(4)(seq)
result := toSlice(Take[int](2)(appended))
assert.Equal(t, []int{1, 2}, result)
})
t.Run("take includes appended element", func(t *testing.T) {
seq := From(1, 2, 3)
appended := Append(4)(seq)
result := toSlice(Take[int](4)(appended))
assert.Equal(t, []int{1, 2, 3, 4}, result)
})
}
// TestTakeWithFlatten tests Take with Flatten
func TestTakeWithFlatten(t *testing.T) {
t.Run("takes from flattened sequence", func(t *testing.T) {
nested := From(From(1, 2), From(3, 4), From(5, 6))
flattened := Flatten(nested)
result := toSlice(Take[int](4)(flattened))
assert.Equal(t, []int{1, 2, 3, 4}, result)
})
t.Run("takes from flattened with empty inner sequences", func(t *testing.T) {
nested := From(From(1, 2), Empty[int](), From(3, 4))
flattened := Flatten(nested)
result := toSlice(Take[int](3)(flattened))
assert.Equal(t, []int{1, 2, 3}, result)
})
}
// TestTakeDoesNotConsumeEntireSequence tests that Take is lazy
func TestTakeDoesNotConsumeEntireSequence(t *testing.T) {
t.Run("only consumes needed elements", func(t *testing.T) {
callCount := 0
seq := MonadMap(From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10), func(x int) int {
callCount++
return x * 2
})
taken := Take[int](3)(seq)
// Manually iterate to verify lazy evaluation
result := []int{}
for v := range taken {
result = append(result, v)
}
assert.Equal(t, []int{2, 4, 6}, result)
// The map function may be called one extra time to check if there are more elements
// This is expected behavior with Go's range over iterators
assert.LessOrEqual(t, callCount, 4, "should not consume significantly more than needed")
assert.GreaterOrEqual(t, callCount, 3, "should consume at least the needed elements")
})
t.Run("stops early with filter", func(t *testing.T) {
callCount := 0
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
filtered := MonadFilter(seq, func(x int) bool {
callCount++
return x%2 == 0
})
taken := Take[int](2)(filtered)
// Manually iterate to verify lazy evaluation
result := []int{}
for v := range taken {
result = append(result, v)
}
assert.Equal(t, []int{2, 4}, result)
// Should stop after finding 2 even numbers, may check a few more elements
assert.LessOrEqual(t, callCount, 7, "should not consume significantly more than needed")
assert.GreaterOrEqual(t, callCount, 4, "should consume at least enough to find 2 evens")
})
}
// TestTakeEdgeCases tests edge cases
func TestTakeEdgeCases(t *testing.T) {
t.Run("take 1 from single element", func(t *testing.T) {
seq := From(42)
result := toSlice(Take[int](1)(seq))
assert.Equal(t, []int{42}, result)
})
t.Run("take 0 from single element", func(t *testing.T) {
seq := From(42)
result := toSlice(Take[int](0)(seq))
assert.Empty(t, result)
})
t.Run("take large number from small sequence", func(t *testing.T) {
seq := From(1, 2)
result := toSlice(Take[int](1000000)(seq))
assert.Equal(t, []int{1, 2}, result)
})
t.Run("take with very large n", func(t *testing.T) {
seq := From(1, 2, 3)
result := toSlice(Take[int](int(^uint(0) >> 1))(seq)) // max int
assert.Equal(t, []int{1, 2, 3}, result)
})
}
// Benchmark tests
func BenchmarkTake(b *testing.B) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
taken := Take[int](5)(seq)
for range taken {
}
}
}
func BenchmarkTakeLargeSequence(b *testing.B) {
data := make([]int, 1000)
for i := range data {
data[i] = i
}
seq := From(data...)
b.ResetTimer()
for i := 0; i < b.N; i++ {
taken := Take[int](100)(seq)
for range taken {
}
}
}
func BenchmarkTakeWithMap(b *testing.B) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
mapped := MonadMap(seq, N.Mul(2))
taken := Take[int](5)(mapped)
for range taken {
}
}
}
func BenchmarkTakeWithFilter(b *testing.B) {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
b.ResetTimer()
for i := 0; i < b.N; i++ {
filtered := MonadFilter(seq, func(x int) bool { return x%2 == 0 })
taken := Take[int](3)(filtered)
for range taken {
}
}
}
// Example tests for documentation
func ExampleTake() {
seq := From(1, 2, 3, 4, 5)
taken := Take[int](3)(seq)
for v := range taken {
fmt.Printf("%d ", v)
}
// Output: 1 2 3
}
func ExampleTake_moreThanAvailable() {
seq := From(1, 2, 3)
taken := Take[int](10)(seq)
for v := range taken {
fmt.Printf("%d ", v)
}
// Output: 1 2 3
}
func ExampleTake_zero() {
seq := From(1, 2, 3, 4, 5)
taken := Take[int](0)(seq)
count := 0
for range taken {
count++
}
fmt.Printf("Count: %d\n", count)
// Output: Count: 0
}
func ExampleTake_withFilter() {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
evens := MonadFilter(seq, func(x int) bool { return x%2 == 0 })
taken := Take[int](3)(evens)
for v := range taken {
fmt.Printf("%d ", v)
}
// Output: 2 4 6
}
func ExampleTake_withMap() {
seq := From(1, 2, 3, 4, 5)
doubled := MonadMap(seq, N.Mul(2))
taken := Take[int](3)(doubled)
for v := range taken {
fmt.Printf("%d ", v)
}
// Output: 2 4 6
}
func ExampleTake_chained() {
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
result := Take[int](5)(
MonadFilter(seq, func(x int) bool { return x > 3 }),
)
for v := range result {
fmt.Printf("%d ", v)
}
// Output: 4 5 6 7 8
}

167
v2/iterator/iter/uniq.go Normal file
View File

@@ -0,0 +1,167 @@
// Copyright (c) 2023 - 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package iter
import F "github.com/IBM/fp-go/v2/function"
// Uniq returns an operator that filters a sequence to contain only unique elements,
// where uniqueness is determined by a key extraction function.
//
// This function takes a key extraction function and returns an operator that removes
// duplicate elements from a sequence. Two elements are considered duplicates if the
// key extraction function returns the same key for both. Only the first occurrence
// of each unique key is kept in the output sequence.
//
// The operation maintains a map of seen keys internally, so memory usage grows with
// the number of unique keys encountered. The operation is lazy - elements are processed
// and filtered as they are consumed.
//
// RxJS Equivalent: [distinct] - https://rxjs.dev/api/operators/distinct
//
// Type Parameters:
// - A: The type of elements in the sequence
// - K: The type of the key used for uniqueness comparison (must be comparable)
//
// Parameters:
// - f: A function that extracts a comparable key from each element
//
// Returns:
// - An Operator that filters the sequence to contain only unique elements based on the key
//
// Example - Remove duplicate integers:
//
// seq := From(1, 2, 3, 2, 4, 1, 5)
// unique := Uniq(func(x int) int { return x })
// result := unique(seq)
// // yields: 1, 2, 3, 4, 5
//
// Example - Unique by string length:
//
// seq := From("a", "bb", "c", "dd", "eee")
// uniqueByLength := Uniq(func(s string) int { return len(s) })
// result := uniqueByLength(seq)
// // yields: "a", "bb", "eee" (first occurrence of each length)
//
// Example - Unique structs by field:
//
// type Person struct { ID int; Name string }
// seq := From(
// Person{1, "Alice"},
// Person{2, "Bob"},
// Person{1, "Alice2"}, // duplicate ID
// )
// uniqueByID := Uniq(func(p Person) int { return p.ID })
// result := uniqueByID(seq)
// // yields: Person{1, "Alice"}, Person{2, "Bob"}
//
// Example - Case-insensitive unique strings:
//
// seq := From("Hello", "world", "HELLO", "World", "test")
// uniqueCaseInsensitive := Uniq(func(s string) string {
// return strings.ToLower(s)
// })
// result := uniqueCaseInsensitive(seq)
// // yields: "Hello", "world", "test"
//
// Example - Empty sequence:
//
// seq := Empty[int]()
// unique := Uniq(func(x int) int { return x })
// result := unique(seq)
// // yields: nothing (empty sequence)
//
// Example - All duplicates:
//
// seq := From(1, 1, 1, 1)
// unique := Uniq(func(x int) int { return x })
// result := unique(seq)
// // yields: 1 (only first occurrence)
func Uniq[A any, K comparable](f func(A) K) Operator[A, A] {
return func(s Seq[A]) Seq[A] {
return func(yield func(A) bool) {
items := make(map[K]struct{})
for a := range s {
k := f(a)
if _, ok := items[k]; !ok {
items[k] = struct{}{}
if !yield(a) {
return
}
}
}
}
}
}
// StrictUniq filters a sequence to contain only unique elements using direct comparison.
//
// This is a convenience function that uses the identity function as the key extractor,
// meaning elements are compared directly for uniqueness. It's equivalent to calling
// Uniq with the identity function, but provides a simpler API when the elements
// themselves are comparable.
//
// The operation maintains a map of seen elements internally, so memory usage grows with
// the number of unique elements. Only the first occurrence of each unique element is kept.
//
// RxJS Equivalent: [distinct] - https://rxjs.dev/api/operators/distinct
//
// Type Parameters:
// - A: The type of elements in the sequence (must be comparable)
//
// Parameters:
// - as: The input sequence to filter for unique elements
//
// Returns:
// - A sequence containing only the first occurrence of each unique element
//
// Example - Remove duplicate integers:
//
// seq := From(1, 2, 3, 2, 4, 1, 5)
// result := StrictUniq(seq)
// // yields: 1, 2, 3, 4, 5
//
// Example - Remove duplicate strings:
//
// seq := From("apple", "banana", "apple", "cherry", "banana")
// result := StrictUniq(seq)
// // yields: "apple", "banana", "cherry"
//
// Example - Single element:
//
// seq := From(42)
// result := StrictUniq(seq)
// // yields: 42
//
// Example - All duplicates:
//
// seq := From("x", "x", "x")
// result := StrictUniq(seq)
// // yields: "x" (only first occurrence)
//
// Example - Empty sequence:
//
// seq := Empty[int]()
// result := StrictUniq(seq)
// // yields: nothing (empty sequence)
//
// Example - Already unique:
//
// seq := From(1, 2, 3, 4, 5)
// result := StrictUniq(seq)
// // yields: 1, 2, 3, 4, 5 (no changes)
func StrictUniq[A comparable](as Seq[A]) Seq[A] {
return Uniq(F.Identity[A])(as)
}

View File

@@ -0,0 +1,433 @@
// Copyright (c) 2025 IBM Corp.
// All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package iter
import (
"fmt"
"strings"
"testing"
F "github.com/IBM/fp-go/v2/function"
S "github.com/IBM/fp-go/v2/string"
"github.com/stretchr/testify/assert"
)
// TestUniqBasic tests basic Uniq functionality
func TestUniqBasic(t *testing.T) {
t.Run("removes duplicate integers", func(t *testing.T) {
seq := From(1, 2, 3, 2, 4, 1, 5)
unique := Uniq(F.Identity[int])
result := toSlice(unique(seq))
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
})
t.Run("removes duplicate strings", func(t *testing.T) {
seq := From("apple", "banana", "apple", "cherry", "banana")
unique := Uniq(F.Identity[string])
result := toSlice(unique(seq))
assert.Equal(t, []string{"apple", "banana", "cherry"}, result)
})
t.Run("keeps first occurrence", func(t *testing.T) {
seq := From(1, 2, 1, 3, 2, 4)
unique := Uniq(F.Identity[int])
result := toSlice(unique(seq))
assert.Equal(t, []int{1, 2, 3, 4}, result)
})
t.Run("single element", func(t *testing.T) {
seq := From(42)
unique := Uniq(F.Identity[int])
result := toSlice(unique(seq))
assert.Equal(t, []int{42}, result)
})
t.Run("all duplicates", func(t *testing.T) {
seq := From(5, 5, 5, 5)
unique := Uniq(F.Identity[int])
result := toSlice(unique(seq))
assert.Equal(t, []int{5}, result)
})
t.Run("already unique", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
unique := Uniq(F.Identity[int])
result := toSlice(unique(seq))
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
})
}
// TestUniqEmpty tests Uniq with empty sequences
func TestUniqEmpty(t *testing.T) {
t.Run("empty integer sequence", func(t *testing.T) {
seq := Empty[int]()
unique := Uniq(F.Identity[int])
result := toSlice(unique(seq))
assert.Empty(t, result)
})
t.Run("empty string sequence", func(t *testing.T) {
seq := Empty[string]()
unique := Uniq(F.Identity[string])
result := toSlice(unique(seq))
assert.Empty(t, result)
})
}
// TestUniqWithKeyExtractor tests Uniq with custom key extraction
func TestUniqWithKeyExtractor(t *testing.T) {
t.Run("unique by string length", func(t *testing.T) {
seq := From("a", "bb", "c", "dd", "eee", "f")
uniqueByLength := Uniq(S.Size)
result := toSlice(uniqueByLength(seq))
assert.Equal(t, []string{"a", "bb", "eee"}, result)
})
t.Run("unique by absolute value", func(t *testing.T) {
seq := From(1, -1, 2, -2, 3, 1, -3)
uniqueByAbs := Uniq(func(x int) int {
if x < 0 {
return -x
}
return x
})
result := toSlice(uniqueByAbs(seq))
assert.Equal(t, []int{1, 2, 3}, result)
})
t.Run("case-insensitive unique strings", func(t *testing.T) {
seq := From("Hello", "world", "HELLO", "World", "test")
uniqueCaseInsensitive := Uniq(strings.ToLower)
result := toSlice(uniqueCaseInsensitive(seq))
assert.Equal(t, []string{"Hello", "world", "test"}, result)
})
t.Run("unique by modulo", func(t *testing.T) {
seq := From(1, 4, 7, 2, 5, 8, 3)
uniqueByMod3 := Uniq(func(x int) int { return x % 3 })
result := toSlice(uniqueByMod3(seq))
assert.Equal(t, []int{1, 2, 3}, result) // 1%3=1, 4%3=1 (dup), 7%3=1 (dup), 2%3=2, 5%3=2 (dup), 8%3=2 (dup), 3%3=0
})
}
// TestUniqWithComplexTypes tests Uniq with structs and complex types
func TestUniqWithComplexTypes(t *testing.T) {
type Person struct {
ID int
Name string
}
t.Run("unique structs by ID", func(t *testing.T) {
seq := From(
Person{1, "Alice"},
Person{2, "Bob"},
Person{1, "Alice2"}, // duplicate ID
Person{3, "Charlie"},
Person{2, "Bob2"}, // duplicate ID
)
uniqueByID := Uniq(func(p Person) int { return p.ID })
result := toSlice(uniqueByID(seq))
assert.Equal(t, []Person{
{1, "Alice"},
{2, "Bob"},
{3, "Charlie"},
}, result)
})
t.Run("unique structs by name", func(t *testing.T) {
seq := From(
Person{1, "Alice"},
Person{2, "Bob"},
Person{3, "Alice"}, // duplicate name
)
uniqueByName := Uniq(func(p Person) string { return p.Name })
result := toSlice(uniqueByName(seq))
assert.Equal(t, []Person{
{1, "Alice"},
{2, "Bob"},
}, result)
})
t.Run("unique slices by length", func(t *testing.T) {
seq := From([]int{1, 2}, []int{3}, []int{4, 5}, []int{6})
uniqueByLength := Uniq(func(s []int) int { return len(s) })
result := toSlice(uniqueByLength(seq))
assert.Len(t, result, 2)
assert.Equal(t, 2, len(result[0]))
assert.Equal(t, 1, len(result[1]))
})
}
// TestStrictUniq tests StrictUniq functionality
func TestStrictUniq(t *testing.T) {
t.Run("removes duplicate integers", func(t *testing.T) {
seq := From(1, 2, 3, 2, 4, 1, 5)
result := toSlice(StrictUniq(seq))
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
})
t.Run("removes duplicate strings", func(t *testing.T) {
seq := From("apple", "banana", "apple", "cherry", "banana")
result := toSlice(StrictUniq(seq))
assert.Equal(t, []string{"apple", "banana", "cherry"}, result)
})
t.Run("single element", func(t *testing.T) {
seq := From(42)
result := toSlice(StrictUniq(seq))
assert.Equal(t, []int{42}, result)
})
t.Run("all duplicates", func(t *testing.T) {
seq := From("x", "x", "x")
result := toSlice(StrictUniq(seq))
assert.Equal(t, []string{"x"}, result)
})
t.Run("empty sequence", func(t *testing.T) {
seq := Empty[int]()
result := toSlice(StrictUniq(seq))
assert.Empty(t, result)
})
t.Run("already unique", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
result := toSlice(StrictUniq(seq))
assert.Equal(t, []int{1, 2, 3, 4, 5}, result)
})
t.Run("boolean values", func(t *testing.T) {
seq := From(true, false, true, false, true)
result := toSlice(StrictUniq(seq))
assert.Equal(t, []bool{true, false}, result)
})
}
// TestUniqWithChainedOperations tests Uniq combined with other operations
func TestUniqWithChainedOperations(t *testing.T) {
t.Run("uniq then map", func(t *testing.T) {
seq := From(1, 2, 3, 2, 4, 1)
unique := Uniq(F.Identity[int])
mapped := MonadMap(unique(seq), func(x int) int { return x * 2 })
result := toSlice(mapped)
assert.Equal(t, []int{2, 4, 6, 8}, result)
})
t.Run("map then uniq", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5)
mapped := MonadMap(seq, func(x int) int { return x % 3 })
unique := Uniq(F.Identity[int])
result := toSlice(unique(mapped))
assert.Equal(t, []int{1, 2, 0}, result)
})
t.Run("filter then uniq", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5, 6, 2, 4, 6)
filtered := MonadFilter(seq, func(x int) bool { return x%2 == 0 })
unique := Uniq(F.Identity[int])
result := toSlice(unique(filtered))
assert.Equal(t, []int{2, 4, 6}, result)
})
t.Run("uniq then filter", func(t *testing.T) {
seq := From(1, 2, 3, 2, 4, 1, 5, 6)
unique := Uniq(F.Identity[int])
filtered := MonadFilter(unique(seq), func(x int) bool { return x%2 == 0 })
result := toSlice(filtered)
assert.Equal(t, []int{2, 4, 6}, result)
})
t.Run("uniq then take", func(t *testing.T) {
seq := From(1, 2, 3, 2, 4, 1, 5)
unique := Uniq(F.Identity[int])
taken := Take[int](3)(unique(seq))
result := toSlice(taken)
assert.Equal(t, []int{1, 2, 3}, result)
})
t.Run("take then uniq", func(t *testing.T) {
seq := From(1, 2, 1, 3, 2, 4, 5)
taken := Take[int](5)(seq)
unique := Uniq(F.Identity[int])
result := toSlice(unique(taken))
assert.Equal(t, []int{1, 2, 3}, result)
})
}
// TestUniqEarlyTermination tests that Uniq respects early termination
func TestUniqEarlyTermination(t *testing.T) {
t.Run("terminates when yield returns false", func(t *testing.T) {
seq := From(1, 2, 3, 4, 5, 2, 6, 7)
unique := Uniq(F.Identity[int])
count := 0
for v := range unique(seq) {
count++
if v >= 4 {
break
}
}
assert.Equal(t, 4, count) // Should stop at 4
})
}
// TestUniqLargeSequence tests Uniq with larger sequences
func TestUniqLargeSequence(t *testing.T) {
t.Run("uniq large sequence with many duplicates", func(t *testing.T) {
// Create sequence with repeating pattern
data := make([]int, 1000)
for i := range data {
data[i] = i % 10 // Only 10 unique values
}
seq := From(data...)
unique := Uniq(F.Identity[int])
result := toSlice(unique(seq))
assert.Len(t, result, 10)
for i := 0; i < 10; i++ {
assert.Equal(t, i, result[i])
}
})
t.Run("uniq large sequence all unique", func(t *testing.T) {
data := make([]int, 100)
for i := range data {
data[i] = i
}
seq := From(data...)
unique := Uniq(F.Identity[int])
result := toSlice(unique(seq))
assert.Len(t, result, 100)
for i := 0; i < 100; i++ {
assert.Equal(t, i, result[i])
}
})
}
// TestUniqPreservesOrder tests that Uniq maintains element order
func TestUniqPreservesOrder(t *testing.T) {
t.Run("maintains order of first occurrences", func(t *testing.T) {
seq := From(5, 3, 5, 1, 3, 2, 1, 4)
unique := Uniq(F.Identity[int])
result := toSlice(unique(seq))
assert.Equal(t, []int{5, 3, 1, 2, 4}, result)
})
}
// Benchmark tests
func BenchmarkUniq(b *testing.B) {
seq := From(1, 2, 3, 2, 4, 1, 5, 3, 6, 4, 7, 5)
unique := Uniq(F.Identity[int])
b.ResetTimer()
for i := 0; i < b.N; i++ {
for range unique(seq) {
}
}
}
func BenchmarkStrictUniq(b *testing.B) {
seq := From(1, 2, 3, 2, 4, 1, 5, 3, 6, 4, 7, 5)
b.ResetTimer()
for i := 0; i < b.N; i++ {
for range StrictUniq(seq) {
}
}
}
func BenchmarkUniqLarge(b *testing.B) {
data := make([]int, 1000)
for i := range data {
data[i] = i % 100
}
seq := From(data...)
unique := Uniq(F.Identity[int])
b.ResetTimer()
for i := 0; i < b.N; i++ {
for range unique(seq) {
}
}
}
// Example tests for documentation
func ExampleUniq() {
seq := From(1, 2, 3, 2, 4, 1, 5)
unique := Uniq(F.Identity[int])
result := unique(seq)
for v := range result {
fmt.Printf("%d ", v)
}
// Output: 1 2 3 4 5
}
func ExampleUniq_byLength() {
seq := From("a", "bb", "c", "dd", "eee")
uniqueByLength := Uniq(func(s string) int { return len(s) })
result := uniqueByLength(seq)
for v := range result {
fmt.Printf("%s ", v)
}
// Output: a bb eee
}
func ExampleUniq_caseInsensitive() {
seq := From("Hello", "world", "HELLO", "World", "test")
uniqueCaseInsensitive := Uniq(func(s string) string {
return strings.ToLower(s)
})
result := uniqueCaseInsensitive(seq)
for v := range result {
fmt.Printf("%s ", v)
}
// Output: Hello world test
}
func ExampleStrictUniq() {
seq := From(1, 2, 3, 2, 4, 1, 5)
result := StrictUniq(seq)
for v := range result {
fmt.Printf("%d ", v)
}
// Output: 1 2 3 4 5
}
func ExampleStrictUniq_strings() {
seq := From("apple", "banana", "apple", "cherry", "banana")
result := StrictUniq(seq)
for v := range result {
fmt.Printf("%s ", v)
}
// Output: apple banana cherry
}
func ExampleUniq_empty() {
seq := Empty[int]()
unique := Uniq(F.Identity[int])
result := unique(seq)
count := 0
for range result {
count++
}
fmt.Printf("Count: %d\n", count)
// Output: Count: 0
}