NAME

Sidef::Object::Enumerator - Lazy iterator object for generating and processing custom sequences.

DESCRIPTION

This class implements a general-purpose enumerator: an object built around a single "generator" block that produces values by calling back into a callback it's given, however it likes (a loop, recursion, an infinite loop { ... }, and so on). This makes it easy to build custom lazy sequences -- including infinite ones -- without writing a dedicated iterator class.

An Enumerator is created with a block that receives one argument, conventionally named yield, f, or cb: a callback the block should call, once per produced value, for as long as it wants to keep producing values.

Two important behavioral points to keep in mind:

  • Early exit. "first", "nth", and "while" can stop consuming the generator block as soon as they have what they need -- internally, by jumping straight out of the (possibly still-running, possibly infinite) generator block the moment enough values have been produced. This is what makes it safe to call, say, .first(10) on a generator that loops forever. Every other method ("each", "to_a", "map", "grep", "count", "length") has no such escape hatch and will run the generator block to completion -- looping forever if it never stops on its own.

  • Multi-value yields. The callback can technically be invoked with more than one value at a time (e.g. yield(a, b)). "map" and "grep" handle this correctly, applying the block to each yielded value individually. "first", "nth", "count", and "length", however, treat each callback invocation as a single unit: they don't count or split up multiple values yielded together in one call. In practice, nearly every generator yields one value at a time, so this rarely matters.

SYNOPSIS

# Create an enumerator that yields prime numbers
var primes = Enumerator({|yield|
    for n in (2..Inf) {
        yield(n) if n.is_prime
    }
})

# Get the first 10 primes
say(primes.first(10))    #=> [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

# Create a Fibonacci sequence enumerator
var fib = Enumerator({|yield|
    var (a, b) = (0, 1)
    loop {
        yield(a)
        (a, b) = (b, a + b)
    }
})

say(fib.first(10))       #=> [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

INHERITS

Inherits methods from Sidef::Object::Object.

CONSTRUCTION

new

Enumerator(block)
Enumerator.new(block)

Creates a new enumerator from block. block receives a single callback argument (commonly named yield, f, or cb) that it should call once per value it wants the enumerator to produce.

var squares = Enumerator({|yield|
    for n in (1..Inf) {
        yield(n * n)
    }
})

Aliases: call

EARLY-EXIT CONSUMPTION

These methods can stop consuming the generator block as soon as they have enough values, so they're safe to use even on an enumerator that produces values forever.

first

self.first(n)
self.first(n, block)

Returns an Array of the first n values produced by the enumerator. If block is given, only values for which block returns a truthy value are counted and collected. If n is less than or equal to 1, only the very first (matching) value found is collected before stopping.

var e = Enumerator({|f| (1..Inf).each {|i| f(i) } })

say(e.first(5))                   #=> [1, 2, 3, 4, 5]
say(e.first(5, {|n| n.is_prime })) #=> [2, 3, 5, 7, 11]

nth

self.nth(n)
self.nth(n, block)

Returns the n-th (1-indexed) value produced by the enumerator, or nil if the enumerator stops before reaching it. If block is given, only values for which block returns a truthy value are counted, so this returns the n-th matching value. If n is less than or equal to 1, this returns the first (matching) value found.

var primes = Enumerator({|f| (2..Inf).each {|i| f(i) if i.is_prime } })

say(primes.nth(10))    #=> 29   # the 10th prime
say(primes.nth(100))   #=> 541  # the 100th prime

while

self.while(block)

Collects values produced by the enumerator into an Array for as long as block returns a truthy value for each one, and stops -- without including the value that failed -- as soon as block returns a falsy value.

var palindromes = Enumerator({|f|
    var n = 1
    loop {
        f(n)
        n = n.next_palindrome!
    }
})

# Get all palindromes less than 100
say(palindromes.while {|n| n < 100 })
#=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 11, 22, 33, 44, 55, 66, 77, 88, 99]

FULL CONSUMPTION

These methods always run the generator block to completion, with no early exit -- they will never return if used on a genuinely infinite enumerator.

each

self.each(block)

Calls block once for every value the enumerator produces (block is the callback the generator block invokes directly), and returns self.

var e = Enumerator({|f| (1..5).each {|i| f(i) } })
e.each {|n| say(n) }    # prints 1, 2, 3, 4, 5

to_a

self.to_a

Collects every value the enumerator produces into an Array.

var e = Enumerator({|f| (1..5).each {|i| f(i) } })
say(e.to_a)    #=> [1, 2, 3, 4, 5]

map

self.map(block)

Applies block to every value the enumerator produces and returns a new Array of the results.

var e = Enumerator({|f| (1..5).each {|i| f(i) } })
say(e.map {|n| n * 2 })    #=> [2, 4, 6, 8, 10]

Aliases: collect

grep

self.grep(block)

Returns a new Array containing only the values produced by the enumerator for which block returns a truthy value.

var e = Enumerator({|f| (1..20).each {|i| f(i) } })
say(e.grep {|n| n.is_even })    #=> [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

Aliases: select

count

self.count(block)

Returns the number of values produced by the enumerator for which block returns a truthy value.

var e = Enumerator({|f| (1..100).each {|i| f(i) } })
say(e.count {|n| n.is_prime })    #=> 25

Aliases: count_by

length

self.length

Returns the total number of values the enumerator produces.

var e = Enumerator({|f| (1..100).each {|i| f(i) if i.is_prime } })
say(e.length)    #=> 25

Aliases: len, size

EXAMPLES

Ludic Numbers

func ludics_upto(nmax = 100000) {
    Enumerator({|collect|
        collect(1)
        var arr = @(2..nmax)
        while (arr) {
            collect(var n = arr[0])
            arr.range.by(n).each {|i| arr[i] = nil }
            arr.compact!
        }
    })
}

say(ludics_upto(1000).first(25))

Infinite Prime Generator

var primes = Enumerator({|yield|
    for n in (2..Inf) {
        yield(n) if n.is_prime
    }
})

say(primes.first(10))              # first 10 primes
say(primes.nth(100))               # the 100th prime
say(primes.first(20, {|n| n > 50 })) # first 20 primes greater than 50

Word Wrapping

func wordwrap(words, maxwidth) {
    Enumerator({|y|
        var cols = 0
        words.slice_before {|w|
            cols += 1 if (cols != 0)
            cols += w.len
            if (maxwidth < cols) {
                cols = w.len
                true
            } else {
                false
            }
        }.each {|ws| y(ws) }
    })
}

var text = "The quick brown fox jumps over the lazy dog"
wordwrap(text.words, 15).each {|line| say(line.join(" ")) }

SEE ALSO

Sidef::Types::Array::Array, Sidef::Types::Range::RangeNumber