itu

package module
v0.8.1 Latest Latest
Warning

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

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

README

itu

itu is a small Go library of iterator utilities.

It provides composable, lazy building blocks for working with Go’s iter package.

Docs: https://pkg.go.dev/github.com/lymar/itu

Goals

  • Least surprise: familiar iterator-style utilities for people coming from other ecosystems.
  • Lazy by default: values are produced only when the iterator is consumed.
  • Streaming-friendly: designed to work well with potentially large or infinite sequences.
  • Type-safe: uses Go generics.
  • No wrapper types: doesn’t add types like Pair/Tuple/Option; it composes standard iter types.
  • Minimal dependencies: built around iter.Seq and related iterator types.

Installation

go get github.com/lymar/itu

Quick example

package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	seq := slices.Values([]int{1, 2, 3, 4, 5, 6})

	evens := itu.Filter(seq, func(x int) bool { return x%2 == 0 })
	evensSquared := itu.Map(evens, func(x int) int { return x * x })

	for v := range evensSquared {
		fmt.Println(v) // 4, 16, 36
	}
}

Documentation

API docs are published on pkg.go.dev. Examples live next to the code (see *_test.go with Example... functions).

For local browsing, you can run:

./dev/doc.sh

Then open your browser at: http://localhost:6060/github.com/lymar/itu

License

Licensed under the MIT License. See LICENSE.

Documentation

Overview

Package itu provides iterator utilities for Go.

It offers small, composable building blocks for working with iter.Seq types, focusing on lazy, streaming-friendly iteration and functional-style composition.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func All added in v0.7.1

func All[T any](seq iter.Seq[T], pred func(T) bool) bool

All tests if every element of seq matches pred. All consumes seq eagerly and stops as soon as pred returns false. If seq is empty, All returns true.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	seq := slices.Values([]int{2, 4, 6})
	fmt.Println(itu.All(seq, func(v int) bool { return v%2 == 0 }))
}
Output:

true

func All2 added in v0.7.1

func All2[K, V any](seq iter.Seq2[K, V], pred func(K, V) bool) bool

All2 tests if every pair (k, v) of seq matches pred. All2 consumes seq eagerly and stops as soon as pred returns false. If seq is empty, All2 returns true.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	// slices.All turns a slice into an iterator that yields index/value pairs.
	seq := slices.All([]string{"a", "bb", "ccc"})
	fmt.Println(itu.All2(seq, func(_ int, v string) bool { return len(v) >= 2 }))
}
Output:

false

func Any added in v0.7.1

func Any[T any](seq iter.Seq[T], pred func(T) bool) bool

Any tests if at least one element of seq matches pred. Any consumes seq eagerly and stops as soon as pred returns true. If seq is empty, Any returns false.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	seq := slices.Values([]int{1, 2, 3})
	fmt.Println(itu.Any(seq, func(v int) bool { return v%2 == 0 }))
}
Output:

true

func Any2 added in v0.7.1

func Any2[K, V any](seq iter.Seq2[K, V], pred func(K, V) bool) bool

Any2 tests if at least one pair (k, v) of seq matches pred. Any2 consumes seq eagerly and stops as soon as pred returns true. If seq is empty, Any2 returns false.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	// slices.All turns a slice into an iterator that yields index/value pairs.
	seq := slices.All([]string{"a", "bb", "ccc"})
	fmt.Println(itu.Any2(seq, func(_ int, v string) bool { return len(v) >= 4 }))
}
Output:

false

func Chain added in v0.3.1

func Chain[T any](seqs ...iter.Seq[T]) iter.Seq[T]

Chain returns a lazy iterator that yields elements from each seq in seqs, in order. Values are produced only as the returned iterator is consumed.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	first := slices.Values([]int{1, 2})
	second := slices.Values([]int{})
	third := slices.Values([]int{3, 4, 5})

	chained := itu.Chain(first, second, third)
	for v := range chained {
		fmt.Println(v)
	}
}
Output:

1
2
3
4
5

func Chain2 added in v0.3.1

func Chain2[K, V any](seqs ...iter.Seq2[K, V]) iter.Seq2[K, V]

Chain2 returns a lazy iterator that yields pairs from each seq in seqs, in order. Values are produced only as the returned iterator is consumed.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	// slices.All returns an iter.Seq2 over index/value pairs.
	first := slices.All([]string{"a", "b"})
	second := slices.All([]string{})
	third := slices.All([]string{"c"})

	chained := itu.Chain2(first, second, third)
	for i, v := range chained {
		fmt.Printf("%d:%s\n", i, v)
	}
}
Output:

0:a
1:b
0:c

func Compare added in v0.7.1

func Compare[E cmp.Ordered](seq1 iter.Seq[E], seq2 iter.Seq[E]) int

Compare compares the elements of seq1 and seq2, using cmp.Compare on each pair of elements. The elements are compared sequentially, starting at the first yielded value, until one element is not equal to the other.

The result of comparing the first non-matching elements is returned. If both sequences are equal until one of them ends, the shorter sequence is considered less than the longer one.

