Ruby Enumerator类

556 查看

Ruby Enumerator是一个可以内部和外部迭代的类。
下面列出Enumerator的实例方法
#each,#each_with_index,#each_with_object,#feed,#inspect,#next,#next_values,#peek,#peek_values,#rewind,#size,#with_index,#with_object
创建方法

  • Kernel#to_enum
  • Kernel#enum_for
  • ::new
    上面方法返回值有两种形式:
  • 如果指定了Block,那么将在Block内部迭代对象。
  • 如果没有指定Block,返回一个新的Enumerator对象。

实例:

enumerator = %w(one two three).each
puts enumerator.class # => Enumerator

enumerator.each_with_object("foo") do |item, obj|
  puts "#{obj}: #{item}"
end
# foo: one
# foo: two
# foo: three

我们可以使用链试操作。实例如下:

puts %w[foo bar baz].map.with_index { |w, i| "#{i}:#{w}" }
# => ["0:foo", "1:bar", "2:baz"]

Enumerator实例也可以用于外部迭代,例如使用 #next方法返回一个值或者触发StopIteration错误。

e = [1,2,3].each   # returns an enumerator object.
puts e.next   # => 1
puts e.next   # => 2
puts e.next   # => 3
puts e.next   # raises StopIteration

我们可以使用它来实现一个内部迭代,如下:

def ext_each(e)
  while true
    begin
      vs = e.next_values
    rescue StopIteration
      return $!.result
    end
    y = yield(*vs)
    e.feed y
  end
end

o = Object.new

def o.each
  puts yield
  puts yield(1)
  puts yield(1, 2)
  3
end

# use o.each as an internal iterator directly.
puts o.each {|*x| puts x; [:b, *x] }
# => [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3

# convert o.each to an external iterator for
# implementing an internal iterator.
puts ext_each(o.to_enum) {|*x| puts x; [:b, *x] }
# => [], [:b], [1], [:b, 1], [1, 2], [:b, 1, 2], 3

类公开方法:
new(size=nil){|yielder| ..} OR new(obj, method=:each, *args)
创建一个新的Enumerator对象实例,可以使用Enumerable的所有方法

第一种形式

创建Enumerator,迭代定义在Block里面,Block的参数yielder对象,可以使用yield方法来yield一个值。其实就是指定Enumberator迭代的对象是哪一个值。别名方法为<<

我们来看一个例子:

fib = Enumerator.new do |y|
  a = b = 1
  loop do
    y << a
    a, b = b, a + b
  end
end

p fib.take(10) # => [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

注意:对于下面yielder这个对象,我自己的理解就是,该对象有一个实例方法,来指定迭代的内容。

triangular_number = Enumerator.new do |yielder|
      number = 0
      count = 1

  loop do
    number += count
    count += 1
    yielder.yield number
  end
end

require 'pp'

5.times do
  pp triangular_number.next
end

返回结果如下:
1
3
6
10
15

Enumerator对象实例可以完整的使用maxin Enumerable的所有方法,例如:to_a,first,take...

pp triangular_number.first 5
[1, 3, 6, 10, 15]

下面的例子:

fib = Enumerator.new do |yielder|
  a = 1
  b = 1
  loop do
    yielder.yield a
    a , b = b , a + b
  end
end

require 'pp'

#5.times { pp fib.next  }

#pp fib.take(10)

#我们通过Enumerator创建一个无限枚举对象,当然我们也可以
#传递一个Block生成一个新的Enumerator

def infinite_sleect(enum, &block)
  Enumerator.new do |yielder|
    enum.each do |value|
      yielder.yield value if block.call(value)
    end
  end
end

pp infinite_sleect(fib) { |value| value % 5 ==0 }.first(10)

下面的例子:

fib = Enumerator.new do |yielder|
  a = 1
  b = 1
  loop do
    yielder.yield a
    a , b = b , a + b
  end
