Enumerator - External Iterators
Ruby comes with a built-in Enumerator class, which implements external iterators in Ruby.
Create an Enumerator object
对 Collection (Array ,Hash) 调用 to_enum
方法,从而生成 Enumerator object
ruby
a = [1,3,"car"] b = { dog: "canine", fox: "vulpine" } # Create Enumerators enum_a = a.to_enum emum_h = h.to_enum enum_a.next # => 1 emum_h.next # => [:dog, "canine"] enum_a.next # => 3 enum_a.next # => [:fox, "vulpine"]
Most of the internal iterator methods will also return an Enumerator object if called without a block
ruby
a = [1,3,"cat"] enum_a = a.each enum_a.next # => 1 enum_a.next # => 3
Loop with Enumerator
When an enumerator object runs out of values inside a loop, the loop will terminate cleanly.
ruby
short_enum = [1,2,3].to_enum long_enum = ('a'..'z').to_enum loop do puts "#{short_enum.next} - #{long_enum.next}" end produces: 1 - a 2 - b 3 - c
Enumerators Are Objects
Enumerators take something that's normally executable code and turn it into an object.
each_with_index
This invokes its hose class's each method, returning successive values with an index
ruby
['a', 'b', 'c'].each_with_index { |item, index| result << [item, index] } result # => [["a",0], ["b",1], ["c",2]]
each_char
The each_char method of strings will return an enumerator is you don't give it a block
result = []
"cat".each_char.each_with_index {|item, index| result << [item, index]}
result # => [["c", 0],["a",1],["t",2]]
each_with_index
也可以简写为 with_index
如果对象是 Enumerator
Create the Enumerator object explicitly
We can call to_a on an enumerator to iterate over it
ruby
enum = "car".enum_for(:each_char) enum.to_a # => ["c","a","t"]
如果方法支持参数,也可以传进去
ruby
enum_in_threes = (1..10).enum_for(:each_slice, 3) enum_in_threes.to_a # => [[1,2,3],[4,5,6],[7,8,9],[10]]
Enumerators Are Generators and Filters
Create an explicit enumerator and passing it a block.
ruby
triangular_numbers = Enumerator.new do |yielder| number = 0 count = 1 loop do number += count count += 1 yielder.yield number end end 5.times { print triangular_numbers.next, " " } puts # => 1 3 6 10 15
Enumerator objects are also enumerable
ruby
triangular_numbers = Enumerator.new do |yielder| number = 0 count = 1 loop do number += count count += 1 yielder.yield number end end p triangular_numbers.first(5) # => [1, 3, 6, 10, 15]
&block notation
ruby
triangular_numbers = Enumerator.new do |yielder| number = 0 count = 1 loop do number += count count += 1 yielder.yield number end end def infinite_select(enum, &block) Enumerator.new do |yielder| enum.each do |value| yielder.yield(value) if block.call(value) end end end p infinite_select(triangular_numbers) { |val| val 5 10 == 10 }.first(5)
Lazy Enumerators in Ruby 2
None of the lazy versions of the methods actually consume any data from the collection until that data is requested.
ruby
def Integer.all Enumerator.new do |yielder, n: 0| loop { yielder.yield(n += 1) } end.lazy end p Integer.all.first(10) # => [1,2,3,4,5,6,7,8,9,10] p Integer.all.select {|i| (i % 3).zero?}.first(10) # => [3,6,9,12,15,18,21,24,27,30]