mirror of
https://github.com/IBM/fp-go.git
synced 2025-12-21 23:47:34 +02:00
347 lines
9.0 KiB
Go
347 lines
9.0 KiB
Go
// 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"
|
|
|
|
F "github.com/IBM/fp-go/v2/function"
|
|
N "github.com/IBM/fp-go/v2/number"
|
|
O "github.com/IBM/fp-go/v2/option"
|
|
S "github.com/IBM/fp-go/v2/string"
|
|
"github.com/stretchr/testify/assert"
|
|
)
|
|
|
|
// TestFirst tests getting the first element from a non-empty sequence
|
|
func TestFirst(t *testing.T) {
|
|
t.Run("returns first element from integer sequence", func(t *testing.T) {
|
|
seq := From(1, 2, 3)
|
|
fst := First(seq)
|
|
assert.Equal(t, O.Of(1), fst)
|
|
})
|
|
|
|
t.Run("returns first element from string sequence", func(t *testing.T) {
|
|
seq := From("a", "b", "c")
|
|
fst := First(seq)
|
|
assert.Equal(t, O.Of("a"), fst)
|
|
})
|
|
|
|
t.Run("returns first element from single element sequence", func(t *testing.T) {
|
|
seq := From(42)
|
|
fst := First(seq)
|
|
assert.Equal(t, O.Of(42), fst)
|
|
})
|
|
|
|
t.Run("returns first element from large sequence", func(t *testing.T) {
|
|
seq := From(100, 200, 300, 400, 500)
|
|
fst := First(seq)
|
|
assert.Equal(t, O.Of(100), fst)
|
|
})
|
|
}
|
|
|
|
// TestFirstEmpty tests getting the first element from an empty sequence
|
|
func TestFirstEmpty(t *testing.T) {
|
|
t.Run("returns None for empty integer sequence", func(t *testing.T) {
|
|
seq := Empty[int]()
|
|
fst := First(seq)
|
|
assert.Equal(t, O.None[int](), fst)
|
|
})
|
|
|
|
t.Run("returns None for empty string sequence", func(t *testing.T) {
|
|
seq := Empty[string]()
|
|
fst := First(seq)
|
|
assert.Equal(t, O.None[string](), fst)
|
|
})
|
|
|
|
t.Run("returns None for empty struct sequence", func(t *testing.T) {
|
|
type TestStruct struct {
|
|
Value int
|
|
}
|
|
seq := Empty[TestStruct]()
|
|
fst := First(seq)
|
|
assert.Equal(t, O.None[TestStruct](), fst)
|
|
})
|
|
}
|
|
|
|
// TestFirstWithFiltered tests First with filtered sequences
|
|
func TestFirstWithFiltered(t *testing.T) {
|
|
t.Run("returns first element matching filter", func(t *testing.T) {
|
|
seq := From(1, 2, 3, 4, 5)
|
|
filtered := MonadFilter(seq, N.MoreThan(3))
|
|
fst := First(filtered)
|
|
assert.Equal(t, O.Of(4), fst)
|
|
})
|
|
|
|
t.Run("returns None when no elements match filter", func(t *testing.T) {
|
|
seq := From(1, 2, 3)
|
|
filtered := MonadFilter(seq, N.MoreThan(10))
|
|
fst := First(filtered)
|
|
assert.Equal(t, O.None[int](), fst)
|
|
})
|
|
|
|
t.Run("returns first even number", func(t *testing.T) {
|
|
seq := From(1, 3, 5, 6, 7, 8)
|
|
filtered := MonadFilter(seq, func(x int) bool { return x%2 == 0 })
|
|
fst := First(filtered)
|
|
assert.Equal(t, O.Of(6), fst)
|
|
})
|
|
}
|
|
|
|
// TestFirstWithMapped tests First with mapped sequences
|
|
func TestFirstWithMapped(t *testing.T) {
|
|
t.Run("returns first element after mapping", func(t *testing.T) {
|
|
seq := From(1, 2, 3)
|
|
mapped := MonadMap(seq, N.Mul(2))
|
|
fst := First(mapped)
|
|
assert.Equal(t, O.Of(2), fst)
|
|
})
|
|
|
|
t.Run("returns first string after mapping", func(t *testing.T) {
|
|
seq := From(1, 2, 3)
|
|
mapped := MonadMap(seq, S.Format[int]("num-%d"))
|
|
fst := First(mapped)
|
|
assert.Equal(t, O.Of("num-1"), fst)
|
|
})
|
|
}
|
|
|
|
// TestFirstWithComplex tests First with complex types
|
|
func TestFirstWithComplex(t *testing.T) {
|
|
type Person struct {
|
|
Name string
|
|
Age int
|
|
}
|
|
|
|
t.Run("returns first person", func(t *testing.T) {
|
|
seq := From(
|
|
Person{"Alice", 30},
|
|
Person{"Bob", 25},
|
|
Person{"Charlie", 35},
|
|
)
|
|
fst := First(seq)
|
|
expected := O.Of(Person{"Alice", 30})
|
|
assert.Equal(t, expected, fst)
|
|
})
|
|
|
|
t.Run("returns first pointer", func(t *testing.T) {
|
|
p1 := &Person{"Alice", 30}
|
|
p2 := &Person{"Bob", 25}
|
|
seq := From(p1, p2)
|
|
fst := First(seq)
|
|
assert.Equal(t, O.Of(p1), fst)
|
|
})
|
|
}
|
|
|
|
// TestFirstDoesNotConsumeEntireSequence tests that First only consumes the first element
|
|
func TestFirstDoesNotConsumeEntireSequence(t *testing.T) {
|
|
t.Run("only consumes first element", func(t *testing.T) {
|
|
callCount := 0
|
|
seq := MonadMap(From(1, 2, 3, 4, 5), func(x int) int {
|
|
callCount++
|
|
return x * 2
|
|
})
|
|
|
|
fst := First(seq)
|
|
|
|
assert.Equal(t, O.Of(2), fst)
|
|
// Should only have called the map function once for the first element
|
|
assert.Equal(t, 1, callCount)
|
|
})
|
|
}
|
|
|
|
// TestFirstWithChainedOperations tests First with multiple chained operations
|
|
func TestFirstWithChainedOperations(t *testing.T) {
|
|
t.Run("chains filter, map, and first", func(t *testing.T) {
|
|
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
|
filtered := MonadFilter(seq, N.MoreThan(5))
|
|
mapped := MonadMap(filtered, N.Mul(10))
|
|
result := First(mapped)
|
|
assert.Equal(t, O.Of(60), result)
|
|
})
|
|
|
|
t.Run("chains map and filter", func(t *testing.T) {
|
|
seq := From(1, 2, 3, 4, 5)
|
|
mapped := MonadMap(seq, N.Mul(2))
|
|
filtered := MonadFilter(mapped, N.MoreThan(5))
|
|
result := First(filtered)
|
|
assert.Equal(t, O.Of(6), result)
|
|
})
|
|
}
|
|
|
|
// TestFirstWithReplicate tests First with replicated values
|
|
func TestFirstWithReplicate(t *testing.T) {
|
|
t.Run("returns first from replicated sequence", func(t *testing.T) {
|
|
seq := Replicate(5, 42)
|
|
fst := First(seq)
|
|
assert.Equal(t, O.Of(42), fst)
|
|
})
|
|
|
|
t.Run("returns None from zero replications", func(t *testing.T) {
|
|
seq := Replicate(0, 42)
|
|
fst := First(seq)
|
|
assert.Equal(t, O.None[int](), fst)
|
|
})
|
|
}
|
|
|
|
// TestFirstWithMakeBy tests First with MakeBy
|
|
func TestFirstWithMakeBy(t *testing.T) {
|
|
t.Run("returns first generated element", func(t *testing.T) {
|
|
seq := MakeBy(5, func(i int) int { return i * i })
|
|
fst := First(seq)
|
|
assert.Equal(t, O.Of(0), fst)
|
|
})
|
|
|
|
t.Run("returns None for zero elements", func(t *testing.T) {
|
|
seq := MakeBy(0, F.Identity[int])
|
|
fst := First(seq)
|
|
assert.Equal(t, O.None[int](), fst)
|
|
})
|
|
}
|
|
|
|
// TestFirstWithPrepend tests First with Prepend
|
|
func TestFirstWithPrepend(t *testing.T) {
|
|
t.Run("returns prepended element", func(t *testing.T) {
|
|
seq := From(2, 3, 4)
|
|
prepended := Prepend(1)(seq)
|
|
fst := First(prepended)
|
|
assert.Equal(t, O.Of(1), fst)
|
|
})
|
|
|
|
t.Run("returns prepended element from empty sequence", func(t *testing.T) {
|
|
seq := Empty[int]()
|
|
prepended := Prepend(42)(seq)
|
|
fst := First(prepended)
|
|
assert.Equal(t, O.Of(42), fst)
|
|
})
|
|
}
|
|
|
|
// TestFirstWithAppend tests First with Append
|
|
func TestFirstWithAppend(t *testing.T) {
|
|
t.Run("returns first element, not appended", func(t *testing.T) {
|
|
seq := From(1, 2, 3)
|
|
appended := Append(4)(seq)
|
|
fst := First(appended)
|
|
assert.Equal(t, O.Of(1), fst)
|
|
})
|
|
|
|
t.Run("returns appended element from empty sequence", func(t *testing.T) {
|
|
seq := Empty[int]()
|
|
appended := Append(42)(seq)
|
|
fst := First(appended)
|
|
assert.Equal(t, O.Of(42), fst)
|
|
})
|
|
}
|
|
|
|
// TestFirstWithChain tests First with Chain (flatMap)
|
|
func TestFirstWithChain(t *testing.T) {
|
|
t.Run("returns first from chained sequence", func(t *testing.T) {
|
|
seq := From(1, 2, 3)
|
|
chained := MonadChain(seq, func(x int) Seq[int] {
|
|
return From(x, x*10)
|
|
})
|
|
fst := First(chained)
|
|
assert.Equal(t, O.Of(1), fst)
|
|
})
|
|
|
|
t.Run("returns None when chain produces empty", func(t *testing.T) {
|
|
seq := From(1, 2, 3)
|
|
chained := MonadChain(seq, func(x int) Seq[int] {
|
|
return Empty[int]()
|
|
})
|
|
fst := First(chained)
|
|
assert.Equal(t, O.None[int](), fst)
|
|
})
|
|
}
|
|
|
|
// TestFirstWithFlatten tests First with Flatten
|
|
func TestFirstWithFlatten(t *testing.T) {
|
|
t.Run("returns first from flattened sequence", func(t *testing.T) {
|
|
nested := From(From(1, 2), From(3, 4), From(5))
|
|
flattened := Flatten(nested)
|
|
fst := First(flattened)
|
|
assert.Equal(t, O.Of(1), fst)
|
|
})
|
|
|
|
t.Run("returns None from empty nested sequence", func(t *testing.T) {
|
|
nested := Empty[Seq[int]]()
|
|
flattened := Flatten(nested)
|
|
fst := First(flattened)
|
|
assert.Equal(t, O.None[int](), fst)
|
|
})
|
|
}
|
|
|
|
// Benchmark tests
|
|
func BenchmarkFirst(b *testing.B) {
|
|
seq := From(1, 2, 3, 4, 5)
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
First(seq)
|
|
}
|
|
}
|
|
|
|
func BenchmarkFirstLargeSequence(b *testing.B) {
|
|
data := make([]int, 1000)
|
|
for i := range data {
|
|
data[i] = i
|
|
}
|
|
seq := From(data...)
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
First(seq)
|
|
}
|
|
}
|
|
|
|
func BenchmarkFirstFiltered(b *testing.B) {
|
|
seq := From(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
|
|
b.ResetTimer()
|
|
for b.Loop() {
|
|
filtered := MonadFilter(seq, N.MoreThan(5))
|
|
First(filtered)
|
|
}
|
|
}
|
|
|
|
// Example tests for documentation
|
|
func ExampleFirst() {
|
|
seq := From(1, 2, 3, 4, 5)
|
|
first := First(seq)
|
|
|
|
if value, ok := O.Unwrap(first); ok {
|
|
fmt.Printf("First element: %d\n", value)
|
|
}
|
|
// Output: First element: 1
|
|
}
|
|
|
|
func ExampleFirst_empty() {
|
|
seq := Empty[int]()
|
|
first := First(seq)
|
|
|
|
if _, ok := O.Unwrap(first); !ok {
|
|
fmt.Println("Sequence is empty")
|
|
}
|
|
// Output: Sequence is empty
|
|
}
|
|
|
|
func ExampleFirst_filtered() {
|
|
seq := From(1, 2, 3, 4, 5)
|
|
filtered := MonadFilter(seq, N.MoreThan(3))
|
|
first := First(filtered)
|
|
|
|
if value, ok := O.Unwrap(first); ok {
|
|
fmt.Printf("First element > 3: %d\n", value)
|
|
}
|
|
// Output: First element > 3: 4
|
|
}
|