是否可以安全删除Array
中的元素,同时通过each
进行迭代?第一项测试看起来很有希望:
a = (1..4).to_a
a.each { |i| a.delete(i) if i == 2 }
# => [1, 3, 4]
然而,我找不到关于的事实:
在过去的某些时刻,它似乎是not possible to do:
它无效,因为Ruby在尝试删除某些内容时会退出
.each
循环。
documentation没有说明迭代过程中的删除性。
我不是在寻找reject
或delete_if
。我想用数组的元素做一些事情,有时也从数组中删除一个元素(在我用所述元素完成其他事情之后)。
更新1:我对“安全”的定义不太清楚,我的意思是:
Array
答案 0 :(得分:8)
您不应过多依赖未经授权的答案。你引用的答案是错误的,正如凯文对它的评论所指出的那样。
从Ruby开始是安全的(从Ruby的开头)删除元素而each
,因为Ruby不会因此而引发错误,并且会给出决定性的(即,不是随机的)结果
但是,你需要小心,因为当你删除一个元素时,它后面的元素将被移位,因此下一个应该迭代的元素将被移动到已被迭代的已删除元素的位置已经过了,将被跳过。
答案 1 :(得分:3)
为了回答你的问题,是否安全"要做到这一点,你首先必须定义你的意思" safe"。你的意思是
raise
Exception
?raise
和Exception
?不幸的是,Ruby语言规范并不完全有用:
15.2.12.5.10 Array#each
each
(&安培;块)可见性:公开
<强>行为强>:
- 如果给出了阻止:
- 对于索引顺序中接收者的每个元素,使用元素作为唯一参数调用 block 。
- 返回接收器。
这似乎意味着它在上面的1.,2.,4。和5.意义上确实是完全安全的。
each { |item| block }
→ary
为
self
中的每个元素调用给定的块一次,将该元素作为参数传递。
同样,这似乎意味着与规范相同。
不幸的是,当前现有的Ruby实现的 none 以这种方式解释规范。
在MRI和YARV中发生实际的内容如下:数组的变异,包括元素和/或索引的任何移位立即可见,包括迭代器代码的内部实现它基于数组索引。因此,如果您删除当前正在迭代的位置或之前的元素,您将跳过 next 元素,而如果在之后删除元素迭代,你将跳过那个元素。对于each_with_index
,您还将观察到已删除元素之后的所有元素都将其索引移位(或者反之亦然:索引保持不变,但元素会移位)。
所以,这种行为是'#34;安全&#34;在1,2,2和4的意义上。
其他Ruby实现主要是复制这个(未记录的)行为,但是没有文档记录,你不能依赖它,事实上,我相信至少有一个人做过短暂的实验,提出某种{ {1}}而不是。
答案 2 :(得分:1)
我会说它是安全的,基于以下内容:
2.2.2 :035 > a = (1..4).to_a
=> [1, 2, 3, 4]
2.2.2 :036 > a.each { |i| a.delete(i+1) if i > 1 ; puts i }
1
2
4
=> [1, 2, 4]
我从这个测试推断Ruby正确地识别,同时迭代元素&#34; 3&#34;已删除元素&#34; 2&#34;正在处理,否则元素&#34; 4&#34;也会被删除。
然而,
2.2.2 :040 > a.each { |i| puts i; a.delete(i) if i > 1 ; puts i }
1
1
2
2
4
4
这表明&#34; 2&#34;删除后,处理的下一个元素是数组中现在的第三个元素,因此以前在第三位的元素根本不会被处理。每个似乎重新检查数组以找到每次迭代时要处理的下一个元素。
我认为考虑到这一点,你应该在处理前复制你的情况。
答案 3 :(得分:0)
取决于。
所有.each
都会返回一个枚举器,它为集合保存一个指向它所在位置的指针。例如:
a = [1,2,3]
b = a.each # => #<Enumerator: [1, 2, 3]:each>
b.next # => 1
a.delete(2)
b.next # => 3
a.clear
b.next # => StopIteration: iteration reached an end
每个块都调用next
,直到迭代结束。因此,只要您不修改任何“未来”数组记录,它就应该是安全的。
然而,在ruby的Enumerable
和Array
中有很多有用的方法,你真的不应该这样做。
答案 4 :(得分:0)
You are right, in the past it was advised not to remove items from the collection while iterating over it. In my tests and at least with version 1.9.3 in practice in an array this gives no problem, even when deleting prior or next elements.
It is my opinion that while you can you shouldn't. A more clear and safe approach is to reject the elements and assign to a new array.
b = a.reject{ |i| i == 2 } #[1, 3, 4]
In case you want to reuse your a array that is also possible
a = a.reject{ |i| i == 2 } #[1, 3, 4]
which is in fact the same as
a.reject!{ |i| i == 2 } #[1, 3, 4]
You say you don't want to use reject because you want to do other things with the elements before deleting, but that is also possible.
a.reject!{ |i| puts i if i == 2;i == 2 }
# 2
#[1, 3, 4]