Folding and grouping
Reducing a sequence to one value, or to one value per key. FoldBy is the operator that justifies the library: it aggregates per key as elements stream past, so its memory is bounded by the number of distinct keys rather than by the number of elements — where GroupBy retains everything.
func (s Seq[T]) Fold[A any](init A, f func(A, T) A) AFold reduces the sequence into an accumulator, left to right. Reduce is the same operation using the first element as the initial accumulator; FoldBy folds per key in one streaming pass.
fmt.Println(catena.Of(1, 2, 3).Fold(100, func(acc, n int) int { return acc + n }))106FoldIndexed
Section titled “FoldIndexed”func (s Seq[T]) FoldIndexed[A any](init A, f func(int, A, T) A) AFoldIndexed is Fold with the element index.
fmt.Println(catena.Of(10, 20).FoldIndexed(0, func(i, acc, n int) int { return acc + i*n }))20FoldWhile
Section titled “FoldWhile”func (s Seq[T]) FoldWhile[A any](init A, f func(A, T) (A, bool)) AFoldWhile folds until f reports false; the accumulator from the stopping call is included in the result.
// Stops when the callback says so; the accumulator from the stopping// call is included.fmt.Println(catena.Of(1, 2, 3, 4).FoldWhile(0, func(acc, n int) (int, bool) { acc += n return acc, acc < 5}))6FoldErr
Section titled “FoldErr”func (s Seq[T]) FoldErr[A any](init A, f func(A, T) (A, error)) (A, error)FoldErr folds until f fails, returning the accumulator so far and the first error.
// Stops at the first error and returns the accumulator so far.acc, err := catena.Of(1, 2, 3).FoldErr(0, func(acc, n int) (int, error) { if n == 3 { return 0, errors.New("three is too many") } return acc + n, nil})fmt.Println(acc, err)3 three is too manyFoldBy
Section titled “FoldBy”func (s Seq[T]) FoldBy[K comparable, A any]( key func(T) K, init func(K) A, f func(A, T) A,) map[K]AFoldBy folds each element into a per-key accumulator: streaming grouped aggregation with no intermediate per-key slices. init is called once per distinct key. Memory is bounded by the number of distinct keys, not elements.
// Streaming aggregation per key. GroupBy would retain every element;// this retains one accumulator per distinct key.type sale struct { Region string Amount int}sales := catena.Of( sale{"west", 100}, sale{"east", 40}, sale{"west", 20},)totals := sales.FoldBy( func(s sale) string { return s.Region }, func(string) int { return 0 }, func(sum int, s sale) int { return sum + s.Amount },)fmt.Println(totals["west"], totals["east"])120 40Reduce
Section titled “Reduce”func (s Seq[T]) Reduce(f func(T, T) T) (T, bool)Reduce folds the sequence using its first element as the initial accumulator; (zero, false) on empty input.
// Uses the first element as the seed, so it reports false on empty// rather than inventing a zero.v, ok := catena.Of(3, 1, 2).Reduce(func(a, b int) int { return a * b })_, empty := catena.Empty[int]().Reduce(func(a, b int) int { return a })fmt.Println(v, ok, empty)6 true falseGroupBy
Section titled “GroupBy”func (s Seq[T]) GroupBy[K comparable](sel func(T) K) map[K][]TGroupBy collects elements into per-key buckets, each in encounter order.
// Retains every element. For an aggregate, FoldBy is bounded by keys.byParity := catena.Range(1, 6, 1).GroupBy(func(n int) string { if n%2 == 0 { return "even" } return "odd"})fmt.Println(byParity["odd"], byParity["even"])[1 3 5] [2 4]IndexBy
Section titled “IndexBy”func (s Seq[T]) IndexBy[K comparable](sel func(T) K) map[K]TIndexBy maps each key to its element; on duplicate keys the last element wins.
// A lookup table; on a duplicate key the last element wins.m := catena.Of("apple", "avocado", "blueberry"). IndexBy(func(s string) byte { return s[0] })fmt.Println(string(m['a']), string(m['b']))avocado blueberryTallyBy
Section titled “TallyBy”func (s Seq[T]) TallyBy[K comparable](sel func(T) K) map[K]intTallyBy counts elements per key.
counts := catena.Of("apple", "avocado", "blueberry"). TallyBy(func(s string) byte { return s[0] })fmt.Println(counts['a'], counts['b'])2 1Associate
Section titled “Associate”func (s Seq[T]) Associate[K comparable, V any](f func(T) (K, V)) map[K]VAssociate builds a map from f’s key/value pairs; on duplicate keys the last pair wins.
m := catena.Of(1, 2).Associate(func(n int) (int, string) { return n, strings.Repeat("*", n)})fmt.Println(m[1], m[2])* **Partition
Section titled “Partition”func (s Seq[T]) Partition(pred func(T) bool) (yes, no []T)Partition splits elements by pred, preserving encounter order on both sides; nil slices for empty sides.
// Both sides in one pass, each in encounter order.even, odd := catena.Range(1, 6, 1).Partition(func(n int) bool { return n%2 == 0 })fmt.Println(even, odd)[2 4] [1 3 5]AssociateWith
Section titled “AssociateWith”func AssociateWith[T comparable, V any](s Seq[T], f func(T) V) map[T]VAssociateWith maps each element to f(element); on duplicate elements the last value wins.
m := catena.AssociateWith(catena.Of("go", "rust"), func(s string) int { return len(s) })fmt.Println(m["go"], m["rust"])2 4func Tally[T comparable](s Seq[T]) map[T]intTally counts occurrences per value.
fmt.Println(catena.Tally(catena.Of("a", "b", "a"))["a"])2ToKeySet
Section titled “ToKeySet”func ToKeySet[T comparable](s Seq[T]) map[T]struct{}ToKeySet drains the sequence into a membership map, whose keys are the distinct elements. Named for what it returns rather than for the set it stands in for: ToSet is reserved for a real set type, should Go ever grow one.
set := catena.ToKeySet(catena.Of(1, 2, 2))_, has := set[2]fmt.Println(len(set), has)2 true