Compare consumes the input sequences eagerly as needed; it stops as soon as a mismatch is found or either sequence ends. If both sequences yield an identical infinite stream of values, Compare does not return.

The result is 0 if seq1 == seq2, -1 if seq1 < seq2, and +1 if seq1 > seq2.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	fmt.Println(itu.Compare(slices.Values([]int{1, 2, 3}), slices.Values([]int{1, 2, 3})))
	fmt.Println(itu.Compare(slices.Values([]int{1, 2}), slices.Values([]int{1, 2, 0})))
	fmt.Println(itu.Compare(slices.Values([]int{1, 2, 5}), slices.Values([]int{1, 2, 4})))
}
Output:

0
-1
1

func CompareFunc added in v0.7.1

func CompareFunc[E1, E2 any](seq1 iter.Seq[E1], seq2 iter.Seq[E2], cmpFn func(E1, E2) int) int

CompareFunc compares the elements of seq1 and seq2, using cmpFn to compare each pair of elements. The elements are compared sequentially, starting at the first yielded value, until cmpFn reports a mismatch.

The result is -1 if the first non-matching elements compare less than, +1 if they compare greater than, and 0 if all compared elements match and both sequences end at the same time. If both sequences are equal until one of them ends, the shorter sequence is considered less than the longer one.

CompareFunc consumes the input sequences eagerly as needed; it stops as soon as a mismatch is found or either sequence ends. If both sequences yield an identical infinite stream of values, CompareFunc does not return.

Example
package main

import (
	"cmp"
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	cmpLen := func(a int, b string) int { return cmp.Compare(a, len(b)) }

	fmt.Println(itu.CompareFunc(
		slices.Values([]int{1, 2, 3}),
		slices.Values([]string{"a", "bb", "ccc"}),
		cmpLen,
	))
	fmt.Println(itu.CompareFunc(
		slices.Values([]int{1, 2, 4}),
		slices.Values([]string{"a", "bb", "ccc"}),
		cmpLen,
	))
}
Output:

0
1

func CompareFunc2 added in v0.7.1

func CompareFunc2[K1, V1, K2, V2 any](seq1 iter.Seq2[K1, V1], seq2 iter.Seq2[K2, V2], cmpFn func(K1, V1, K2, V2) int) int

CompareFunc2 compares the pairs of seq1 and seq2, using cmpFn to compare each pair (k1, v1) and (k2, v2). The pairs are compared sequentially, starting at the first yielded pair, until cmpFn reports a mismatch.

The result is -1 if the first non-matching pairs compare less than, +1 if they compare greater than, and 0 if all compared pairs match and both sequences end at the same time. If both sequences are equal until one of them ends, the shorter sequence is considered less than the longer one.

CompareFunc2 consumes the input sequences eagerly as needed; it stops as soon as a mismatch is found or either sequence ends. If both sequences yield an identical infinite stream of pairs, CompareFunc2 does not return.

Example
package main

import (
	"cmp"
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	cmpPair := func(k1 int, v1 string, k2 int64, v2 int) int {
		if c := cmp.Compare(int64(k1), k2); c != 0 {
			return c
		}
		return cmp.Compare(len(v1), v2)
	}

	fmt.Println(itu.CompareFunc2(
		itu.Zip(slices.Values([]int{1, 2, 3}), slices.Values([]string{"a", "bb", "ccc"})),
		itu.Zip(slices.Values([]int64{1, 2, 3}), slices.Values([]int{1, 2, 3})),
		cmpPair,
	))
	fmt.Println(itu.CompareFunc2(
		itu.Zip(slices.Values([]int{1, 2, 4}), slices.Values([]string{"a", "bb", "ccc"})),
		itu.Zip(slices.Values([]int64{1, 2, 3}), slices.Values([]int{1, 2, 3})),
		cmpPair,
	))
}
Output:

0
1

func Count added in v0.6.1

func Count[T any](seq iter.Seq[T]) int

Count consumes seq eagerly and returns the number of yielded elements. If the result overflows, Count panics.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	seq := slices.Values([]int{10, 20, 30, 40})
	fmt.Println(itu.Count(seq))
}
Output:

4

func Count2 added in v0.6.1

func Count2[K, V any](seq iter.Seq2[K, V]) int

Count2 consumes seq eagerly and returns the number of yielded pairs. If the result overflows, Count2 panics.

Example
package main

import (
	"fmt"
	"maps"

	"github.com/lymar/itu"
)

func main() {
	// maps.All returns an iter.Seq2 over key/value pairs.
	// Count the number of "facts" in the map.
	m := map[string]int{
		"Go":     2009,
		"Rust":   2010,
		"Elixir": 2011,
	}
	seq := maps.All(m)
	fmt.Println(itu.Count2(seq))
}
Output:

3

func Cycle added in v0.7.1

func Cycle[T any](seq iter.Seq[T]) iter.Seq[T]

Cycle returns a lazy iterator that repeats seq in a cycle.

Cycle consumes seq eagerly to build an internal slice copy of all values, then returns an iterator that yields those values repeatedly until the consumer stops.

