Akash CP
Akash CP

Reputation: 53

Ruby deleting array element while iterating

a = ['red', 'green', 'blue', 'purple']

a.each do |e|
 puts e
 if e == 'green'
  a.delete(e)
 end
end

By running the above code, I get the following output:

red
green
purple

Can someone explain to me what is happening behind the scene?

Upvotes: 0

Views: 805

Answers (2)

David Aldridge
David Aldridge

Reputation: 52336

To add to Sergio's explanation, a more idiomatic approach to modifying the array would be:

a = ['red', 'green', 'blue', 'purple']
a.reject! do |e|
  puts e
  e == 'green'
end

so:

2.2.5 :001 >     a = ['red', 'green', 'blue', 'purple']
 => ["red", "green", "blue", "purple"] 
2.2.5 :002 >     a.reject! do |e|
2.2.5 :003 >           puts e
2.2.5 :004?>         e == 'green'
2.2.5 :005?>       end
red
green
blue
purple
 => ["red", "blue", "purple"] 
2.2.5 :006 > 

Upvotes: 3

Sergio Tulentsev
Sergio Tulentsev

Reputation: 230286

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

Upvotes: 7

Related Questions