Qufr kaifi
Qufr kaifi

Reputation: 21

Why does Array.delete inside Array.each not delete all items?

I am new to Ruby and I wrote a very simple application to print the days of week and then delete one day in a loop:

def print_days(days)
    days.each do |day|
        print "The day of the week is: #{day}\n"
        days.delete(day)
        print "\n*****************************************************\n"
        print days
        print "\n*****************************************************\n"
    end
end

wd = %w[Monday Tuesday Wednesday Thursday Friday Saturday Sunday]

print print_days(wd

This gives the following output when run. Can anyone explain me why Tuesday, Thursday and Saturday are skipped when I am deleting each element sequentially and the array shows them being there? You can run this simple code at your setup:

The day of the week is: Monday

*****************************************************
["Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"]
*****************************************************
The day of the week is: Wednesday

*****************************************************
["Tuesday", "Thursday", "Friday", "Saturday", "Sunday"]
*****************************************************
The day of the week is: Friday

*****************************************************
["Tuesday", "Thursday", "Saturday", "Sunday"]
*****************************************************
The day of the week is: Sunday

*****************************************************
["Tuesday", "Thursday", "Saturday"]
*****************************************************
["Tuesday", "Thursday", "Saturday"]

Upvotes: 2

Views: 190

Answers (4)

Leif
Leif

Reputation: 1289

Given most of the above answers explain why what you're doing is failing, you can get what you seem to want using variations of

Array#drop_while
Array#delete_if
Array#select!

or

Array#keep_if

e.g.

a=[1,2,3,4]
a.drop_while{|e| puts e; true}

Gives

1
2
3
4
 => []

Upvotes: 0

AShelly
AShelly

Reputation: 35520

You are deleting elements from the array while you are iterating through it, invalidating the iterator.

You could try

   until (days.empty?) 
       day = days.shift
       print "The day of the week is: #{day}\n"
   end

or

   days.each{|day| print "The day of the week is: #{day}\n"}
   days.clear

Upvotes: 4

Alex Wayne
Alex Wayne

Reputation: 186994

You broke the cardinal rule, do not mutate the object you are iterating over, while you are iterating over it!

Here what's happeneing:

  1. Iterate over the array
  2. Start with the first item
  3. Delete the first item
  4. The second item in the array is now the first.
  5. Grab the second item, and since the previous second item is now first, it grabs what used to be the third item.
  6. Delete the second item (which used to be third) in the mutated array

So it sort of skips deleting every other item. These types of bizarre bugs are why it's very frowned upon to change the object that you are iterating over, while you are iterating over it. Don't do that.

But given how contrived your example is, it's hard to suggest a better way. Depending on your actual goal, there is better ways to do this.

Upvotes: 2

digidigo
digidigo

Reputation: 2584

You are modifying the array during the iteration of all the elements. Internally the each method is keeping the index of the last item it yielded to your block. This is basically invalidating the iterator. Other languages would throw an exception for you.

Ruby does not.

So on the first time through it yields the element at index 0

Then you delete the element at index 0

Then the next time through the it yields the element at index 1, which basically skips the Tuesday since it is now at index 0.

Upvotes: 3

Related Questions