[读书笔记] - Ruby 中的 Enumerator

682 查看

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

rubya = [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

rubya = [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.

rubyshort_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

rubyenum = "car".enum_for(:each_char)
enum.to_a # => ["c","a","t"]

如果方法支持参数,也可以传进去

rubyenum_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.

rubytriangular_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

rubytriangular_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

rubytriangular_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.

rubydef 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]