If seq yields no values, Cycle returns an empty iterator.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	input := slices.Values([]int{1, 2, 3})
	cycle := itu.Cycle(input)
	first8 := itu.Take(cycle, 8)

	// Cycle is effectively infinite for non-empty inputs, so we take N values.
	for v := range first8 {
		fmt.Println(v)
	}

}
Output:

1
2
3
1
2
3
1
2

func Cycle2 added in v0.7.1

func Cycle2[A, B any](seq iter.Seq2[A, B]) iter.Seq2[A, B]

Cycle2 returns a lazy iterator that repeats pairs (a, b) from seq in a cycle.

Cycle2 consumes seq eagerly to build an internal slice copy of all pairs, then returns an iterator that yields those pairs repeatedly until the consumer stops.

If seq yields no pairs, Cycle2 returns an empty iterator.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	// slices.All returns an iter.Seq2 over index/value pairs.
	input := slices.All([]string{"a", "b"})
	cycle := itu.Cycle2(input)
	first5 := itu.Take2(cycle, 5)

	// Cycle2 is effectively infinite for non-empty inputs, so we take N pairs.
	for i, v := range first5 {
		fmt.Printf("%d %s\n", i, v)
	}

}
Output:

0 a
1 b
0 a
1 b
0 a

func Empty added in v0.7.1

func Empty[T any]() iter.Seq[T]

Empty returns an iterator that yields no values.

func Empty2 added in v0.7.1

func Empty2[A, B any]() iter.Seq2[A, B]

Empty2 returns an iterator that yields no pairs (a, b).

func Enumerate added in v0.7.1

func Enumerate[T any](seq iter.Seq[T]) iter.Seq2[int, T]

Enumerate returns a lazy iterator that yields pairs (i, x) for each element x in seq.

The index i starts at 0 and increments by 1 for each yielded element. Values are produced only as the returned iterator is consumed.

If advancing the index would overflow int, iteration stops (it does not wrap around).

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	seq := slices.Values([]string{"zero", "one", "two"})

	for i, s := range itu.Enumerate(seq) {
		fmt.Printf("%d:%s\n", i, s)
	}
}
Output:

0:zero
1:one
2:two

func Equal added in v0.7.1

func Equal[T comparable](seq1, seq2 iter.Seq[T]) bool

Equal reports whether seq1 and seq2 yield the same values in the same order.

The sequences are compared sequentially, starting at the first yielded value. Equal consumes the input sequences eagerly as needed; it stops as soon as a mismatch is found or either sequence ends. If both sequences yield an identical infinite stream of values, Equal does not return.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	fmt.Println(itu.Equal(slices.Values([]int{1, 2, 3}), slices.Values([]int{1, 2, 3})))
	fmt.Println(itu.Equal(slices.Values([]int{1, 2}), slices.Values([]int{1, 2, 0})))
	fmt.Println(itu.Equal(slices.Values([]int{1, 2, 3}), slices.Values([]int{1, 2, 4})))
}
Output:

true
false
false

func Equal2 added in v0.7.1

func Equal2[K, V comparable](seq1 iter.Seq2[K, V], seq2 iter.Seq2[K, V]) bool

Equal2 reports whether seq1 and seq2 yield the same pairs (k, v) in the same order.

The sequences are compared sequentially, starting at the first yielded pair. Equal2 consumes the input sequences eagerly as needed; it stops as soon as a mismatch is found or either sequence ends. If both sequences yield an identical infinite stream of pairs, Equal2 does not return.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	seq1 := itu.Zip(slices.Values([]int{1, 2, 3}), slices.Values([]string{"a", "bb", "ccc"}))
	seq2 := itu.Zip(slices.Values([]int{1, 2, 3}), slices.Values([]string{"a", "bb", "ccc"}))
	fmt.Println(itu.Equal2(seq1, seq2))

	seq3 := itu.Zip(slices.Values([]int{1, 2, 3}), slices.Values([]string{"a", "bb", "ccc"}))
	seq4 := itu.Zip(slices.Values([]int{1, 2, 4}), slices.Values([]string{"a", "bb", "ccc"}))
	fmt.Println(itu.Equal2(seq3, seq4))
}
Output:

true
false

func EqualFunc added in v0.7.1

func EqualFunc[E1, E2 any](seq1 iter.Seq[E1], seq2 iter.Seq[E2], eqFn func(E1, E2) bool) bool

EqualFunc reports whether seq1 and seq2 yield matching values in the same order, as determined by eqFn.

The sequences are compared sequentially, starting at the first yielded value. EqualFunc consumes the input sequences eagerly as needed; it stops as soon as eqFn reports a mismatch or either sequence ends. If both sequences yield an identical infinite stream of values and eqFn reports a match for every pair, EqualFunc does not return.

EqualFunc panics if eqFn is nil.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	eqLen := func(a int, b string) bool { return a == len(b) }

	fmt.Println(itu.EqualFunc(
		slices.Values([]int{1, 2, 3}),
		slices.Values([]string{"a", "bb", "ccc"}),
		eqLen,
	))
	fmt.Println(itu.EqualFunc(
		slices.Values([]int{1, 2, 4}),
		slices.Values([]string{"a", "bb", "ccc"}),
		eqLen,
	))
}
Output:

