itertools

package module
v1.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jan 14, 2026 License: MIT Imports: 12 Imported by: 0

README

Iterator Library for Go

This library provides a powerful and flexible generic iterator system for Go. It enables functional-style iteration, transformation, and aggregation of collections. It is inspired by the iterator protocols found in Python and Rust.


Table of Contents

  1. Installation
  2. Usage
  3. Features
  4. API Reference
  5. Examples
  6. Performance & Benchmarks
  7. Contributing
  8. License

Installation

go get -u github.com/amjadjibon/itertools

Usage

Here's a simple example to get you started.

package main

import (
    "fmt"
    "github.com/amjadjibon/itertools"
)

func main() {
    // Create an iterator from a slice
    iter := itertools.ToIter([]int{1, 2, 3, 4, 5})

    // Filter even numbers, map them to their squares, and collect the result
    result := iter.Filter(func(x int) bool { return x%2 == 0 }).
        Map(func(x int) int { return x * x }).
        Collect()

    fmt.Println(result) // Output: [4, 16]
}

Features

  • Chainable API: Combine transformations like Filter, Map, Take, and Drop into one functional-style chain.
  • Laziness: Iterators are lazy; they only compute elements as needed.
  • Stream Support: Create iterators from channels, io.Reader, generators, and custom functions.
  • Context Support: Built-in context support for cancellable stream operations.
  • Composable: Supports operations like Zip, Chain, Union, Intersection, Difference, and Flatten.
  • Collection Methods: Collect items into slices, count them, partition them, and more.
  • Generalized Iterators: Supports all types, as it uses Go's generics.
  • Multiple Data Transformations: Sort, shuffle, reverse, compact, and manipulate iterator contents.

API Reference

Creating an Iterator
  1. From Slice

    iter := itertools.ToIter([]int{1, 2, 3, 4})
    
  2. Custom Sequences

    iter := itertools.NewIterator(1, 2, 3, 4)
    
  3. Repeated Values

    iter := itertools.Repeat(42, 5)
    
  4. Cycle

    iter := itertools.NewIterator(1, 2, 3).Cycle()
    

Stream Sources

Create lazy iterators from various stream sources:

Function Description
FromChannel(ch) Create iterator from a channel (consumes until closed)
FromChannelWithContext(ctx, ch) Channel iterator with cancellation support
FromReader(r) Create iterator from io.Reader (reads lines)
FromReaderWithContext(ctx, r) Reader iterator with cancellation support
FromCSV(r) Create iterator from CSV reader (yields []string rows)
FromCSVWithContext(ctx, r) CSV iterator with cancellation support
FromCSVWithHeaders(r) CSV iterator with header support (returns CSVRow)
FromCSVWithHeadersContext(ctx, r) CSV with headers and cancellation
FromFunc(fn) Create iterator from a generator function
FromFuncWithContext(ctx, fn) Generator with cancellation support
Range(start, end) Yields integers from start to end (exclusive)
RangeStep(start, end, step) Range with custom step size
Generate(fn) Infinite iterator by repeatedly calling function
GenerateWithContext(ctx, fn) Infinite generator with cancellation

Examples:

// From channel
ch := make(chan int)
go func() {
    for i := 0; i < 10; i++ {
        ch <- i
    }
    close(ch)
}()
iter := itertools.FromChannel(ch)
result := iter.Filter(func(x int) bool { return x%2 == 0 }).Collect()

// From CSV file (lazy loading)
file, _ := os.Open("large_data.csv")
defer file.Close()
csvReader := csv.NewReader(file)
iter, headers, _ := itertools.FromCSVWithHeaders(csvReader)

// Process only what you need - doesn't load entire file!
highEarners := iter.
    Filter(func(row itertools.CSVRow) bool {
        salary, _ := strconv.Atoi(row.GetByHeader(headers, "salary"))
        return salary > 100000
    }).
    Take(100). // Only first 100 high earners
    Collect()

// From text file
file, _ := os.Open("large_file.txt")
defer file.Close()
iter := itertools.FromReader(file)
errorLines := iter.Filter(func(line string) bool {
    return strings.Contains(line, "ERROR")
}).Collect()

// Fibonacci generator
a, b := 0, 1
iter := itertools.FromFunc(func() (int, bool) {
    result := a
    a, b = b, a+b
    return result, true
})
first10 := iter.Take(10).Collect() // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

// Range
iter := itertools.Range(0, 5)
squares := iter.Map(func(x int) int { return x * x }).Collect() // [0, 1, 4, 9, 16]

// With context (cancellable)
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
iter := itertools.FromChannelWithContext(ctx, ch)
result := iter.Collect() // Stops after 5 seconds or when channel closes

Iterator Methods

These methods modify or operate on the elements of an iterator.

Method Description
Next() Advances the iterator to the next element.
Current() Returns the current element.
Collect() Collects all elements into a slice.
Each(f func(V)) Applies f to each element.
Filter(f func(V) bool) Yields only elements that satisfy the predicate f.
Map(f func(V) V) Transforms each element using f.
Reverse() Iterates over elements in reverse order.
Take(n int) Takes the first n elements.
Drop(n int) Skips the first n elements.
TakeWhile(f func(V) bool) Yields elements while the predicate f is true.
DropWhile(f func(V) bool) Drops elements while the predicate f is true.
Count() Counts the total number of elements.
First() Returns the first element (panics if empty).
Last() Returns the last element (panics if empty).
Nth(n int) Returns the nth element (panics if not enough elements).
FirstOr(default V) Returns the first element or default if empty.
LastOr(default V) Returns the last element or default if empty.
NthOr(n int, default V) Returns the nth element or default if not found.
Sort(less func(a, b V) bool) Sorts elements according to less.
Min(less func(a, b V) bool) Returns the minimum element.
Max(less func(a, b V) bool) Returns the maximum element.
Any(f func(V) bool) Returns true if any element satisfies f.
All(f func(V) bool) Returns true if all elements satisfy f.
Find(f func(V) bool) Returns the first element that satisfies f.
Index(f func(V) bool) Returns the index of the first element that satisfies f.
LastIndex(f func(V) bool) Returns the index of the last element that satisfies f.
IsSorted(less func(a, b V) bool) Returns true if the elements are sorted.
Replace(f func(V) bool, replacement V) Replaces elements that satisfy f.
Compact() Removes nil/zero-value elements.
Union(other *Iterator, keyFunc func(V) any) Merges two iterators without duplicates.
Difference(other *Iterator, keyFunc func(V) any) Difference of two iterators.
Intersection(other *Iterator, keyFunc func(V) any) Intersection of two iterators.

