Deleting While Iterating in Ruby

How can I use Array#delete while iterating over the array?

a.delete_if { |x| x >= 3 }

See method documentation here

Update:

You can handle x in the block:

a.delete_if do |element|
if element >= 3
do_something_with(element)
true # Make sure the if statement returns true, so it gets marked for deletion
end
end

Delete element of array while iterating

It's because of the index of the deleted element. I added some output to show you:

arr = [1,2,3]  
arr.each do |x|
puts "1: #{arr.inspect}, x: #{x}"
arr.each do |y|
puts "2: #{arr.inspect}, y: #{y}"
puts "#{arr.delete(y)}"
end
end

Result:

1: [1, 2, 3], x: 1
2: [1, 2, 3], y: 1
1
2: [2, 3], y: 3
3
=> [2]

The first deleted element is 1 (index is 0) in the inner each block. After the deletion 2 has the index 0 and now the each iteration goes to index 1 which is now element 3. 3 will be deleted and that's the end of the iteration. So you get [2].

The same happens without nested each:

arr = [1,2,3]  
arr.each do |x|
puts "1: #{arr.inspect}, x: #{x}"
puts "#{arr.delete(x)}"
end

Result:

1: [1, 2, 3], x: 1
1
1: [2, 3], x: 3
3
=> [2]

I suggest to use reverse_each for such operations to avoid this behavior:

arr = [1,2,3]  
arr.reverse_each do |x|
puts "1: #{arr.inspect}, x: #{x}"
puts "#{arr.delete(x)}"
end

Result:

1: [1, 2, 3], x: 3
3
1: [1, 2], x: 2
2
1: [1], x: 1
1
=> []

Deleting While Iterating in Ruby?

use delete_if

array.delete_if do |v|
if v.should_be_deleted?
true
else
v.update
false
end
end

Ruby deleting array element while iterating

This is [part of] implementation of each

for (i=0; i<RARRAY_LEN(ary); i++) {
rb_yield(RARRAY_AREF(ary, i));
}
return ary;

So it simply moves a "reading head" along the array until it reaches the end.

When you delete "green", elements of the array are shifted to take its place and "blue" is now where green was. But we already read the element at this location. Next element to be read is purple.

That is exactly why you should never mutate the collection you're iterating (unless this effect is what you actually desire).

I can't understand the native code

Here's a little visualization of that "reading head" mental model.

 v
red green blue purple # print red

# next

v
red green blue purple # print green

v
red blue purple # delete green in the same iteration

# next

v
red blue purple # print purple

Delete array elements while looping in ruby

class Dog
attr_reader :array

def initialize
@array = [1, 2, 3]
end

def delete_entry(entry)
@array.delete(entry)
end
end

d = Dog.new

d.array.length.times do
d.delete_entry(d.array[0])
end

p d.array

--output:--
[]

I have read that removing elements in the midst of iteration is
disallowed by design in ruby.

Ridiculous. It just won't work as you may expect. Here's why:

deleting elements from an array while iterating over the array

Is it safe to delete from an Array inside each?

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.

Ruby: deleting object while looping over list with that object

In Ruby you will actually find a glitch if you do what you are saying.

Try this:

objects = [1,2,3,4]
objects.each { |el| objects.delete(el) }
=> [2, 4]

You would expect the result to be an empty array, but is not. We are messing up with the elements of the arr and each gets confused, because the length of the array has changed. The each iterator looks something like this in pseudocode:

count = 0
while length(objects) > count
yield objects[count]
count + 1
end

So, in the example I shown above, the reason why we get [2, 4] can be explained on a step by step analysis on what objects.each { |el| objects.delete(el) } is doing:

  1. We start with 4 (length of objects) > 0.
  2. Number 1 is yielded, and deleted.
  3. count = 1
  4. 3 (length of objects) > 1
  5. Number 3 is yielded and deleted.
  6. count = 2
  7. 2 (length of objects) is not bigger than count
  8. We are done, so we have [2, 4]

There is a better way to do what you are trying, by using delete_if:

new_objects = objects.delete_if {|o| o.withinBounds }

Ruby didn't loop all elements?

The fact that only about half (5/9) of your items have disappeared is a dead giveaway that the problem is deleting while iterating over the collection.

The iteration will be processing indexes 1, 2, 3, 4 and so on. If you delete index 2 while processing it, that will shift all later indexes down by one.

So, when you then move on to index 3 in the next iteration, you will skip the original index 3 because that will have been shifted down to index 2.

In other words, let's start with a simpler example with two consecutive items to remove:

index | 0 | 1 | 2 | 3 |
value | 1 | 7 | 7 | 9 |

You check the first index and the value is 1 so you do nothing. You then check the second index and the value is 7 so you delete it, giving:

index | 0 | 1 | 2 |
value | 1 | 7 | 9 |

You then check the third index and the value is 9 so you do nothing. You've also reached the end so it stops.

So you can see there that you actually skipped the second item you wanted to delete, because you shifted things around while iterating. This isn't a Ruby-specific problem, a great many languages have this same issue.

In general, each complete pair of adjacent items will only have the first of the pair deleted while an item on its own (not followed by another of the same value) will be deleted normally. That's why only 5/9 of your 7s are deleted, one for each of the four pairs and the final standalone one.

The correct way (in Ruby) to delete all items of a single given value is to use the array delete method:

a.delete(7)

You can also use conditional delete for more complex conditions such as deleting everything greater than 7:

a.delete_if {|val| val > 7}

And, if you really want to do it yourself (as an educational exercise), you just have to realise that the problem is because you process the array in a forward manner - when you do that, changes beyond where you're deleting may cause issues.

If you were to find some way to process the array in a backwards manner, this issue would not occur. Luckily, Ruby has such a beast:

a.to_enum.with_index.reverse_each do |item, index|

That line will process the array in such a way that deletions will not affect future operations. Just be aware that deleting while iterating can still be a problem if the data structure you're processing is not a simple indexed array.

I still warrant that delete and delete_if are the correct way to go since they're baked into Ruby already, and therefore incredibly unlikely to have bugs.

Ruby: What happens when I delete element of an array when iterating over the same array?

It is not safe. Do not change underlying container while you iterate it. This could cause unexpected behaviour.

For example, following code causes infinite loop:

a = [1]
a.each { a.push 1 }

You code can be written using Array#delete_if:

array1 = [5, 4, 3, 1, 2, 7, 8]
min = 4
array1.delete_if { |arrayElement|
arrayElement < min
}
array # => [5, 4, 7, 8]


Related Topics



Leave a reply



Submit