我是ruby的新手,并清楚地看到并在网上发现以下内容失败:
arr = [10, 20, 30, 40]
arr.each.with_index do |elmt, i|
print "#{elmt}, #{i}, "
arr.delete_at(i) if elmt == 20
puts arr.length
end
显然,delete_at正在与迭代器进行交互,但我无法找到迭代器和delete_at如何工作的明确描述,以便这样做。 (顺便说一句,我理解有效的解决方案 - 我不是在寻找一种正确的方法来实现这一点,我正在努力理解语义,以便我知道为什么这不符合预期。) 为了完整性,这是输出
10, 0, 4
20, 1, 3
40, 2, 3
=> [10, 30, 40]
答案 0 :(得分:1)
<强>灯泡!! 强>
似乎很清楚delete_at
会立即调整基础数据结构,左移所有元素到已删除项目的右侧。该数组看起来更像是一个链表而不是一个数组。所以“下一个”项(这里是“30”)现在是arr [1](迭代器已经处理过),当迭代器递增到arr [2]时,它看到“40”。 arr [3]返回nil并且看起来似乎对nil无效。
答案 1 :(得分:1)
看看: https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/include/ruby/intern.h
https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/enumerator.c
https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/array.c
每个都为您提供一个枚举器,with_index与枚举器一起使用。
当你到达元素20时,你将它打印出来,然后你擦除它,此时Ruby有效地向下移动阵列中的所有元素。现在,由数组支持的枚举器选取下一个元素40,因为所有内容都向下移动(30个被复制超过20个,40个被复制超过30个,数组被调整大小)
看看: https://github.com/ruby/ruby/blob/ca6b174078fa15f33655be704d9409fdbc4f9929/array.c#L3023 它通过memmove移动元素的魔力发生了。
答案 2 :(得分:1)
让我们一步一步:
arr = [10, 20, 30, 40]
enum0 = arr.each
#=> #<Enumerator: [10, 20, 30, 40]:each>
enum1 = enum0.with_index
#=> #<Enumerator: #<Enumerator: [10, 20, 30, 40]:each>:with_index>
我们可以通过将enum1
转换为数组来查看enum1.to_a
#=> [[10, 0], [20, 1], [30, 2], [40, 3]]
的内容:
enum1
这告诉我们Enumerator#each(将调用Array#each)将枚举器elmt, i = enum1.next
#=> [10, 0]
puts elmt, i
# 10
# 0
elmt == 20
#=> false
的四个元素传递给块,然后将它们依次分配给块变量。第一个是:
arr.delete_at(i)
因此arr
未执行。
enum1
和arr
#=> [10, 20, 30, 40]
enum1.to_a
#=> [[10, 0], [20, 1], [30, 2], [40, 3]]
都未被更改:
each
enum1
现在将elmt, i = enum1.next
#=> [20, 1]
elmt == 20
#=> true
的下一个元素传递到块中:
arr.delete_at(i)
#=> [10, 20, 30, 40].delete_at(1)
#=> 20
arr
#=> [10, 30, 40]
enum1.to_a
#=> [[10, 0], [30, 1], [40, 2]]
所以我们执行:
arr
啊!因此,枚举器和enum1
都已更改。这是完全合理的,因为在创建枚举器时,会建立对原始接收器的引用,以及对其进行操作的规则。因此,对接收器的更改将影响枚举器。
我们可以使用Enumerator#peek来查看each
将传递到块中的enum1.peek
#=> [40, 2]
的下一个元素:
each
所以你看到each
移动到下一个索引位置,忽略了一个先前元素被移除的事实,导致后面的元素每个向下移动一个位置,导致[30,1]
跳过elmt, i = enum1.next
#=> [40, 2]
elmt == 20
#=> false
arr
#=> [10, 30, 40]
enum1.to_a
#=> [[10, 0], [30, 1], [40, 2]]
。
each
此时arr
到达枚举器的末尾,因此它的工作已经完成。因此它返回原始接收器[10, 30, 40]
,但是已经修改了,所以我们得到:
arr = [10, 20, 20, 40]
更好的例子可能是:
[10, 20, 40]
其中:
{{1}}
将被退回。