Utility Functions
Function Description
Zip(it1, it2) Zips two iterators together.
Zip2(it1, it2, fill) Zips two iterators, filling extra elements with fill.
Fold(it, transform, initial) Reduces the elements using transform.
Sum(it, transform, zero) Sums the elements.
Product(it, transform, one) Computes the product of elements.
ChunkSlice(it, size) Returns slices of size.
Flatten(it1, it2, ...) Flattens multiple iterators into one.
CartesianProduct(it1, it2) Generates Cartesian product of two iterators.

Examples

Basic Usage
// Create an iterator from a slice
iter := itertools.ToIter([]int{1, 2, 3, 4})

// Filter and Map
result := iter.Filter(func(x int) bool { return x%2 == 0 }).
    Map(func(x int) int { return x * 2 }).
    Collect()

fmt.Println(result) // Output: [4, 8]
Stream Processing
// Process a large file lazily
file, _ := os.Open("server.log")
defer file.Close()

errorCount := itertools.FromReader(file).
    Filter(func(line string) bool {
        return strings.Contains(line, "ERROR")
    }).
    Count()

fmt.Println("Total errors:", errorCount)
// Channel-based pipeline
dataCh := make(chan int)
go func() {
    for i := 0; i < 1000; i++ {
        dataCh <- i
    }
    close(dataCh)
}()

result := itertools.FromChannel(dataCh).
    Filter(func(x int) bool { return x%2 == 0 }).
    Map(func(x int) int { return x * x }).
    Take(10).
    Collect()

fmt.Println(result) // First 10 even squares
// Fibonacci with generator
a, b := 0, 1
fib := itertools.FromFunc(func() (int, bool) {
    result := a
    a, b = b, a+b
    return result, true
})

first20 := fib.Take(20).Collect()
fmt.Println(first20)
// Context-aware processing with timeout
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
defer cancel()

counter := 0
iter := itertools.GenerateWithContext(ctx, func() int {
    counter++
    time.Sleep(100 * time.Millisecond)
    return counter
})

// Collects elements until timeout
result := iter.Collect()
fmt.Println("Collected before timeout:", result)
Large CSV File Processing
// Process large CSV without loading entire file into memory
file, _ := os.Open("sales_data.csv") // 10GB file with millions of rows
defer file.Close()

csvReader := csv.NewReader(file)
iter, headers, _ := itertools.FromCSVWithHeaders(csvReader)

// Find top 100 sales over $10,000 - only processes until we find 100
topSales := iter.
    Filter(func(row itertools.CSVRow) bool {
        amount, _ := strconv.ParseFloat(row.GetByHeader(headers, "amount"), 64)
        return amount > 10000
    }).
    Take(100). // Stop after finding 100
    Collect()

fmt.Printf("Found %d high-value sales\n", len(topSales))
// CSV aggregation by category
file, _ := os.Open("products.csv")
defer file.Close()

csvReader := csv.NewReader(file)
iter, headers, _ := itertools.FromCSVWithHeaders(csvReader)

// Group by category
categoryTotals := make(map[string]float64)
iter.Each(func(row itertools.CSVRow) {
    category := row.GetByHeader(headers, "category")
    price, _ := strconv.ParseFloat(row.GetByHeader(headers, "price"), 64)
    quantity, _ := strconv.Atoi(row.GetByHeader(headers, "quantity"))
    
    categoryTotals[category] += price * float64(quantity)
})

for category, total := range categoryTotals {
    fmt.Printf("%s: $%.2f\n", category, total)
}
// CSV with complex filtering pipeline
file, _ := os.Open("users.csv")
defer file.Close()

csvReader := csv.NewReader(file)
iter, headers, _ := itertools.FromCSVWithHeaders(csvReader)

activeHighScoreUsers := iter.
    Filter(func(row itertools.CSVRow) bool {
        return row.GetByHeader(headers, "status") == "active"
    }).
    Filter(func(row itertools.CSVRow) bool {
        score, _ := strconv.Atoi(row.GetByHeader(headers, "score"))
        return score > 80
    }).
    Filter(func(row itertools.CSVRow) bool {
        email := row.GetByHeader(headers, "email")
        return strings.HasSuffix(email, "@company.com")
    }).
    Take(50). // First 50 matching users
    Collect()

fmt.Printf("Qualified users: %d\n", len(activeHighScoreUsers))
Sort Elements
iter := itertools.ToIter([]int{3, 1, 4, 2})
sorted := iter.Sort(func(a, b int) bool { return a < b }).Collect()
fmt.Println(sorted) // Output: [1, 2, 3, 4]
Zip Two Iterators
iter1 := itertools.ToIter([]int{1, 2, 3})
iter2 := itertools.ToIter([]string{"a", "b", "c"})

zipped := itertools.Zip(iter1, iter2).Collect()
fmt.Println(zipped) 
// Output: [{1 a} {2 b} {3 c}]
Generate Cartesian Product
iter1 := itertools.ToIter([]int{1, 2})
iter2 := itertools.ToIter([]string{"a", "b"})

cartesian := itertools.CartesianProduct(iter1, iter2).Collect()
fmt.Println(cartesian)
// Output: [{X: 1, Y: "a"} {X: 1, Y: "b"} {X: 2, Y: "a"} {X: 2, Y: "b"}]

Performance & Benchmarks

This library is designed for high performance with lazy evaluation and minimal memory overhead.

