官方 Ruby FAQ
如果您希望报告错误或对本 FAQ 提出改进建议,请访问我们的 GitHub 仓库,并提出 issue 或 pull request。
迭代器
什么是迭代器?
迭代器是一种接受块(block)或 Proc 对象的方法。在源文件中,块紧跟在方法调用之后。迭代器用于生成用户定义的控制结构——特别是循环。
让我们看一个例子,了解它是如何工作的。迭代器经常用于对集合中的每个元素重复相同的操作,如下所示:
data = [1, 2, 3]
data.each do |i|
puts i
end
产生
1
2
3
数组 data 的 each 方法接收 do ... end 块,并反复执行它。每次调用时,块都会接收数组的连续元素。
您可以使用 { ... } 来定义块,代替 do ... end。
data = [1, 2, 3]
data.each { |i|
puts i
}
产生
1
2
3
此代码与上一个示例的含义相同。然而,在某些情况下,优先级问题会导致 do ... end 和 { ... } 的作用不同。
foobar a, b do ... end # foobar is the iterator.
foobar a, b { ... } # b is the iterator.
这是因为 { ... } 比 do ... end 块与前面的表达式结合得更紧密。第一个示例等同于 foobar(a, b) do ... end,而第二个示例是 foobar(a, b { ... })。
如何将块传递给迭代器?
您只需将块放在迭代器调用之后。您也可以通过在指向 Proc 的变量或常量名称前添加 & 来传递 Proc 对象。
块在迭代器中是如何使用的?
本节或部分内容可能已过时或需要确认。
有三种方法可以从迭代器方法执行块:(1)使用 yield 控制结构;(2)调用 Proc 参数(由块创建)并使用 call;(3)使用 Proc.new 后跟一个调用。
yield 语句调用块,可选地将一个或多个参数传递给它。
def my_iterator
yield 1, 2
end
my_iterator {|a, b| puts a, b }
产生
1
2
如果方法定义有一个块参数(最后一个形式参数前面有一个 ampersand ( & )),它将接收附加的块,并将其转换为 Proc 对象。这可以使用 prc.call(args) 调用。
def my_iterator(&b)
b.call(1, 2)
end
my_iterator {|a, b| puts a, b }
产生
1
2
在迭代器定义中使用 Proc.new(或等效的 proc 或 lambda 调用)时,它会将传递给方法的块作为其参数,并从中生成一个过程对象。(proc 和 lambda 基本上是同义词。)
[需要更新:lambda 的行为略有不同,并且会产生警告 tried to create Proc object without a block(尝试在没有块的情况下创建 Proc 对象)。]
def my_iterator
Proc.new.call(3, 4)
proc.call(5, 6)
lambda.call(7, 8)
end
my_iterator {|a, b| puts a, b }
产生
3
4
5
6
7
8
也许令人惊讶的是,Proc.new 及其同类方法在任何意义上都不会消耗附加到该方法的块——每次调用 Proc.new 都会使用同一个块生成一个新的过程对象。
您可以通过调用 block_given? 来判断方法是否关联了块。
不带块的 Proc.new 有什么作用?
不带块的 Proc.new 无法生成过程对象,并会发生错误。然而,在方法定义中,不带块的 Proc.new 暗示在调用方法时存在一个块,因此不会发生错误。
如何并行运行迭代器?
这里采纳了 Matz 在 [ruby-talk:5252] 中提出的一个使用线程的解决方案。
require "thread"
def combine(*iterators)
queues = []
threads = []
iterators.each do |it|
queue = SizedQueue.new(1)
th = Thread.new(it, queue) do |i, q|
send(i) {|x| q << x }
end
queues << queue
threads << th
end
loop do
ary = []
queues.each {|q| ary << q.pop }
yield ary
iterators.size.times do |i|
return if !threads[i].status && queues[i].empty?
end
end
end
def it1
yield 1; yield 2; yield 3
end
def it2
yield 4; yield 5; yield 6
end
combine(:it1, :it2) do |x|
# x is [1, 4], then [2, 5], then [3, 6]
end