end
require 'pp'
#更有意思的是我们可以在类Enumerator中定义一个方法,来继续迭代自身
class Enumerator
  def infinite_sleect(&block)
    Enumerator.new do |yielder|
      self.each do |value|
        yielder.yield value if block.call(value)
      end
    end
  end
end

pp fib
   .infinite_sleect { |value| value % 2 == 0 }
   .infinite_sleect { |value| value % 4 == 0 }
   .first(10)

第二种形式

e = Enumerator.new(ObjectSpace, :each_object)

通常我们使用 Kernel#enum_for or Kernel#to_enum来代替

ObjectSpace.enum_for(:each_object)

接下来我们来看看对象实例方法:
1.each{...}
在block中定义Enumerator的结构,如果没有,将返回本身
2.each_with_index{|(*args),idx| ...} OR each_with_index
3.with_obejct(obj) {|(*atgs),obj|...} OR with_object(obj)
如果有block,将会将Enumerator的元素和指定的对象传入block,返回对象。
如果没有block,将会创建一个新的Enumerator

to_three = Enumerator.new do |y|
  3.times do |x|
    y << x
  end
end

to_three_with_string = to_three.with_object("foo")
to_three_with_string.each do |x,string|
  puts "#{string}: #{x}"
end

# => foo:0
# => foo:1
# => foo:2

4.fedd obj -> nil
Sets the value to be returned by the next yield inside e.
If the value is not set, the yield returns nil.
This value is cleared after being yielded.

o = Object.new
def o.each
  x = yield         # (2) blocks
  p x               # (5) => "foo"
  x = yield         # (6) blocks
  p x               # (8) => nil
  x = yield         # (9) blocks
  p x               # not reached w/o another e.next
end

e = o.to_enum
e.next              # (1)
e.feed "foo"        # (3)
e.next              # (4)
e.next              # (7)
                    # (10)

5.inspect -> string
创建一个打印版的 Enumerator实例对象

p [1,2,3,4,5].each.inspect
# => "#<Enumerator: [1, 2, 3, 4, 5]:each>"

6.next -> object
返回Enumerator的下一个对象,将迭代位置前移,当到达结束位置时,引发StopIteration异常

a = [1,2,3]
e = a.to_enum
p e.next   #=> 1
p e.next   #=> 2
p e.next   #=> 3
p e.next   #raises StopIteration

7.next_value -> array
同next,但是结果会放入一个数组。

a = [1,2,3]
e = a.to_enum
p e.next_values   #=> 1
p e.next_values   #=> 2
p e.next_values   #=> 3
p e.next_values   #raises StopIteration

再来一个例子:

o = Object.new
def o.each
  yield
  yield 1
  yield 1, 2
  yield nil
  yield [1, 2]
end
e = o.to_enum
p e.next_values
p e.next_values
p e.next_values
p e.next_values
p e.next_values
e = o.to_enum
p e.next
p e.next
p e.next
p e.next
p e.next

## yield args       next_values      next
#  yield            []               nil
#  yield 1          [1]              1
#  yield 1, 2       [1, 2]           [1, 2]
#  yield nil        [nil]            nil
#  yield [1, 2]     [[1, 2]]         [1, 2]

8.peek -> object
返回Enumerator的下一个对象,但是不会移动迭代的位置。(peek就是窥视; 一瞥,看一眼,挺形象的,我就偷偷看一看而已)

a = [1,2,3]
e = a.to_enum
p e.next   #=> 1
p e.peek   #=> 2
p e.peek   #=> 2
p e.peek   #=> 2
p e.next   #=> 2
p e.next   #=> 3
p e.next   #raises StopIteration

9.peek_values -> array

o = Object.new
def o.each
  yield
  yield 1
  yield 1, 2
end
e = o.to_enum
p e.peek_values    #=> []
e.next
p e.peek_values    #=> [1]
p e.peek_values    #=> [1]
e.next
p e.peek_values    #=> [1, 2]
e.next
p e.peek_values    # raises StopIteration

10.size -> int, Float::INFINITY or nil

(1..100).to_a.permutation(4).size # => 94109400
loop.size # => Float::INFINITY