Key Performance Features
  • Lazy Evaluation: Elements are computed on-demand using Go's iter.Pull() - only processes what you need
  • Zero-Copy Operations: Most operations work directly on the sequence without copying data
  • Memory Efficient: Uses pull-based iteration to avoid storing entire collections in memory
  • Early Termination: Stops processing immediately when conditions are met
Benchmark Results

Run benchmarks yourself:

go test -bench=. -benchmem

Sample results on Apple M4 Pro:

Collection Iterators:

BenchmarkIterator_Next_TakeFirst-12    1000000    2004 ns/op    756 B/op    9 allocs/op
BenchmarkIterator_Next_TakeAll-12         1000  1180000 ns/op  80056 B/op   10009 allocs/op
BenchmarkIterator_Collect-12              8000   142000 ns/op  81920 B/op       2 allocs/op
BenchmarkIterator_Filter_TakeFirst-12   500000    2100 ns/op    756 B/op       9 allocs/op
BenchmarkIterator_Chain-12              100000   11500 ns/op   1912 B/op      15 allocs/op

Stream Iterators:

BenchmarkFromChannel-12                  396609    3034 ns/op    3128 B/op      12 allocs/op
BenchmarkFromReader-12                    68184   17112 ns/op   43488 B/op    1016 allocs/op
BenchmarkFromFunc_Fibonacci-12          6594074     180 ns/op     608 B/op      10 allocs/op
BenchmarkRange-12                         12000  100000 ns/op   81920 B/op       2 allocs/op
BenchmarkGenerate-12                     100000   11000 ns/op    8184 B/op      11 allocs/op

Key Takeaways:

  • Taking the first element from 1 million items is extremely fast (~2µs) - the iterator doesn't process all elements
  • Fibonacci generator creates 20 numbers in ~180ns - extremely efficient for mathematical sequences
  • Channel-based iteration is fast and integrates well with Go's concurrency model
Memory Usage

The iterator uses a pull-based model that:

  • Only stores the current element (not the entire collection)
  • Allows filtering/mapping without intermediate allocations
  • Properly cleans up resources with stop() function
When to Use This Library

Good for:

  • Large datasets where you need only a subset of results
  • Chaining multiple transformations (filter, map, take, etc.)
  • Memory-constrained environments
  • Functional-style data processing
  • Processing streams (files, channels, network data)
  • Building data pipelines with cancellation support
  • Generating infinite sequences (Fibonacci, primes, etc.)

Not ideal for:

  • Simple single-pass operations on small slices (use native Go loops)
  • When you need random access to all elements
  • Real-time systems with strict latency requirements (use pre-allocated slices)

Stream Processing Use Cases

Log File Analysis:

file, _ := os.Open("app.log")
defer file.Close()

errors := itertools.FromReader(file).
    Filter(func(line string) bool { return strings.Contains(line, "ERROR") }).
    Take(100). // First 100 errors
    Collect()

Real-time Data Processing:

dataCh := subscribeToDataStream() // Returns <-chan Event

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

processed := itertools.FromChannelWithContext(ctx, dataCh).
    Filter(func(e Event) bool { return e.Priority == "HIGH" }).
    Map(func(e Event) Alert { return processEvent(e) }).
    Collect()

Infinite Sequences:

// Generate prime numbers
iter := itertools.Generate(func() int {
    // your prime generator logic
}).TakeWhile(func(p int) bool { return p < 1000 })

primes := iter.Collect()

Contributing

Contributions are welcome! To get started:

  1. Fork the repository.
  2. Create a new branch for your feature/bugfix.
  3. Submit a pull request.

License

This project is licensed under the MIT License. See the LICENSE file for details.


With this library, you can process collections in a functional, chainable, and lazy manner. From filtering and mapping to complex operations like cartesian products, this iterator system brings the power of iterables to Go. Happy iterating! 🎉

Documentation

Overview

Package itertools provides a powerful and flexible generic iterator system for Go. It enables functional-style iteration, transformation, and aggregation of collections with support for lazy evaluation and composable operations.

The library is inspired by iterator protocols found in Python and Rust, adapted for Go's type system and idioms using Go 1.23+ generics and iter.Seq.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Fold

func Fold[V any, T any](it *Iterator[V], transform func(T, V) T, initial T) T

Fold accumulates the elements of the iterator using a binary operation. Also known as reduce or aggregate in other languages.

The transform function takes an accumulator and the next value, returning the new accumulator value.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5})
sum := itertools.Fold(iter, func(acc, v int) int { return acc + v }, 0)
// sum is 15

func Product

func Product[V any, T Productable](it *Iterator[V], transform func(V) T, one T) T

Product multiplies all elements of the iterator after applying the transform function. The one parameter specifies the multiplicative identity for the result type.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5})
product := itertools.Product(iter, func(v int) int { return v }, 1)
// product is 120

func Sum

func Sum[V any, T cmp.Ordered](it *Iterator[V], transform func(V) T, zero T) T

Sum adds all elements of the iterator after applying the transform function. The zero parameter specifies the additive identity for the result type.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5})
sum := itertools.Sum(iter, func(v int) int { return v }, 0)
// sum is 15

// Sum of squares
iter2 := itertools.ToIter([]int{1, 2, 3})
sumSquares := itertools.Sum(iter2, func(v int) int { return v * v }, 0)
// sumSquares is 14

Types

type CSVRow added in v1.1.0

type CSVRow struct {
	Fields []string
	Index  int
}

CSVRow represents a parsed CSV row with helper methods

func (CSVRow) Get added in v1.1.0

func (r CSVRow) Get(index int) string

Get returns the field at the given index, or empty string if out of bounds

func (CSVRow) GetByHeader added in v1.1.0

func (r CSVRow) GetByHeader(headers []string, name string) string

GetByHeader returns the field with the given header name headers should be the first row of the CSV

type Iterator

type Iterator[V any] struct {
	// contains filtered or unexported fields
}

Iterator is a generic iterator that can iterate over any type of sequence. It supports both functional-style operations (via the seq field) and imperative-style iteration (via Next/Current methods).