true
false

func EqualFunc2 added in v0.7.1

func EqualFunc2[K1, V1, K2, V2 any](seq1 iter.Seq2[K1, V1], seq2 iter.Seq2[K2, V2], eqFn func(K1, V1, K2, V2) bool) bool

EqualFunc2 reports whether seq1 and seq2 yield matching pairs in the same order, as determined by eqFn.

The sequences are compared sequentially, starting at the first yielded pair. EqualFunc2 consumes the input sequences eagerly as needed; it stops as soon as eqFn reports a mismatch or either sequence ends. If both sequences yield an identical infinite stream of pairs and eqFn reports a match for every pair, EqualFunc2 does not return.

EqualFunc2 panics if eqFn is nil.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	eqPair := func(k1 int, v1 string, k2 int64, v2 int) bool {
		return int64(k1) == k2 && len(v1) == v2
	}

	seq1 := itu.Zip(slices.Values([]int{1, 2, 3}), slices.Values([]string{"a", "bb", "ccc"}))
	seq2 := itu.Zip(slices.Values([]int64{1, 2, 3}), slices.Values([]int{1, 2, 3}))
	fmt.Println(itu.EqualFunc2(seq1, seq2, eqPair))

	seq3 := itu.Zip(slices.Values([]int{1, 2, 3}), slices.Values([]string{"a", "bb", "cccc"}))
	seq4 := itu.Zip(slices.Values([]int64{1, 2, 3}), slices.Values([]int{1, 2, 3}))
	fmt.Println(itu.EqualFunc2(seq3, seq4, eqPair))
}
Output:

true
false

func Filter

func Filter[T any](seq iter.Seq[T], pred func(T) bool) iter.Seq[T]

Filter returns a lazy iterator over the elements of seq for which pred returns true. Elements are tested only as the returned iterator is consumed.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	input := slices.Values([]int{0, 1, 2, 3, 4, 5})
	evenNumbers := itu.Filter(input, func(v int) bool {
		return v%2 == 0
	})
	for v := range evenNumbers {
		fmt.Println(v)
	}
}
Output:

0
2
4

func Filter2 added in v0.5.1

func Filter2[K, V any](seq iter.Seq2[K, V], pred func(K, V) bool) iter.Seq2[K, V]

Filter2 returns a lazy iterator over the pairs of seq for which pred returns true. Pairs are tested only as the returned iterator is consumed.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	// slices.All returns an iter.Seq2 over index/value pairs.
	input := slices.All([]string{"aa", "bb", "ccc", "d"})
	filtered := itu.Filter2(input, func(i int, s string) bool {
		return i%2 == 0 && len(s) >= 2
	})
	for i, s := range filtered {
		fmt.Printf("%d:%s\n", i, s)
	}
}
Output:

0:aa
2:ccc

func Find added in v0.7.1

func Find[T any](seq iter.Seq[T], pred func(T) bool) (value T, ok bool)

Find searches for an element of seq that satisfies pred. Find consumes seq eagerly and stops as soon as pred returns true. If no element matches, Find returns the zero value of T and ok=false.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	v, ok := itu.Find(slices.Values([]int{1, 2, 3}), func(v int) bool { return v%2 == 0 })
	fmt.Println(v, ok)
}
Output:

2 true

func Find2 added in v0.7.1

func Find2[K, V any](seq iter.Seq2[K, V], pred func(K, V) bool) (k K, v V, ok bool)

Find2 searches for a pair (k, v) of seq that satisfies pred. Find2 consumes seq eagerly and stops as soon as pred returns true. If no pair matches, Find2 returns zero values of K and V and ok=false.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	// slices.All turns a slice into an iterator that yields index/value pairs.
	k, v, ok := itu.Find2(slices.All([]string{"a", "bb", "ccc"}), func(_ int, v string) bool {
		return len(v) >= 2
	})
	fmt.Println(k, v, ok)
}
Output:

1 bb true

func Flatten added in v0.7.1

func Flatten[T any](seq iter.Seq[iter.Seq[T]]) iter.Seq[T]

Flatten returns a lazy iterator that yields elements from each inner sequence in seq, in order.

Values are produced only as the returned iterator is consumed.

Example
package main

import (
	"fmt"
	"iter"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	inners := []iter.Seq[int]{
		slices.Values([]int{1, 2}),
		slices.Values([]int{}),
		slices.Values([]int{3}),
	}
	outer := slices.Values(inners)

	flat := itu.Flatten[int](outer)
	for v := range flat {
		fmt.Println(v)
	}
}
Output:

1
2
3

func FlattenTo2 added in v0.7.1

func FlattenTo2[K, V any](seq iter.Seq[iter.Seq2[K, V]]) iter.Seq2[K, V]

FlattenTo2 returns a lazy iterator that yields pairs (k, v) from each inner sequence in seq, in order.

Values are produced only as the returned iterator is consumed.

Example
package main

import (
	"fmt"
	"iter"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	// slices.All returns an iter.Seq2 over index/value pairs.
	inners := []iter.Seq2[int, string]{
		slices.All([]string{"a", "bb"}),
		slices.All([]string{"x"}),
	}
	outer := slices.Values(inners)

	flat := itu.FlattenTo2[int, string](outer)
	for i, s := range flat {
		fmt.Printf("%d:%s\n", i, s)
	}
}
Output:

0:a
1:bb
0:x

func Fold added in v0.7.1

func Fold[T, R any](seq iter.Seq[T], acc R, fn func(R, T) R) R

Fold folds seq from left to right, starting with acc. For each element x in seq it updates the accumulator as: acc = fn(acc, x). Fold consumes seq eagerly and returns the final accumulator. If seq is empty, Fold returns acc.

Note: if R is a reference type (map, slice, pointer, etc.), fn may mutate the accumulator value.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	input := slices.Values([]int{1, 2, 3, 4, 5})
	sum := itu.Fold(input, "", func(acc string, v int) string {
		return acc + fmt.Sprintf("%d ", v)
	})
	fmt.Println(sum)
}
Output:

1 2 3 4 5

func Fold2 added in v0.7.1

func Fold2[K, V, R any](seq iter.Seq2[K, V], acc R, fn func(R, K, V) R) R

Fold2 folds seq from left to right, starting with acc. For each pair (k, v) in seq it updates the accumulator as: acc = fn(acc, k, v). Fold2 consumes seq eagerly and returns the final accumulator. If seq is empty, Fold2 returns acc.

Note: if R is a reference type (map, slice, pointer, etc.), fn may mutate the accumulator value.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	// slices.All returns an iter.Seq2 over index/value pairs.
	input := slices.All([]string{"a", "bb", "ccc"})
	result := itu.Fold2(input, "", func(acc string, i int, s string) string {
		return acc + fmt.Sprintf("%d:%s ", i, s)
	})
	fmt.Println(result)
}
Output:

0:a 1:bb 2:ccc

func Intersperse added in v0.7.1

func Intersperse[T any](seq iter.Seq[T], sep T) iter.Seq[T]

Intersperse returns a lazy iterator that yields the elements of seq with sep inserted between each pair of adjacent elements. Values are produced only as the returned iterator is consumed.

Example
package main

import (
	"fmt"
	"slices"
	"strings"

	"github.com/lymar/itu"
)

func main() {
	input := slices.Values([]string{"a", "b", "c"})
	result := itu.Intersperse(input, "-")

	var b strings.Builder
	for s := range result {
		b.WriteString(s)
	}
	fmt.Println(b.String())

}
Output:

a-b-c
Example (Single)
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	input := slices.Values([]int{42})
	result := itu.Intersperse(input, 0)
	for v := range result {
		fmt.Println(v)
	}

}
Output:

42

func Last added in v0.8.1

func Last[T any](seq iter.Seq[T]) (value T, ok bool)

Last returns the last element produced by seq.

Last consumes seq eagerly. If seq is empty, Last returns the zero value of T and ok=false.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	v, ok := itu.Last(slices.Values([]int{10, 20, 30}))
	fmt.Println(v, ok)
}
Output:

30 true

func Last2 added in v0.8.1

func Last2[K, V any](seq iter.Seq2[K, V]) (k K, v V, ok bool)

Last2 returns the last pair (k, v) produced by seq.

Last2 consumes seq eagerly. If seq is empty, Last2 returns the zero values of K and V and ok=false.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	// slices.All turns a slice into an iterator that yields index/value pairs.
	k, v, ok := itu.Last2(slices.All([]string{"a", "bb", "ccc"}))
	fmt.Println(k, v, ok)
}
Output:

2 ccc true

func Map

func Map[T, R any](seq iter.Seq[T], fn func(T) R) iter.Seq[R]

Map returns a lazy iterator that yields fn(x) for each element x in seq. Values are produced only as the returned iterator is consumed.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	input := slices.Values([]int{0, 1, 2})
	result := itu.Map(input, func(v int) string {
		return fmt.Sprintf("[ %d ]", v+1)
	})
	for v := range result {
		fmt.Println(v)
	}
}
Output:

[ 1 ]
[ 2 ]
[ 3 ]

func Map2 added in v0.4.1

func Map2[K, V, RK, RV any](seq iter.Seq2[K, V], fn func(K, V) (RK, RV)) iter.Seq2[RK, RV]

Map2 returns a lazy iterator that yields pairs (rk, rv) produced by fn(k, v) for each input pair (k, v) from seq.

Values are produced only as the returned iterator is consumed.

Example
package main

import (
	"fmt"
	"slices"
	"strings"

	"github.com/lymar/itu"
)

func main() {
	// slices.All returns an iter.Seq2 over index/value pairs.
	input := slices.All([]string{"a", "bb"})
	result := itu.Map2(input, func(i int, s string) (string, int) {
		return strings.ToUpper(s), i
	})
	for s, i := range result {
		fmt.Printf("%s:%d\n", s, i)
	}
}
Output:

A:0
BB:1

func Map2To added in v0.4.1

func Map2To[TK, TV, R any](seq iter.Seq2[TK, TV], fn func(TK, TV) R) iter.Seq[R]

