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