Iterator provides lazy evaluation - operations are only performed when elements are consumed. This allows efficient processing of large or infinite sequences.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5})
result := iter.
    Filter(func(x int) bool { return x%2 == 0 }).
    Map(func(x int) int { return x * x }).
    Collect()
// result is []int{4, 16}

func CartesianProduct

func CartesianProduct[A, B any](it1 *Iterator[A], it2 *Iterator[B]) *Iterator[struct {
	X A
	Y B
}]

CartesianProduct returns an iterator of all pairs of elements from two iterators. Note: The second iterator (it2) is fully collected into memory to enable multiple iterations over it for each element in it1. Use with caution for large datasets.

Example:

iter1 := ToIter([]int{1, 2})
iter2 := ToIter([]string{"a", "b"})
product := CartesianProduct(iter1, iter2).Collect()
// Result: [{1, "a"}, {1, "b"}, {2, "a"}, {2, "b"}]

func ChunkList

func ChunkList[V any](it *Iterator[V], size int) []*Iterator[V]

ChunkList returns a slice of iterators, each containing up to `size` elements. This is a convenience function that collects Chunks into a slice.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5, 6})
chunks := itertools.ChunkList(iter, 2)
// chunks is []*Iterator with 3 iterators containing [1,2], [3,4], [5,6]

func ChunkSlice

func ChunkSlice[V any](it *Iterator[V], size int) *Iterator[[]V]

ChunkSlice returns an iterator that yields slices of up to `size` elements. The last chunk may contain fewer elements if the total is not divisible by size.

Each chunk is a separate slice - modifications won't affect the original data.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5, 6, 7})
chunks := itertools.ChunkSlice(iter, 3).Collect()
// chunks is [][]int{{1, 2, 3}, {4, 5, 6}, {7}}

func Chunks

func Chunks[V any](it *Iterator[V], size int) *Iterator[*Iterator[V]]

Chunks returns an iterator that yields iterators, each containing up to `size` elements. The last chunk may contain fewer elements if the total is not divisible by size.

Unlike ChunkSlice, this returns iterators instead of slices.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5})
chunks := itertools.Chunks(iter, 2)
for chunks.Next() {
    chunk := chunks.Current()
    fmt.Println(chunk.Collect())
}
// Output: [1 2]
//         [3 4]
//         [5]

func Flatten

func Flatten[V any](its ...*Iterator[V]) *Iterator[V]

Flatten concatenates multiple iterators into a single iterator. Elements are yielded in order: all elements from the first iterator, then all from the second, and so on.

Properly handles early termination - stops immediately when yield returns false.

Example:

iter1 := itertools.ToIter([]int{1, 2, 3})
iter2 := itertools.ToIter([]int{4, 5, 6})
iter3 := itertools.ToIter([]int{7, 8, 9})
flattened := itertools.Flatten(iter1, iter2, iter3).Collect()
// flattened is []int{1, 2, 3, 4, 5, 6, 7, 8, 9}

func FromCSV added in v1.1.0

func FromCSV(r *csv.Reader) *Iterator[[]string]

FromCSV creates a lazy Iterator that reads records from a CSV reader. Each element is a []string representing one CSV row. This is useful for processing large CSV files without loading them entirely into memory.

Example:

file, _ := os.Open("large_data.csv")
defer file.Close()
iter := itertools.FromCSV(csv.NewReader(file))
records := iter.Filter(func(row []string) bool {
    return len(row) > 0 && row[0] != ""
}).Take(100).Collect()

func FromCSVWithContext added in v1.1.0

func FromCSVWithContext(ctx context.Context, r *csv.Reader) *Iterator[[]string]

FromCSVWithContext creates a lazy Iterator from a CSV reader with context support. The iterator will stop when either the CSV is exhausted or the context is cancelled.

Example:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
file, _ := os.Open("huge_data.csv")
defer file.Close()
iter := itertools.FromCSVWithContext(ctx, csv.NewReader(file))
records := iter.Collect()

func FromCSVWithHeaders added in v1.1.0

func FromCSVWithHeaders(r *csv.Reader) (*Iterator[CSVRow], []string, error)

FromCSVWithHeaders creates a lazy Iterator that reads CSV records with header support. The first row is treated as headers and subsequent rows are wrapped in CSVRow for easier access. Returns the iterator and the header row.

Example:

file, _ := os.Open("data.csv")
defer file.Close()
iter, headers := itertools.FromCSVWithHeaders(csv.NewReader(file))
records := iter.Filter(func(row CSVRow) bool {
    age := row.GetByHeader(headers, "age")
    return age != "" && age > "30"
}).Collect()

func FromCSVWithHeadersContext added in v1.1.0

func FromCSVWithHeadersContext(ctx context.Context, r *csv.Reader) (*Iterator[CSVRow], []string, error)

FromCSVWithHeadersContext creates a lazy Iterator from CSV with headers and context support.

Example:

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute)
defer cancel()
file, _ := os.Open("large.csv")
defer file.Close()
iter, headers, _ := itertools.FromCSVWithHeadersContext(ctx, csv.NewReader(file))

func FromChannel added in v1.1.0

func FromChannel[V any](ch <-chan V) *Iterator[V]

FromChannel creates a lazy Iterator from a channel. The iterator will consume elements from the channel until it's closed. This is useful for concurrent producer-consumer patterns.

Example:

ch := make(chan int)
go func() {
    for i := 0; i < 10; i++ {
        ch <- i
    }
    close(ch)
}()
iter := itertools.FromChannel(ch)
result := iter.Filter(func(x int) bool { return x%2 == 0 }).Collect()

func FromChannelWithContext added in v1.1.0

func FromChannelWithContext[V any](ctx context.Context, ch <-chan V) *Iterator[V]

FromChannelWithContext creates a lazy Iterator from a channel with context support. The iterator will stop when either the channel is closed or the context is cancelled.

Example:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
ch := make(chan int)
iter := itertools.FromChannelWithContext(ctx, ch)

func FromFunc added in v1.1.0

func FromFunc[V any](fn func() (V, bool)) *Iterator[V]

