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 coroutine13.2with 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

Casiano Rodriguez León 2015-06-18