Subsecciones

Enumeradores Perezosos y Enumeradores Infinitos

Otra forma de crear iteradores infinitos en Ruby 2.0 es mediante la clase Enumerator::Lazy:

[16] pry(main)> e = (1..Float::INFINITY).lazy.map { |x| x*x }
=> #<Enumerator::Lazy: ...>
[17] pry(main)> e.next
=> 1
[18] pry(main)> e.next
=> 4
[19] pry(main)> e.next
=> 9
[20] pry(main)> e.next
=> 16
[21] pry(main)> e.take(4).force
=> [1, 4, 9, 16]
[22] pry(main)> e.take(4)
=> #<Enumerator::Lazy: ...>
[23] pry(main)> e.first(4)
=> [1, 4, 9, 16]

En este otro ejemplo creamos un enumerador perezoso filter_map que devuelve los elementos procesados por el bloque cuyo valor no es nil. Su funcionamiento sería así::

[1] pry(main)> require './lazy_enum'
=> true
[2] pry(main)> [1,2,3].map {|x| x*x if x % 2 != 0 }
=> [1, nil, 9]
[3] pry(main)> [1,2,3].filter_map {|x| x*x if x % 2 != 0 }
=> [1, 9]         # se elimina el nil
Para usarlo con rangos infinitos:
[4] pry(main)> (1..Float::INFINITY).lazy.filter_map{|i| i*i if i.even?}.first(5)
=> [4, 16, 36, 64, 100]

El método lazy se encuentra en el módulo Enumerable y retorna un enumerador perezoso Enumerator::Lazy que enumera/construye el valor cuando se necesita.

[~/ruby/PROGRAMMINGRUBYDAVETHOMAS]$ cat lazy_enum.rb 
module Enumerable
  def filter_map(&block) # Para explicar la semantica
    map(&block).compact
  end
end

class Enumerator::Lazy
  def filter_map
    Lazy.new(self) do |yielder, *values|
      result = yield *values
      yielder << result if result # << es un alias de yield
    end
  end
end

The call new(self) { |yielder, *values| ... } creates a new Lazy enumerator using self as the coupled enumerator.

When the enumerator is actually enumerated (e.g. by calling force or next), self will be enumerated and each value passed to the given block.

The block then yield values back using yielder.yield or yielder << ... .

Ejemplo: Números primos

[~/rubytesting]$ cat primes.rb 
def primes
  (1..Float::INFINITY).lazy.select do |n|
    ((2..n**0.5)).all? { |f| n % f > 0 }
  end
end

p primes.first(10)
[~/rubytesting]$ ruby primes.rb 
[1, 2, 3, 5, 7, 11, 13, 17, 19, 23]

Véase

  1. Ruby 2.0 Works Hard So You Can Be Lazy por Pat Shaughnessy. April 3rd 2013

Casiano Rodriguez León 2015-06-18