Map2To returns a lazy iterator that yields fn(k, v) for each input pair (k, v) from seq.

Values are produced only as the returned iterator is consumed.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	// slices.All returns an iter.Seq2 over index/value pairs.
	input := slices.All([]string{"x", "y"})
	result := itu.Map2To(input, func(i int, s string) string {
		return fmt.Sprintf("%d=%s", i, s)
	})
	for v := range result {
		fmt.Println(v)
	}
}
Output:

0=x
1=y

func MapTo2 added in v0.4.1

func MapTo2[T, RK, RV any](seq iter.Seq[T], fn func(T) (RK, RV)) iter.Seq2[RK, RV]

MapTo2 returns a lazy iterator that yields pairs (rk, rv) produced by fn(x) for each element x in seq.

Values are produced only as the returned iterator is consumed.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	input := slices.Values([]int{2, 3})
	result := itu.MapTo2(input, func(v int) (string, int) {
		return fmt.Sprintf("key=%d", v), v * v
	})
	for label, sq := range result {
		fmt.Printf("%s:%d\n", label, sq)
	}
}
Output:

key=2:4
key=3:9

func Nth added in v0.8.1

func Nth[T any](seq iter.Seq[T], n int) (value T, ok bool)

Nth returns the element of seq at the zero-based index n.

Nth consumes seq eagerly and stops as soon as the n-th element is reached. If n is negative, or seq ends before producing the n-th element, Nth returns the zero value of T and ok=false.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	v, ok := itu.Nth(slices.Values([]int{10, 20, 30}), 1)
	fmt.Println(v, ok)
}
Output:

20 true

func Nth2 added in v0.8.1

func Nth2[K, V any](seq iter.Seq2[K, V], n int) (k K, v V, ok bool)

Nth2 returns the pair (k, v) of seq at the zero-based index n.

Nth2 consumes seq eagerly and stops as soon as the n-th pair is reached. If n is negative, or seq ends before producing the n-th pair, Nth2 returns the zero values of K and V and ok=false.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	// slices.All turns a slice into an iterator that yields index/value pairs.
	k, v, ok := itu.Nth2(slices.All([]string{"a", "bb", "ccc"}), 2)
	fmt.Println(k, v, ok)
}
Output:

2 ccc true

func Of added in v0.7.1

func Of[T any](items ...T) iter.Seq[T]

Of returns an iterator that yields each value from items in order.

If items is empty, Of yields no values.

Example
package main

import (
	"fmt"

	"github.com/lymar/itu"
)

func main() {
	seq := itu.Of("a", "b", "c")
	for v := range seq {
		fmt.Println(v)
	}
}
Output:

a
b
c

func Range added in v0.2.1

func Range[T Integer](start, end T) iter.Seq[T]

Range returns a lazy iterator that counts from start towards end with step 1.

It yields the half-open range [start, end): start, start+1, ..., end-1.

If start >= end, Range yields no values. If advancing the current value would overflow the underlying integer type, the sequence stops (it does not wrap around).

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	fmt.Println(slices.Collect(itu.Range(3, 7)))
}
Output:

[3 4 5 6]

func RangeBy added in v0.2.1

func RangeBy[T Integer](start, end, step T) iter.Seq[T]

RangeBy returns a lazy iterator that counts from start towards end using step.

For step > 0 it yields the half-open range [start, end) by repeatedly adding step while the current value is < end. For step < 0 it yields the half-open range (end, start] by repeatedly adding step while the current value is > end.

If step == 0, RangeBy treats it as non-negative: it yields no values when start >= end, and otherwise produces an infinite sequence of start values until the consumer stops.

If advancing the current value overflows the underlying integer type, the sequence stops (it does not wrap around).

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	fmt.Println(slices.Collect(itu.RangeBy(0, 5, 2)))
}
Output:

[0 2 4]

func RangeFrom added in v0.2.1

func RangeFrom[T Integer](start T) iter.Seq[T]

RangeFrom returns a lazy iterator that starts at start and keeps advancing by 1 until the next step would overflow the underlying integer type (it does not wrap around).

Values are produced only as the returned iterator is consumed.

Example
package main

import (
	"fmt"

	"github.com/lymar/itu"
)

func main() {
	seq := itu.RangeFrom(-2)

	out := make([]int, 0, 5)
	for v := range seq {
		out = append(out, v)
		if len(out) == cap(out) {
			break
		}
	}

	fmt.Println(out)
}
Output:

[-2 -1 0 1 2]

func RangeFromBy added in v0.2.1

func RangeFromBy[T Integer](start, step T) iter.Seq[T]

RangeFromBy returns a lazy iterator that starts at start and keeps advancing by step until the next step would overflow the underlying integer type (it does not wrap around).

If step == 0, RangeFromBy produces an infinite sequence of start values until the consumer stops.

Example
package main

import (
	"fmt"

	"github.com/lymar/itu"
)

func main() {
	seq := itu.RangeFromBy(10, 3)

	out := make([]int, 0, 5)
	for v := range seq {
		out = append(out, v)
		if len(out) == cap(out) {
			break
		}
	}

	fmt.Println(out)
}
Output:

