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