FromFunc creates a lazy Iterator from a generator function. The function is called repeatedly until it returns false. This is useful for generating infinite sequences or custom data sources.

Example:

// Fibonacci sequence
iter := itertools.FromFunc(func() (int, bool) {
    a, b := 0, 1
    return func() (int, bool) {
        result := a
        a, b = b, a+b
        return result, true
    }
}())
first10 := iter.Take(10).Collect()

func FromFuncWithContext added in v1.1.0

func FromFuncWithContext[V any](ctx context.Context, fn func() (V, bool)) *Iterator[V]

FromFuncWithContext creates a lazy Iterator from a generator function with context support. The iterator will stop when either the function returns false or the context is cancelled.

Example:

ctx, cancel := context.WithCancel(context.Background())
defer cancel()
iter := itertools.FromFuncWithContext(ctx, func() (int, bool) {
    return rand.Int(), true
})
result := iter.Take(100).Collect()

func FromReader added in v1.1.0

func FromReader(r io.Reader) *Iterator[string]

FromReader creates a lazy Iterator that reads lines from an io.Reader. Each element is a line from the reader (without the newline character). This is useful for processing large files without loading them entirely into memory.

Example:

file, _ := os.Open("large_file.txt")
defer file.Close()
iter := itertools.FromReader(file)
count := iter.Filter(func(line string) bool {
    return strings.Contains(line, "ERROR")
}).Count()

func FromReaderWithContext added in v1.1.0

func FromReaderWithContext(ctx context.Context, r io.Reader) *Iterator[string]

FromReaderWithContext creates a lazy Iterator from an io.Reader with context support. The iterator will stop when either the reader is exhausted or the context is cancelled.

Example:

ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
file, _ := os.Open("large_file.txt")
defer file.Close()
iter := itertools.FromReaderWithContext(ctx, file)

func Generate added in v1.1.0

func Generate[V any](fn func() V) *Iterator[V]

Generate creates an infinite Iterator by repeatedly calling a generator function. Use Take() or TakeWhile() to limit the output.

Example:

counter := 0
iter := itertools.Generate(func() int {
    counter++
    return counter
})
first5 := iter.Take(5).Collect()  // [1, 2, 3, 4, 5]

func GenerateWithContext added in v1.1.0

func GenerateWithContext[V any](ctx context.Context, fn func() V) *Iterator[V]

GenerateWithContext creates an infinite Iterator with context support.

Example:

ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
defer cancel()
iter := itertools.GenerateWithContext(ctx, func() int {
    return rand.Int()
})
result := iter.Take(1000).Collect()

func NewIterator

func NewIterator[V any](v ...V) *Iterator[V]

NewIterator creates a new iterator from a variadic list of values.

Example:

iter := itertools.NewIterator(1, 2, 3, 4, 5)
result := iter.Collect()
// result is []int{1, 2, 3, 4, 5}

func Range added in v1.1.0

func Range(start, end int) *Iterator[int]

Range creates an Iterator that yields integers from start (inclusive) to end (exclusive). This is useful for generating sequences of numbers.

Example:

iter := itertools.Range(0, 10)  // yields 0, 1, 2, ..., 9
squares := iter.Map(func(x int) int { return x * x }).Collect()

func RangeStep added in v1.1.0

func RangeStep(start, end, step int) *Iterator[int]

RangeStep creates an Iterator that yields integers from start to end with a given step.

Example:

iter := itertools.RangeStep(0, 10, 2)  // yields 0, 2, 4, 6, 8
result := iter.Collect()

func Repeat

func Repeat[V any](v V, n int) *Iterator[V]

Repeat creates an iterator that yields the same value n times.

Example:

iter := itertools.Repeat(42, 5)
result := iter.Collect()
// result is []int{42, 42, 42, 42, 42}

func ToIter

func ToIter[V any](slice []V) *Iterator[V]

ToIter creates an Iterator from a slice. The iterator will yield each element of the slice in order.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4})
for iter.Next() {
    fmt.Println(iter.Current())
}

func Zip

func Zip[A, B any](it1 *Iterator[A], it2 *Iterator[B]) *Iterator[struct {
	First  A
	Second B
}]

Zip combines two iterators element-wise into a single iterator of pairs. Iteration stops when either iterator is exhausted.

The resulting iterator yields struct values with First and Second fields.

Example:

iter1 := itertools.ToIter([]int{1, 2, 3})
iter2 := itertools.ToIter([]string{"a", "b", "c"})
zipped := itertools.Zip(iter1, iter2).Collect()
// zipped is [{First: 1, Second: "a"}, {First: 2, Second: "b"}, {First: 3, Second: "c"}]

func Zip2

func Zip2[A, B any](it1 *Iterator[A], it2 *Iterator[B], fill struct {
	First  A
	Second B
}) *Iterator[struct {
	First  A
	Second B
}]

Zip2 combines two iterators element-wise into a single iterator of pairs. Unlike Zip, if one iterator is longer than the other, the shorter one is extended using the fill values.

Example:

iter1 := itertools.ToIter([]int{1, 2, 3})
iter2 := itertools.ToIter([]string{"a"})
fill := struct{ First int; Second string }{0, ""}
zipped := itertools.Zip2(iter1, iter2, fill).Collect()
// zipped is [{1, "a"}, {2, ""}, {3, ""}]

func (*Iterator[V]) All

func (it *Iterator[V]) All(predicate func(V) bool) bool

All returns true if all elements in the iterator satisfy the predicate. Returns true for an empty iterator.

Example:

iter := itertools.ToIter([]int{2, 4, 6, 8})
allEven := iter.All(func(x int) bool { return x%2 == 0 }) // Returns true

func (*Iterator[V]) Any

func (it *Iterator[V]) Any(predicate func(V) bool) bool

Any returns true if any element in the iterator satisfies the predicate. Returns false for an empty iterator.

Example:

iter := itertools.ToIter([]int{1, 3, 5, 6, 7})
hasEven := iter.Any(func(x int) bool { return x%2 == 0 }) // Returns true

func (*Iterator[V]) AssertEq

