A small utility library for defining and consuming enumerators in Go. It provides a consistent interface to iterate over slices, channels, and custom generators.
go get github.com/fgrzl/enumeratorsGuides: docs/ — overview, getting started, operations
This library defines a generic Enumerator[T] interface with concrete implementations for common data sources:
- Slices
- Channels
- Callback-based generators
All enumerators implement the Enumerator[T] interface and extend the Disposable interface:
type Enumerator[T any] interface {
Disposable
MoveNext() bool
Current() (T, error)
Err() error
}
type Disposable interface {
Dispose()
}Important: All enumerators must be disposed when no longer needed to ensure proper resource cleanup. Most functions in this library automatically dispose enumerators for you, but when manually iterating, always call Dispose() or use defer to ensure cleanup.
Iterates over a Go slice.
e := enumerators.Slice([]int{1, 2, 3})
defer e.Dispose() // Ensure cleanup
for e.MoveNext() {
v, _ := e.Current()
fmt.Println(v)
}Iterates over values published to a channel-based enumerator.
e := enumerators.Channel[string](context.Background(), 0)
defer e.Dispose() // Ensure cleanup
// Publish data in a separate goroutine
go func() {
e.Publish("hello")
e.Publish("world")
e.Complete() // Signal completion
}()
for e.MoveNext() {
v, _ := e.Current()
fmt.Println(v)
}If the context passed to Channel is canceled, iteration stops and Err() returns the context error. Publish returns false after completion, disposal, or cancellation.
Many functions in this library automatically dispose enumerators for you:
// ToSlice automatically disposes the enumerator
slice, err := enumerators.ToSlice(enumerators.Slice([]int{1, 2, 3}))
// ForEach automatically disposes the enumerator
err := enumerators.ForEach(enumerators.Slice([]string{"a", "b"}), func(s string) error {
fmt.Println(s)
return nil
})
// Consume automatically disposes the enumerator
err := enumerators.Consume(enumerators.Slice([]int{1, 2, 3}))Enumerators can be chained together for complex data processing:
result, err := enumerators.ToSlice(
enumerators.Map(
enumerators.Filter(
enumerators.Slice([]int{1, 2, 3, 4, 5}),
func(x int) bool { return x%2 == 0 }, // Keep even numbers
),
func(x int) (string, error) { return fmt.Sprintf("num_%d", x), nil }, // Convert to string
),
)
// result: []string{"num_2", "num_4"}Group emits adjacent groups, so each group should be consumed before moving to the next one.
grouped := enumerators.Group(
enumerators.Slice([]string{"ant", "ape", "bat", "bee"}),
func(item string) (byte, error) { return item[0], nil },
)
groups, err := enumerators.CollectGroupingSlices(grouped)
// groups: [{Group: 'a', Items: ["ant", "ape"]}, {Group: 'b', Items: ["bat", "bee"]}]Lazy Operations (recommended for large datasets):
Map,Filter,Take,Skip,FlatMap- Process elements one by one, minimal memory usage
Eager Operations (materialize entire sequence in memory):
Sort,Reverse,Union,Intersect,Except,Sample- Load all elements into memory- Warning: These operations can cause out-of-memory errors for large datasets. Consider processing in batches or using lazy alternatives.
Terminal Operations (consume the enumerator):
ToSlice,ToMap,Count,Sum,Min,Max,First,Last- May load data into memory depending on input size
Operations that expect at least one element (First, Last, Single, Aggregate) return ErrEmptySequence for empty enumerators. Operations that work with empty sequences (Min, Max, Sum) return zero values.
Map[T, U](enumerator, mapper)- Transform each elementFlatMap[T, U](enumerator, mapper)- Transform and flattenFilter[T](enumerator, predicate)- Keep elements matching predicateFilterMap[TIn, TOut](enumerator, apply)- Filter and transform in one stepSort[T](enumerator, less)- Sort elementsReverse[T](enumerator)- Reverse element order
Take[T](enumerator, n)- Take first N elementsTakeWhile[T](enumerator, condition)- Take while condition holdsSkip[T](enumerator, n)- Skip first N elementsSkipIf[T](enumerator, condition)- Skip while condition holdsDistinct[T](enumerator)- Remove duplicatesSample[T](enumerator, n)- Randomly sample N elementsElementAt[T](enumerator, index)- Get element at indexSingle[T](enumerator)- Get single element or error
Sum[T, TSum](enumerator, selector)- Sum elementsCount[T](enumerator)- Count elementsMin[T](enumerator)- Find minimumMax[T](enumerator)- Find maximumFirst[T](enumerator)- Get first elementLast[T](enumerator)- Get last elementAny[T](enumerator, predicate)- Check if any element matchesAll[T](enumerator, predicate)- Check if all elements matchContains[T](enumerator, value)- Check if contains valueAggregate[TSource, TAccumulate](enumerator, seed, accumulator)- Custom accumulation
SequenceEqual[T](first, second)- Compare two sequences for equality
DefaultIfEmpty[T](enumerator, defaultValue)- Provide default value for empty sequences
MapWithContext[T, U](ctx, enumerator, mapper)- Transform with context cancellationFilterWithContext[T](ctx, enumerator, predicate)- Filter with context cancellation
Group[T, G](enumerator, keySelector)- Group by keyChunk[T, TSize](enumerator, sizeSelector)- Chunk by sizeChunkByKey[V, K](in, keySelector)- Chunk by keyChunkWhen[V, K](in, splitFunc)- Chunk when conditionPartition[T](enumerator, predicate)- Partition into two slices
Chain[T](enumerators...)- Concatenate enumeratorsInterleave[T, TOrdered](enumerators, keySelector)- Interleave by keyZip[T, U, V](left, right, zipper)- Zip two enumeratorsUnion[T](enumerators...)- Union of enumeratorsIntersect[T](enumerators...)- Intersection of enumeratorsExcept[T](left, right)- Set difference
ToSlice[T](enumerator)- Convert to sliceToMap[T, K, V](enumerator, keyFn, valFn)- Convert to mapToMapWithKey[K, V](enumerator, keyFn)- Convert to map with identity values
ForEach[T](enumerator, action)- Execute action for each elementConsume[T](enumerator)- Consume without processingPeekable[T](enumerator)- Add peek functionalityCleanup[T](enumerator, cleanupFunc)- Add cleanup function