2

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"]
2
  • 2
    why you want to call delete inside each ? what you are trying to achieve? Commented Jan 18, 2013 at 20:58
  • 1
    (Please try to create a focused title.) Commented Jan 18, 2013 at 21:06

4 Answers 4

4

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
Sign up to request clarification or add additional context in comments.

3 Comments

Should shift be used instead of unshift?
AShelly, that helped. Thanks a lot! I have a question when I iterating using array.each is not the same as iterating using 'until'? Does until invoke an iterator?
Until does not invoke the iterator and doesn't work the way .each works either. Technically .each isn't an iterator either , but it is useful to think of it that way especially if you are coming from java.
3

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.

Comments

2

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.

2 Comments

I thought the cardinal rule was "never get involved in a land war in Asia", followed by "Never go against a Sicilian when death is on the line"
Mutating an array while iterating over it? Inconceivable!
0

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
 => []

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.