[10 13 16 19 22]

func RangeInclusive added in v0.2.1

func RangeInclusive[T Integer](start, end T) iter.Seq[T]

RangeInclusive returns a lazy iterator that counts from start towards end with step 1, including end.

It yields the closed range [start, end]: start, start+1, ..., end.

If start > end, RangeInclusive yields no values. If advancing the current value would overflow the underlying integer type, the sequence stops (it does not wrap around).

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	fmt.Println(slices.Collect(itu.RangeInclusive(3, 7)))
}
Output:

[3 4 5 6 7]

func RangeInclusiveBy added in v0.2.1

func RangeInclusiveBy[T Integer](start, end, step T) iter.Seq[T]

RangeInclusiveBy returns a lazy iterator that counts from start towards end using step, including end.

For step > 0 it yields the closed range [start, end] by repeatedly adding step while the current value is <= end. For step < 0 it yields the closed range [end, start] in descending order by repeatedly adding step while the current value is >= end.

If step == 0, RangeInclusiveBy treats it as non-negative: it yields no values when start > end, and otherwise produces an infinite sequence of start values until the consumer stops.

If advancing the current value overflows the underlying integer type, the sequence stops (it does not wrap around).

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	fmt.Println(slices.Collect(itu.RangeInclusiveBy(0, 4, 2)))
}
Output:

[0 2 4]

func Reduce

func Reduce[T any](seq iter.Seq[T], fn func(T, T) T) (result T, ok bool)

Reduce reduces seq from left to right. It uses the first element of seq as the initial accumulator, then for each subsequent element x updates the accumulator as: acc = fn(acc, x). Reduce consumes seq eagerly.

If seq is empty, Reduce returns the zero value of T and ok=false.

Note: if T is a reference type (map, slice, pointer, etc.), fn may mutate the accumulator value.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	input := slices.Values([]int{1, 2, 3, 4})
	sum, ok := itu.Reduce(input, func(a, b int) int { return a + b })
	fmt.Println(sum, ok)
}
Output:

10 true
Example (Empty)
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	sum, ok := itu.Reduce(slices.Values([]int(nil)), func(a, b int) int { return a + b })
	fmt.Println(sum, ok)
}
Output:

0 false

func ReduceOr added in v0.7.1

func ReduceOr[T any](seq iter.Seq[T], def T, fn func(T, T) T) T

ReduceOr reduces seq from left to right, like Reduce.

If seq is empty, ReduceOr returns def.

Note: if T is a reference type (map, slice, pointer, etc.), fn may mutate the accumulator value.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	empty := slices.Values([]int(nil))
	sum := itu.ReduceOr(empty, 123, func(a, b int) int { return a + b })
	fmt.Println(sum)
}
Output:

123

func Skip added in v0.7.1

func Skip[T any](seq iter.Seq[T], n int) iter.Seq[T]

Skip returns a lazy iterator that skips the first n elements of seq.

It consumes and discards up to n values from seq, then yields the remaining values (if any). Values are produced only as the returned iterator is consumed.

Skip panics if n is negative.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	seq := slices.Values([]int{10, 11, 12, 13, 14, 15})
	fmt.Println(slices.Collect(itu.Skip(seq, 2)))
}
Output:

[12 13 14 15]

func Skip2 added in v0.7.1

func Skip2[K, V any](seq iter.Seq2[K, V], n int) iter.Seq2[K, V]

Skip2 returns a lazy iterator that skips the first n pairs from seq.

It consumes and discards up to n pairs from seq, then yields the remaining pairs (if any). Pairs are produced only as the returned iterator is consumed.

Skip2 panics if n is negative.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	// slices.All returns an iter.Seq2 over index/value pairs.
	all := slices.All([]string{"a", "b", "c", "d"})
	seq := itu.Skip2(all, 2)
	for i, v := range seq {
		fmt.Printf("%d:%s\n", i, v)
	}
}
Output:

2:c
3:d

func SkipWhile added in v0.7.1

func SkipWhile[T any](seq iter.Seq[T], pred func(T) bool) iter.Seq[T]

SkipWhile returns a lazy iterator that skips elements from seq while pred returns true.

It consumes and discards consecutive values from seq while pred returns true. As soon as pred returns false for a value, that value and all remaining values are yielded.

Values are tested and produced only as the returned iterator is consumed.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	seq := slices.Values([]int{10, 11, 12, 13, 14, 15})
	out := slices.Collect(itu.SkipWhile(seq, func(v int) bool { return v < 12 }))
	fmt.Println(out)
}
Output:

[12 13 14 15]

func SkipWhile2 added in v0.7.1

func SkipWhile2[K, V any](seq iter.Seq2[K, V], pred func(K, V) bool) iter.Seq2[K, V]

SkipWhile2 returns a lazy iterator that skips pairs (k, v) from seq while pred returns true.

It consumes and discards consecutive pairs from seq while pred returns true. As soon as pred returns false for a pair, that pair and all remaining pairs are yielded.

