This post originated from an RSS feed registered with Ruby Buzz
by Andrew Johnson.
Original Post: Ruby: Enumerators and Generators
Feed Title: Simple things ...
Feed URL: http://www.siaris.net/index.cgi/index.rss
Feed Description: On programming, problem solving, and communication.
Included with the Ruby distribution are the generator library and
the enumerator extension — both useful tools when ordinary
iteration doesn’t quite measure up.
The enumerator extension is simple in concept: create a new
Enumerable object given an object and a method of that object to be used as
an iterator. For example, if we add an each_even iterator to the
Array class to iterate over every element with an even numbered index, we
can use enumerator to create enumerable versions of an array
object that use each_even as the iterator:
require 'enumerator'
class Array
def each_even
self.each_with_index do|el,i|
yield el if i % 2 == 0
end
end
end
arr = ['a','b','c','d','e','f','g','h']
enum = Enumerable::Enumerator.new(arr, :each_even)
ev = enum.map {|x| x + x}
p ev #=> ["aa", "cc", "ee", "gg"]
In addition to the constructor above, the following convenience functions
are added to the Object class:
to_enum(:iter, *args)
enum_for(:iter, *args)
The Enumerable module is also extended with five additional
methods:
each_slice(n) # iterates over non-overlapping chunks of size n
enum_slice(n) # new enumerator object using :each_slice(n)
('a'..'m').each_slice(4) {|sl| p sl}
# produces:
["a", "b", "c", "d"]
["e", "f", "g", "h"]
["i", "j", "k", "l"]
["m"]
each_cons(n) # iterates over successive chunks of size n
enum_cons(n) # new enumerator using :each_cons(n)
('a'..'m').each_cons(4) {|sl| p sl}
# produces:
["a", "b", "c", "d"]
["b", "c", "d", "e"]
["c", "d", "e", "f"]
["d", "e", "f", "g"]
["e", "f", "g", "h"]
["f", "g", "h", "i"]
["g", "h", "i", "j"]
["h", "i", "j", "k"]
["i", "j", "k", "l"]
["j", "k", "l", "m"]
enum_with_index # new enumerator using :each_with_index
The generator library generates external iterators from either
blocks or Enumerable objects (in the latter case, the :each
iterator is externalized).
require 'generator'
arr = ('a' .. 'm')
gen = Generator.new(arr)
while gen.next?
p gen.next
end
This makes iterating over multiple objects relatively easy. However, the
generator library also provides the SyncEnumerator class
which makes multiple iteration a breeze:
require 'generator'
a = (4..5)
b = ['a',nil,'c']
c = ['x','y','x']
enum = SyncEnumerator.new(a, b, c)
enum.each {|row| p row}
puts '---'
table = [ [1,2,3], [4,5,6], [7,8,9] ]
cols = SyncEnumerator.new(*table)
cols.each {|col| p col}
# produces:
[4, "a", "x"]
[5, nil, "y"]
[nil, "c", "x"]
---
[1, 4, 7]
[2, 5, 8]
[3, 6, 9]
All of which just goes to show: There’s more than one way to iterate
an enumerable.