func (it *Iterator[V]) AssertEq(expected []V, predicate func(V, V) bool) bool

AssertEq checks if the iterator elements are equal to the expected slice using the provided equality predicate. Returns true if all elements match, false otherwise.

Example:

iter := itertools.ToIter([]int{1, 2, 3})
equal := iter.AssertEq([]int{1, 2, 3}, func(a, b int) bool { return a == b })
// equal is true

func (*Iterator[V]) Chain

func (it *Iterator[V]) Chain(other *Iterator[V]) *Iterator[V]

Chain concatenates this iterator with another, yielding all elements from the first iterator followed by all elements from the second.

Chain properly handles early termination - if iteration stops early, the second iterator may not be consumed at all.

Example:

iter1 := itertools.ToIter([]int{1, 2, 3})
iter2 := itertools.ToIter([]int{4, 5, 6})
chained := iter1.Chain(iter2).Collect()
// chained is []int{1, 2, 3, 4, 5, 6}

func (*Iterator[V]) Collect

func (it *Iterator[V]) Collect() []V

Collect consumes the iterator and returns all elements as a slice. After calling Collect, the iterator is exhausted.

Note: This loads all elements into memory. For large sequences, consider using streaming operations like Each or Filter.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4})
evens := iter.Filter(func(x int) bool { return x%2 == 0 }).Collect()
// evens is []int{2, 4}

func (*Iterator[V]) Compact

func (it *Iterator[V]) Compact() *Iterator[V]

Compact removes zero-value elements from the iterator. Zero values are detected using reflection.

Example:

iter := itertools.ToIter([]int{1, 0, 2, 0, 3})
compacted := iter.Compact().Collect()
// compacted is []int{1, 2, 3}

func (*Iterator[V]) CompactWith

func (it *Iterator[V]) CompactWith(zero V) *Iterator[V]

CompactWith removes elements that are equal to the specified zero value.

Example:

iter := itertools.ToIter([]int{1, -1, 2, -1, 3})
compacted := iter.CompactWith(-1).Collect()
// compacted is []int{1, 2, 3}

func (*Iterator[V]) Count

func (it *Iterator[V]) Count() int

Count returns the total number of elements in the iterator. The iterator is consumed after calling Count.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5})
count := iter.Filter(func(x int) bool { return x%2 == 0 }).Count()
// count is 2

func (*Iterator[V]) Current

func (it *Iterator[V]) Current() V

Current returns the current element of the iterator. It panics if called before Next() or after the iterator is exhausted.

Always call Next() before calling Current() to ensure a valid element exists.

Example:

iter := itertools.ToIter([]int{1, 2, 3})
if iter.Next() {
    fmt.Println(iter.Current()) // Prints: 1
}

func (*Iterator[V]) Cycle

func (it *Iterator[V]) Cycle() *Iterator[V]

Cycle returns an infinite iterator that repeatedly cycles through the elements.

Note: This collects all elements into memory. Be careful when using with infinite iterators or very large sequences.

Example:

iter := itertools.ToIter([]int{1, 2, 3})
cycled := iter.Cycle().Take(7).Collect()
// cycled is []int{1, 2, 3, 1, 2, 3, 1}

func (*Iterator[V]) Difference

func (it *Iterator[V]) Difference(other *Iterator[V], keyFunc func(V) any) *Iterator[V]

Difference returns an iterator that yields elements present in this iterator but not in the other iterator. The keyFunc extracts a comparable key for matching elements.

Note: This method consumes the other iterator to build a set of keys.

Example:

iter1 := itertools.ToIter([]int{1, 2, 3, 4})
iter2 := itertools.ToIter([]int{3, 4, 5, 6})
diff := iter1.Difference(iter2, func(x int) any { return x }).Collect()
// diff is []int{1, 2}

func (*Iterator[V]) Drop

func (it *Iterator[V]) Drop(n int) *Iterator[V]

Drop returns a new iterator that skips the first n elements and yields the rest. If the iterator has n or fewer elements, the resulting iterator is empty.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5})
rest := iter.Drop(2).Collect()
// rest is []int{3, 4, 5}

func (*Iterator[V]) DropWhile

func (it *Iterator[V]) DropWhile(predicate func(V) bool) *Iterator[V]

DropWhile returns a new iterator that skips elements while the predicate is true, then yields all remaining elements.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5})
result := iter.DropWhile(func(x int) bool { return x < 3 }).Collect()
// result is []int{3, 4, 5}

func (*Iterator[V]) Each

func (it *Iterator[V]) Each(f func(V))

Each applies a function to each element of the iterator. This is useful for performing side effects like printing or logging.

The iterator is consumed after calling Each.

Example:

iter := itertools.ToIter([]int{1, 2, 3})
iter.Each(func(x int) {
    fmt.Println(x)
})

func (*Iterator[V]) Filter

func (it *Iterator[V]) Filter(predicate func(V) bool) *Iterator[V]

Filter returns a new iterator that only yields elements satisfying the predicate. Elements for which predicate returns false are skipped.

Filter is lazy - predicates are evaluated only as elements are consumed.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5, 6})
evens := iter.Filter(func(x int) bool { return x%2 == 0 }).Collect()
// evens is []int{2, 4, 6}

func (*Iterator[V]) Find

func (it *Iterator[V]) Find(predicate func(V) bool) (V, bool)

Find returns the first element that satisfies the predicate along with true, or the zero value and false if no element satisfies the predicate.

Example:

iter := itertools.ToIter([]int{1, 3, 5, 6, 7})
first, found := iter.Find(func(x int) bool { return x%2 == 0 })
// first is 6, found is true

func (*Iterator[V]) First

func (it *Iterator[V]) First() V

First returns the first element of the iterator. It panics if the iterator is empty.

For a safe alternative that doesn't panic, use FirstOr.

Example:

iter := itertools.ToIter([]int{1, 2, 3})
first := iter.First() // Returns 1

func (*Iterator[V]) FirstOr added in v1.1.0

func (it *Iterator[V]) FirstOr(defaultValue V) V