Pairs are tested and produced only as the returned iterator is consumed.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	// slices.All returns an iter.Seq2 over index/value pairs.
	all := slices.All([]string{"a", "b", "stop", "c"})
	seq := itu.SkipWhile2(all, func(_ int, v string) bool { return v != "stop" })
	for i, v := range seq {
		fmt.Printf("%d:%s\n", i, v)
	}
}
Output:

2:stop
3:c

func StepBy added in v0.7.1

func StepBy[T any](seq iter.Seq[T], n int) iter.Seq[T]

StepBy returns a lazy iterator that yields the first element from seq, then yields every n-th element after that.

For example, for seq producing [0 1 2 3 4 5] and n=2, StepBy yields [0 2 4]. Values are produced only as the returned iterator is consumed.

StepBy panics if n is not positive.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	seq := slices.Values([]int{0, 1, 2, 3, 4, 5})
	fmt.Println(slices.Collect(itu.StepBy(seq, 2)))
}
Output:

[0 2 4]

func StepBy2 added in v0.7.1

func StepBy2[K, V any](seq iter.Seq2[K, V], n int) iter.Seq2[K, V]

StepBy2 returns a lazy iterator that yields the first pair (k, v) from seq, then yields every n-th pair after that.

Pairs are produced only as the returned iterator is consumed.

StepBy2 panics if n is not positive.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	// slices.All returns an iter.Seq2 over index/value pairs.
	all := slices.All([]string{"a", "b", "c", "d", "e", "f"})
	seq := itu.StepBy2(all, 2)
	for i, v := range seq {
		fmt.Printf("%d:%s\n", i, v)
	}
}
Output:

0:a
2:c
4:e

func Take added in v0.7.1

func Take[T any](seq iter.Seq[T], n int) iter.Seq[T]

Take returns a lazy iterator that yields at most n elements from seq.

It yields the first n values from seq, or fewer if seq runs out of values. Values are produced only as the returned iterator is consumed.

Take panics if n is negative.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	seq := slices.Values([]int{10, 11, 12, 13, 14, 15})
	fmt.Println(slices.Collect(itu.Take(seq, 3)))
}
Output:

[10 11 12]

func Take2 added in v0.7.1

func Take2[K, V any](seq iter.Seq2[K, V], n int) iter.Seq2[K, V]

Take2 returns a lazy iterator that yields at most n pairs from seq.

It yields the first n pairs from seq, or fewer if seq runs out of values. Pairs are produced only as the returned iterator is consumed.

Take2 panics if n is negative.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	// slices.All returns an iter.Seq2 over index/value pairs.
	all := slices.All([]string{"a", "b", "c"})
	seq := itu.Take2(all, 2)
	for i, v := range seq {
		fmt.Printf("%d:%s\n", i, v)
	}
}
Output:

0:a
1:b

func TakeWhile added in v0.7.1

func TakeWhile[T any](seq iter.Seq[T], pred func(T) bool) iter.Seq[T]

TakeWhile returns a lazy iterator that yields elements from seq while pred returns true.

It yields consecutive values from seq until pred returns false for a value. As soon as pred returns false, iteration stops and that value is not yielded.

Values are tested and produced only as the returned iterator is consumed.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	seq := slices.Values([]int{10, 11, 12, 13, 14, 15})
	out := slices.Collect(itu.TakeWhile(seq, func(v int) bool { return v <= 12 }))
	fmt.Println(out)
}
Output:

[10 11 12]

func TakeWhile2 added in v0.7.1

func TakeWhile2[K, V any](seq iter.Seq2[K, V], pred func(K, V) bool) iter.Seq2[K, V]

TakeWhile2 returns a lazy iterator that yields pairs (k, v) from seq while pred returns true.

It yields consecutive pairs from seq until pred returns false for a pair. As soon as pred returns false, iteration stops and that pair is not yielded.

Pairs are tested and produced only as the returned iterator is consumed.

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	// slices.All returns an iter.Seq2 over index/value pairs.
	all := slices.All([]string{"a", "b", "stop", "c"})
	seq := itu.TakeWhile2(all, func(_ int, v string) bool { return v != "stop" })
	for i, v := range seq {
		fmt.Printf("%d:%s\n", i, v)
	}
}
Output:

0:a
1:b

func Zip added in v0.6.1

func Zip[T1, T2 any](seq1 iter.Seq[T1], seq2 iter.Seq[T2]) iter.Seq2[T1, T2]

Zip returns a lazy iterator that yields pairs (a, b) from seq1 and seq2. The result has the length of the shorter input sequence: iteration stops as soon as either sequence runs out of values (the longer one is truncated).

Example
package main

import (
	"fmt"
	"slices"

	"github.com/lymar/itu"
)

func main() {
	longer := slices.Values([]int{1, 2, 3, 4})
	shorter := slices.Values([]string{"a", "b"})

	for n, s := range itu.Zip(longer, shorter) {
		fmt.Printf("%d:%s\n", n, s)
	}
}
Output:

1:a
2:b

Types

type Integer added in v0.2.1

type Integer interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 |
		~uint32 | ~uint64 | ~uintptr
}

Jump to

Keyboard shortcuts

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