如果您希望报告错误或对此常见问题解答提出改进建议,请访问我们的 GitHub 仓库 并开启一个 issue 或 pull request。
迭代器
什么是迭代器?
迭代器是一个接受代码块或 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) 使用 call
调用 Proc
参数(由代码块创建);以及 (3) 使用 Proc.new
后跟调用。
yield
语句调用代码块,可以选择传递一个或多个参数。
def my_iterator
yield 1, 2
end
my_iterator {|a, b| puts a, b }
输出
1
2
如果方法定义具有代码块参数(最后一个形参带有 & 前缀),它将接收附加的代码块,并将其转换为 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
。]
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