Siaris

Ruby: Enumerators and Generators
17 May 04 - http://www.siaris.net/index.cgi/Programming/LanguageBits/Ruby/EnumGen.rdoc

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.

Discuss

__END__