考虑以下代码:
[1,2,3].map.with_index { |x, i| x * i }
# => [0,2,6]
Run Code Online (Sandbox Code Playgroud)
这到底是如何运作的?
我的思维模型map是它迭代并在每个元素上应用一个函数。是否with_index以某种方式将函数传递给 enumerator [1,2,3].map,在这种情况下该函数会是什么?
这个SO 线程显示了枚举器如何传递数据,但没有回答问题。事实上,如果你替换map为each那么行为是不同的:
[1,2,3].each.with_index { |x, i| x * i }
# => [1,2,3]
Run Code Online (Sandbox Code Playgroud)
map除了携带要迭代的数据之外,似乎还携带必须应用函数的信息。这是如何运作的?
Todd 的回答非常好,但我觉得看到更多 Ruby 代码可能会有所帮助。具体来说,让我们尝试自己写 。eachmapArray
我不会直接使用任何Enumerable或Enumerator方法,所以我们看看它是如何在幕后工作的(我仍然会使用for循环,并且那些技术上#each在幕后调用,但这只是一点作弊)
首先,有each. each简单。它会迭代数组并对每个元素应用一个函数,然后返回原始数组。
def my_each(arr, &block)
for i in 0..arr.length-1
block[arr[i]]
end
arr
end
Run Code Online (Sandbox Code Playgroud)
够简单的。现在如果我们不通过一个区块怎么办?让我们稍微改变一下以支持这一点。我们实际上想要推迟做事的行为,each以允许Enumerator做它的事情
def my_each(arr, &block)
if block
for i in 0..arr.length-1
block[arr[i]]
end
arr
else
Enumerator.new do |y|
my_each(arr) { |*x| y.yield(*x) }
end
end
end
Run Code Online (Sandbox Code Playgroud)
因此,如果我们不传递一个块,我们会创建一个Enumerator,当使用时,调用my_each,使用枚举器yield 对象作为块。该y对象是一个有趣的东西,但您可以将其视为基本上是您最终将传入的块。因此,在
my_each([1, 2, 3]).with_index { |x, i| x * i }
Run Code Online (Sandbox Code Playgroud)
想象一下y就像{ |x, i| x * i }比特一样。比这更复杂一点,但这就是想法。
顺便说一句,在 Ruby 2.7 及更高版本上,该Enumerator::Yielder对象有自己的#to_proc,因此如果您使用的是最新的 Ruby 版本,您可以这样做
Enumerator.new do |y|
my_each(arr, &y)
end
Run Code Online (Sandbox Code Playgroud)
而不是
Enumerator.new do |y|
my_each(arr) { |*x| y.yield(*x) }
end
Run Code Online (Sandbox Code Playgroud)
现在让我们将这种方法扩展到map. map用块书写很容易。就像each我们积累结果一样。
def my_map(arr, &block)
result = []
for i in 0..arr.length-1
result << block[arr[i]]
end
result
end
Run Code Online (Sandbox Code Playgroud)
够简单的。现在如果我们不通过一个区块怎么办?让我们做与我们所做的完全相同的事情my_each。也就是说,我们将创建一个Enumerator,并在其中Enumerator调用my_map.
def my_map(arr, &block)
if block
result = []
for i in 0..arr.length-1
result << block[arr[i]]
end
result
else
Enumerator.new do |y|
my_map(arr) { |*x| y.yield(*x) }
end
end
end
Run Code Online (Sandbox Code Playgroud)
现在,它Enumerator知道,每当它最终获得一个块时,它都会my_map在最后使用该块。我们可以看到这两个函数在数组上的实际行为类似于map和eachdo
my_each([1, 2, 3]).with_index { |x, i| x * i } # [1, 2, 3]
my_map ([1, 2, 3]).with_index { |x, i| x * i } # [0, 2, 6]
Run Code Online (Sandbox Code Playgroud)
所以你的直觉很准确
map除了携带要迭代的数据之外,似乎还携带必须应用函数的信息。这是如何运作的?
这正是它的作用。map创建一个who 块知道在最后Enumerator调用,而 则执行相同的操作,但使用. 当然,实际上,出于效率和引导的原因,所有这些都是用 C 实现的,但基本思想仍然存在。mapeacheach
| 归档时间: |
|
| 查看次数: |
226 次 |
| 最近记录: |