Is it possible to safely delete elements from an Array
while iterating over it via each
? A first test looks promising:
a = (1..4).to_a
a.each { |i| a.delete(i) if i == 2 }
# => [1, 3, 4]
However, I could not find hard facts on:
At some points in the past, it seems that it was not possible to do:
It's not working because Ruby exits the
.each
loop when attempting to delete something.
The documentation does not state anything about deletability during iteration.
I am not looking for reject
or delete_if
. I want to do things with the elements of an array, and sometimes also remove an element from the array (after I've done other things with said element).
Update 1: I was not very clear on my definition of "safe", what I meant was:
Array
You should not rely on unauthorized answers too much. The answer you cited is wrong, as is pointed out by Kevin's comment to it.
It is safe (from the beginning of Ruby) to delete elements from an Array while each
in the sense that Ruby will not raise an error for doing that, and will give a decisive (i.e., not random) result.
However, you need to be careful because when you delete an element, the elements following it will be shifted, hence the element that was supposed to be iterated next would be moved to the position of the deleted element, which has been iterated over already, and will be skipped.
In order to answer your question, whether it is "safe" to do so, you will first have to define what you mean by "safe". Do you mean
raise
an Exception
?raise
an Exception
?Unfortunately, the Ruby Language Specification is not exactly helpful:
15.2.12.5.10 Array#each
each
(&block)
Visibility: public
Behavior:
- If block is given:
- For each element of the receiver in the indexing order, call block with the element as the only argument.
- Return the receiver.
This seems to imply that it is indeed completely safe in the sense of 1., 2., 4., and 5. above.
The documentation says:
each { |item| block }
→ary
Calls the given block once for each element in
self
, passing that element as a parameter.
Again, this seems to imply the same thing as the spec.
Unfortunately, none of the currently existing Ruby implementations interpret the spec in this way.
What actually happens in MRI and YARV is the following: the mutation to the array, including any shifting of the elements and/or indices becomes visible immediately, including to the internal implementation of the iterator code which is based on array indices. So, if you delete an element at or before the position you are currently iterating, you will skip the next element, whereas if you delete an element after the position you are currently iterating, you will skip that element. For each_with_index
, you will also observe that all elements after the deleted element have their indices shifted (or rather the other way around: the indices stay put, but the elements are shifted).
So, this behavior is "safe" in the sense of 1., 2., and 4.
The other Ruby implementations mostly copy this (undocumented) behavior, but being undocumented, you cannot rely on it, and in fact, I believe at least one did experiment briefly with raising some sort of ConcurrentModificationException
instead.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With