La clase Enumerator permite la iteración tanto interna como externa.
next
element explicitly from the iterator.
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(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"}
yields
a value to your code
next
value, execution resumes at the statement following the yields
[~/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]
count
and select
will happily try to read the whole enumeration
before returning a result
select
that
works with infinite sequences, you’ll need to write it yourself
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
[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 nilPara 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 << ...
.
[~/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]
Casiano Rodriguez León 2015-01-07