FirstOr returns the first element of the iterator, or defaultValue if the iterator is empty. This is a safe alternative to First that doesn't panic.

Example:

iter := itertools.ToIter([]int{})
first := iter.FirstOr(999) // Returns 999

iter2 := itertools.ToIter([]int{1, 2, 3})
first2 := iter2.FirstOr(999) // Returns 1

func (*Iterator[V]) GroupBy

func (it *Iterator[V]) GroupBy(keyFunc func(V) string) map[string][]V

GroupBy groups elements by a key function into a map where keys are strings and values are slices of elements with that key.

Example:

type Person struct { Name string; Age int }
iter := itertools.ToIter([]Person{
    {"Alice", 25}, {"Bob", 30}, {"Charlie", 25},
})
byAge := iter.GroupBy(func(p Person) string {
    return fmt.Sprintf("%d", p.Age)
})
// byAge["25"] is []Person{{"Alice", 25}, {"Charlie", 25}}
// byAge["30"] is []Person{{"Bob", 30}}

func (*Iterator[V]) Index

func (it *Iterator[V]) Index(predicate func(V) bool) int

Index returns the 0-based index of the first element that satisfies the predicate. Returns -1 if no element satisfies the predicate.

Example:

iter := itertools.ToIter([]int{10, 20, 30, 40, 50})
idx := iter.Index(func(x int) bool { return x == 30 })
// idx is 2

func (*Iterator[V]) Intersection

func (it *Iterator[V]) Intersection(other *Iterator[V], keyFunc func(V) any) *Iterator[V]

Intersection returns an iterator that yields elements present in both iterators. The keyFunc extracts a comparable key for matching elements.

Note: This method consumes the other iterator to build a set of keys.

Example:

iter1 := itertools.ToIter([]int{1, 2, 3, 4})
iter2 := itertools.ToIter([]int{3, 4, 5, 6})
intersection := iter1.Intersection(iter2, func(x int) any { return x }).Collect()
// intersection is []int{3, 4}

func (*Iterator[V]) IsSorted

func (it *Iterator[V]) IsSorted(less func(a, b V) bool) bool

IsSorted returns true if the elements are sorted according to the less function. Returns true for empty iterators and single-element iterators.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5})
sorted := iter.IsSorted(func(a, b int) bool { return a < b })
// sorted is true

func (*Iterator[V]) Last

func (it *Iterator[V]) Last() V

Last returns the last element of the iterator. It panics if the iterator is empty.

For a safe alternative that doesn't panic, use LastOr.

Note: This method collects all elements into memory.

Example:

iter := itertools.ToIter([]int{1, 2, 3})
last := iter.Last() // Returns 3

func (*Iterator[V]) LastIndex

func (it *Iterator[V]) LastIndex(predicate func(V) bool) int

LastIndex returns the 0-based index of the last element that satisfies the predicate. Returns -1 if no element satisfies the predicate.

Example:

iter := itertools.ToIter([]int{10, 20, 30, 20, 50})
idx := iter.LastIndex(func(x int) bool { return x == 20 })
// idx is 3

func (*Iterator[V]) LastOr added in v1.1.0

func (it *Iterator[V]) LastOr(defaultValue V) V

LastOr returns the last element of the iterator, or defaultValue if the iterator is empty. This is a safe alternative to Last that doesn't panic.

Note: This method must consume the entire iterator.

Example:

iter := itertools.ToIter([]int{})
last := iter.LastOr(999) // Returns 999

iter2 := itertools.ToIter([]int{1, 2, 3})
last2 := iter2.LastOr(999) // Returns 3

func (*Iterator[V]) Map

func (it *Iterator[V]) Map(f func(V) V) *Iterator[V]

Map transforms each element using the provided function and returns a new iterator.

Map is lazy - the transformation is applied only as elements are consumed.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4})
squared := iter.Map(func(x int) int { return x * x }).Collect()
// squared is []int{1, 4, 9, 16}

func (*Iterator[V]) Max

func (it *Iterator[V]) Max(less func(a, b V) bool) (V, bool)

Max returns the maximum element according to the less function, along with true. Returns the zero value and false if the iterator is empty.

Example:

iter := itertools.ToIter([]int{3, 1, 4, 1, 5})
max, found := iter.Max(func(a, b int) bool { return a < b })
// max is 5, found is true

func (*Iterator[V]) Min

func (it *Iterator[V]) Min(less func(a, b V) bool) (V, bool)

Min returns the minimum element according to the less function, along with true. Returns the zero value and false if the iterator is empty.

Example:

iter := itertools.ToIter([]int{3, 1, 4, 1, 5})
min, found := iter.Min(func(a, b int) bool { return a < b })
// min is 1, found is true

func (*Iterator[V]) Next

func (it *Iterator[V]) Next() bool

Next advances the iterator to the next element and returns true if successful. It returns false when the iterator is exhausted.

Next must be called before accessing the current element via Current(). After Next returns false, subsequent calls will continue to return false.

Example:

iter := itertools.ToIter([]int{1, 2, 3})
for iter.Next() {
    fmt.Println(iter.Current())
}

func (*Iterator[V]) Nth

func (it *Iterator[V]) Nth(n int) V

Nth returns the nth element (0-indexed) of the iterator. It panics if the iterator has fewer than n+1 elements.

For a safe alternative that doesn't panic, use NthOr.

Example:

iter := itertools.ToIter([]int{10, 20, 30, 40})
third := iter.Nth(2) // Returns 30

func (*Iterator[V]) NthOr added in v1.1.0

func (it *Iterator[V]) NthOr(n int, defaultValue V) V

NthOr returns the nth element (0-indexed) of the iterator, or defaultValue if there aren't enough elements. This is a safe alternative to Nth that doesn't panic.

Example:

iter := itertools.ToIter([]int{10, 20, 30})
third := iter.NthOr(2, 999)  // Returns 30
fifth := iter.NthOr(4, 999)  // Returns 999 (not enough elements)

func (*Iterator[V]) Partition

func (it *Iterator[V]) Partition(predicate func(V) bool) (matched *Iterator[V], unmatched *Iterator[V])

