Subsecciones


Enumeradores

Iteradores Externos

La clase Enumerator permite la iteración tanto interna como externa.

Una manera de crear objetos Enumerator es mediante el método to_enum (en la clase Object):

[1] pry(main)> a = [1, 2, "cat" ]
=> [1, 2, "cat"]
[3] pry(main)> ea = a.to_enum
=> #<Enumerator: ...>
[4] pry(main)> ea.next
=> 1
[5] pry(main)> ea.next
=> 2
[6] pry(main)> ea.next
=> "cat"
[7] pry(main)> ea.next
StopIteration: iteration reached an end
from (pry):7:in `next'
[8] pry(main)> ea.rewind
=> #<Enumerator: ...>
[9] pry(main)> ea.next
=> 1
[10] pry(main)> ea.next
=> 2
[11] pry(main)> ea.peek
=> "cat"

También puede usarse con hashes:

28] pry(main)> h = { a: "hello", b: "world" }
=> {:a=>"hello", :b=>"world"}
[29] pry(main)> 
[30] pry(main)> eh = h.to_enum
=> #<Enumerator: ...>
[31] pry(main)> eh.size
=> nil
[32] pry(main)> eh.next
=> [:a, "hello"]
[33] pry(main)> eh.next_values
=> [[:b, "world"]]
[34] pry(main)> eh.rewind
=> #<Enumerator: ...>
El método to_enum está definido en la clase Object.

The call obj.to_enum(method = :each, *args) creates a new Enumerator which will enumerate by calling method :each on obj, passing args if any.

[2] pry(main)> class Tutu
[2] pry(main)*   def chuchu(x)
[2] pry(main)*     if block_given?
[2] pry(main)*       yield x+1
[2] pry(main)*       yield x+2
[2] pry(main)*       yield x+3
[2] pry(main)*     else  
[2] pry(main)*       to_enum(:chuchu, x) { 3 }
[2] pry(main)*     end  
[2] pry(main)*   end  
[2] pry(main)* end  
=> :chuchu
[3] pry(main)> t = Tutu.new
=> #<Tutu:0x007f9e6cb5af60>
[5] pry(main)> t.chuchu(10) { |x| puts x }
11
12
13
=> nil
[6] pry(main)> e = t.chuchu(10) 
=> #<Enumerator: ...>
[7] pry(main)> e.next
=> 11
[8] pry(main)> e.next
=> 12
[9] pry(main)> e.size
=> 3

with_index

El método with_index(offset = 0) {|(*args), idx| ... } (o bien with_index(offset = 0)) iterates the given block for each element with an index, which starts from offset. If no block is given, returns a new Enumerator that includes the index, starting from offset
[40] pry(main)> h = { a: "hello", b: "world" } 
=> {:a=>"hello", :b=>"world"}
[41] pry(main)> eh = h.to_enum
=> #<Enumerator: ...>
[42] pry(main)> eh.with_index.next
=> [[:a, "hello"], 0]
[44] pry(main)> eh.with_index { |x, i| puts "#{x.inspect}, #{i}" }
[:a, "hello"], 0
[:b, "world"], 1
=> {:a=>"hello", :b=>"world"}

Construcción de un Enumerador

  1. As well as creating Enumerators from existing collections, you can create an explicit enumerator, passing it a block
  2. The code in the block will be used when the enumerator object needs to supply a fresh value to your program
  3. However, the block isn’t simply executed from top to bottom
  4. Instead, the block is executed as a coroutine12.1with the rest of your program’s code
  5. Execution starts at the top and pauses when the block yields a value to your code
  6. When the code needs the next value, execution resumes at the statement following the yields
  7. This lets you write enumerators that generate infinite sequences:

[~/ruby/PROGRAMMINGRUBYDAVETHOMAS]$ cat fib_enum.rb 
@fib = Enumerator.new do |y|
  a, b = 0, 1
  loop do
    a, b = b, a + b
    y.yield a
  end
end

Enumerator.new recieves a block that is used to define the iteration. A yielder object y is given to the block as a parameter and can be used to yield a value by calling the yield method (aliased as <<).

[~/ruby/PROGRAMMINGRUBYDAVETHOMAS]$ pry
[1] pry(main)> require './fib_enum'
=> true
[2] pry(main)> @fib.next
=> 1
[3] pry(main)> @fib.next
=> 1
[4] pry(main)> @fib.next
=> 2
[6] pry(main)> @fib.first
=> 1
[7] pry(main)> @fib.take(5)
=> [1, 1, 2, 3, 5]

La clase Enumerator incluye el módulo Enumerable de donde toma, entre otros, los métodos first y take.

[25] pry(main)> Enumerator.ancestors
=> [Enumerator, Enumerable, Object, PP::ObjectMixin, Kernel, BasicObject]

Enumeradores Infinitos

  1. You have to be slightly careful with Enumerators that can generate infinite sequences
  2. Some of the regular Enumerable methods such as count and select will happily try to read the whole enumeration before returning a result
  3. If you want a version of select that works with infinite sequences, you’ll need to write it yourself
  4. Here’s a version that gets passed an Enumerator and a block and returns a new Enumerator containing values from the original for which the block returns true
    [1] pry(main)> require './fib_enum'
    => true
    [2] pry(main)> require './grep8'
    => true
    [3] pry(main)> e = @fib.grep8 { |x| x % 3 == 0}
    => #<Enumerator: ...>
    [4] pry(main)> e.next
    => 3
    [5] pry(main)> e.next
    => 21
    [6] pry(main)> e.next
    => 144
    [7] pry(main)> e.first(5)
    => [3, 21, 144, 987, 6765]
    

[9] pry(main)> .cat grep8.rb
class Enumerator
  def grep8(&block) 
    Enumerator.new do |y|
      self.each do |x|
        y.yield x if block[x]
      end
    end
  end
end

Enumeradores Perezosos

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-01-07