Generic Iterator with Lazy Evaluation

In contrast to eager evaluation where evaluation is performed immediately, an Iterator is lazily evaluated, i.e. not evaluated until it’s necessary, e.g. when you drain an Iterator.

Import

import "github.com/burningxflame/gx/ds/iter"

From Other Types to Iterators

Create Iterators from Builtin Types

// Create an Iterator from a slice
it := iter.FromSlice(l)

// Create an Iterator from a map
it := iter.FromMap(m)

// Create an Iterator from a channel
it := iter.FromChan(c)

Create Iterators from Iters
Iter is an interface representing a lazy iteration.

// Represent a lazy iteration
type Iter[E any] interface {
    // Advance the iteration and return an Option wrapping the next value if exist.
    Next() Option[E]
}

// Represent an optional value
type Option[E any] struct {
    Val E    // the value if exist
    Ok  bool // true if exist, false otherwise
}
// Create an Iterator consisting of all the elements of the first Iter, followed by all the elements of the second Iter, and so on.
it := iter.From(i1, i2, ...)

Create Iterators from Rangers
Ranger is an interface representing a normal iteration (not lazy).

// Represent a normal iteration (not lazy)
type Ranger[E any] interface {
    // Iterate the collection and call fn for each element.
    ForEach(fn func(E))
}
// Create an Iterator from a Ranger
it := iter.FromRanger(r)

Samples
from_to ↗

Iterator Transformations

A transformation method transforms an Iterator in place, and returns the Iterator itself. This approach reduces memory footprint, especially for long pipelines of Iterators. And it enables method chaining as well, for easily building pipelines of Iterators.

// Returns an Iterator consisting of those elements of the Iterator for which fn(e) returns true.
it2 := it.Filter(pred)

// Return an Iterator consisting of the results of applying fn to every element of the Iterator.
it2 := it.Map(fn)

Go doesn’t allow to define type parameters in methods ↗ . So, to transform an Iterator of type E into an Iterator of another type F, use the function (not method) Map instead.

func (it *Iterator[E]) Map(fn MapFn[E, E]) *Iterator[E]
vs
func Map[E, F any](it *Iterator[E], fn MapFn[E, F]) *Iterator[F]

// Map to another type
it2 := iter.Map(it, fn)
// Return an Iterator consisting of the first n elements of the Iterator, or all elements if there are fewer than n.
it2 := it.Take(n)

// Returns an Iterator consisting of all but the first n elements of the Iterator.
it2 := it.Drop(n)

// Return an Iterator consisting of those elements of the Iterator as long as fn(e) returns true. Once fn(e) returns false, the rest of the elements are ignored.
it2 := it.TakeWhile(pred)

// Return an Iterator consisting of those elements of the Iterator starting from the first element for which fn(e) returns false.
it2 := it.DropWhile(pred)

// Return an Iterator consisting of all the elements of the first Iterator, followed by all the elements of the second Iterator, and so on.
it := iter.Chain(it1, it2, ...)

Samples
transform ↗

Pipelines of Iterators

It’s easy to use method chaining to build pipelines of Iterators.

it2 := it.Drop(n).Filter(pred).Map(fn).Take(m)...

Pipelines of Iterators are lazily evaluated, i.e. not evaluated until it’s necessary, e.g. when you drain pipelines. This approach reduces memory footprint, especially for long pipelines of Iterators.

Samples
pipeline ↗

Drain Iterators

Draining an Iterator means completing iteration and producing a result or a side effect. This is the time when an Iterator is really evaluated. After draining, an Iterator is considered exhausted and it’s pointless to use that Iterator again.

// Call fn for each element of the Iterator.
it.ForEach(fn)

// Return the min element of the Iterator.
o := it.Min()

// Return the max element of the Iterator.
o := it.Max()

// Return a sorted slice of all element of the Iterator.
l := it.Sort(less)

// Return the result of applying fn to ini and the first element of the Iterator,
// then applying fn to that result and the second element, and so on.
// If the Iterator is empty, return ini and fn is not called.
acc := it.Reduce(ini, fn)

Go doesn’t allow to define type parameters in methods ↗ . So, to reduce an Iterator of type E into a result of another type F, use the function (not method) Reduce instead.

func (it *Iterator[E]) Reduce(ini E, fn func(acc E, e E) E) E
vs
func Reduce[A any, E any](it *Iterator[E], ini A, fn func(acc A, e E) A) A

// Reduce to another type
acc := iter.Reduce(it, ini, fn)
// Call fn sequentially for each element of the Iterator. If fn returns false, stop iteration.
it.Range(fn)

// Return true if fn(e) is true for any element of the Iterator.
// If the Iterator is empty, return false.
ok := it.Any(pred)

// Return true if fn(e) is true for every element of the Iterator.
// If the Iterator is empty, return true.
ok := it.Every(pred)

Samples
drain ↗

From Iterators to Other Types

Convert Iterators to Builtin Types

// Return a slice of all elements of the Iterator.
l := it.ToSlice()

// Return a map of all elements of the Iterator.
m := iter.ToMap(it)

// Return a channel of all elements of the Iterator.
ch := it.ToChan()

Convert Iterators to Collectors
Collector is an interface representing a collector which collects all elements of an Iterator.

// Represent a collector which collects all elements of an Iterator.
type Collector[E any] interface {
    // Add the element into the Collector
    Add(E)
}
// Feed the Collector c with all elements of the Iterator.
it.To(c)

Samples
from_to ↗