Partition splits the iterator into two iterators: one with elements that satisfy the predicate, and one with elements that don't.

Note: This method collects all elements into memory.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5, 6})
evens, odds := iter.Partition(func(x int) bool { return x%2 == 0 })
// evens.Collect() is []int{2, 4, 6}
// odds.Collect() is []int{1, 3, 5}

func (*Iterator[V]) Replace

func (it *Iterator[V]) Replace(predicate func(V) bool, replacement V) *Iterator[V]

Replace returns an iterator that replaces elements satisfying the predicate with the replacement value.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5})
replaced := iter.Replace(func(x int) bool { return x%2 == 0 }, 0).Collect()
// replaced is []int{1, 0, 3, 0, 5}

func (*Iterator[V]) ReplaceAll

func (it *Iterator[V]) ReplaceAll(replacement V) *Iterator[V]

ReplaceAll returns an iterator that replaces all elements with the replacement value.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5})
replaced := iter.ReplaceAll(0).Collect()
// replaced is []int{0, 0, 0, 0, 0}

func (*Iterator[V]) Reverse

func (it *Iterator[V]) Reverse() *Iterator[V]

Reverse returns a new iterator that yields elements in reverse order.

Note: This method collects all elements into memory to reverse them, so it's not suitable for infinite iterators.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4})
reversed := iter.Reverse().Collect()
// reversed is []int{4, 3, 2, 1}

func (*Iterator[V]) Shuffle

func (it *Iterator[V]) Shuffle() *Iterator[V]

Shuffle returns an iterator that yields elements in random order.

Note: This collects all elements into memory to shuffle them.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5})
shuffled := iter.Shuffle().Collect()
// shuffled is []int in random order, e.g., []int{3, 1, 5, 2, 4}

func (*Iterator[V]) Sort

func (it *Iterator[V]) Sort(less func(a, b V) bool) *Iterator[V]

Sort returns a new iterator with elements sorted according to the less function.

Note: This method collects all elements into memory to sort them.

Example:

iter := itertools.ToIter([]int{3, 1, 4, 1, 5})
sorted := iter.Sort(func(a, b int) bool { return a < b }).Collect()
// sorted is []int{1, 1, 3, 4, 5}

func (*Iterator[V]) StepBy

func (it *Iterator[V]) StepBy(n int) *Iterator[V]

StepBy returns an iterator that yields every nth element (0-indexed).

Example:

iter := itertools.ToIter([]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9})
everyThird := iter.StepBy(3).Collect()
// everyThird is []int{0, 3, 6, 9}

func (*Iterator[V]) String

func (it *Iterator[V]) String() string

String returns a string representation of the iterator by collecting all elements.

Note: This consumes the iterator and loads all elements into memory.

Example:

iter := itertools.ToIter([]int{1, 2, 3})
str := iter.String()
// str is "<Iterator: [1 2 3]>"

func (*Iterator[V]) Take

func (it *Iterator[V]) Take(n int) *Iterator[V]

Take returns a new iterator that yields at most the first n elements. If the iterator has fewer than n elements, all elements are yielded.

Take is lazy and supports early termination.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5})
first3 := iter.Take(3).Collect()
// first3 is []int{1, 2, 3}

func (*Iterator[V]) TakeWhile

func (it *Iterator[V]) TakeWhile(predicate func(V) bool) *Iterator[V]

TakeWhile returns a new iterator that yields elements while the predicate is true. Once the predicate returns false for an element, iteration stops.

Example:

iter := itertools.ToIter([]int{1, 2, 3, 4, 5, 1, 2})
result := iter.TakeWhile(func(x int) bool { return x < 4 }).Collect()
// result is []int{1, 2, 3}

func (*Iterator[V]) ToLower

func (it *Iterator[V]) ToLower() *Iterator[V]

ToLower converts string elements to lowercase. Non-string elements are unchanged.

Example:

iter := itertools.ToIter([]string{"HELLO", "WORLD"})
lower := iter.ToLower().Collect()
// lower is []string{"hello", "world"}

func (*Iterator[V]) ToUpper

func (it *Iterator[V]) ToUpper() *Iterator[V]

ToUpper converts string elements to uppercase. Non-string elements are unchanged.

Example:

iter := itertools.ToIter([]string{"hello", "world"})
upper := iter.ToUpper().Collect()
// upper is []string{"HELLO", "WORLD"}

func (*Iterator[V]) TrimSpace

func (it *Iterator[V]) TrimSpace() *Iterator[V]

TrimSpace trims whitespace from string elements. Non-string elements are unchanged.

Example:

iter := itertools.ToIter([]string{"  hello  ", "  world  "})
trimmed := iter.TrimSpace().Collect()
// trimmed is []string{"hello", "world"}

func (*Iterator[V]) Union

func (it *Iterator[V]) Union(other *Iterator[V], keyFunc func(V) any) *Iterator[V]

Union returns an iterator that yields elements from both iterators without duplicates. The keyFunc extracts a comparable key to detect duplicates.

Note: This method consumes both iterators and tracks seen keys in memory.

Example:

iter1 := itertools.ToIter([]int{1, 2, 3})
iter2 := itertools.ToIter([]int{3, 4, 5})
union := iter1.Union(iter2, func(x int) any { return x }).Collect()
// union is []int{1, 2, 3, 4, 5}

func (*Iterator[V]) Unique

func (it *Iterator[V]) Unique(keyFunc func(V) any) *Iterator[V]

Unique returns a new iterator that yields only unique elements based on the key function. The keyFunc extracts a comparable key from each element.

Each iteration creates a fresh set for tracking seen elements, making it safe to iterate multiple times.

Example:

iter := itertools.ToIter([]int{1, 2, 2, 3, 3, 3, 4})
unique := iter.Unique(func(x int) any { return x }).Collect()
// unique is []int{1, 2, 3, 4}

type Productable

type Productable interface {
	constraints.Integer | constraints.Float | constraints.Complex
}

Productable is a constraint for types that support multiplication.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL