Monday, March 12, 2012

Enumerable#lazy and its benefits

Enumerable#lazy has been introduced into Ruby trunk.  With Enumerable#lazy, the evaluation of Enumerable methods is delayed until needed, so Enumerable methods can be applied on infinite sequences.  The following program shows first ten pythagorean triples:
def pythagorean_triples
  (1..Float::INFINITY).lazy.flat_map {|z|
    (1..z).flat_map {|x|
      (x..z).select {|y|
        x**2 + y**2 == z**2
      }.map {|y|
        [x, y, z]
      }
    }
  }
end
p pythagorean_triples.first(10)
One of the benefits of Enumerable#lazy is that you can separate the way to calculate each value and the way to determine when the iteration should be terminated. Thus the following program can show all pythagorean triples less than 100, without modification of the method pythagorean_triples:
p pythagorean_triples.take_while { |x, y, z| z < 100 }.force
Another example is an implementation of the UNIX wc command:
(ARGV.length == 0 ?
 [["", STDIN]] :
 ARGV.lazy.map { |filename|
  [filename, File.open(filename)]
}).map { |filename, file|
  "%4d  %4d  %4d %s\n" % [*file.lines.lazy.map { |line|
    [1, line.split.length, line.length]
  }.inject([0, 0, 0]) { |(lc, wc, cc), (l, w, c)|
    [wc + w, lc + l, cc + c]
  }, filename]
}.each(&:display)
Without Enumerable#lazy, you need more memory, and you cannot see the result until all files are processed.

No comments: