Forum: Ruby Array#each Looping Gotcha

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
7863582496e914f1ffce56f98d468f72?d=identicon&s=25 Nathan Olberding (nko)
on 2006-04-20 00:43
I think I've found a gotcha. Really, it should be expected behavior, but
it wasn't immediately obvious to me, so I thought it's at least worth
mentioning.

Let's say you want to iterate through an array and delete any items in
that array that match a certain criteria. I thought it'd make sense to
do this:

items.each do |x|
	if x == ""
		items.delete(x)
	end
end


If items.each referred to items[] by reference, this would make sense.
But since it references by value, you're literally changing the array
live, as you're iterating through it. This means that, if you delete an
item in mid-iteration, you change the index of items[]. Since you
deleted something, you skip the next item.

1) Do I have this right?

2) Am I right in assuming that it's possible to create an infinite loop
this way by continually push()ing things on to items[]?
31ab75f7ddda241830659630746cdd3a?d=identicon&s=25 Austin Ziegler (Guest)
on 2006-04-20 01:00
(Received via mailing list)
On 4/19/06, Nathan Olberding <nathan.olberding@gmail.com> wrote:
>                 items.delete(x)
> 1) Do I have this right?
>
> 2) Am I right in assuming that it's possible to create an infinite loop
> this way by continually push()ing things on to items[]?

Except the reference/value thing.

Look at Array#delete_if for your specific example.

-austin
0ec4920185b657a03edf01fff96b4e9b?d=identicon&s=25 Yukihiro Matsumoto (Guest)
on 2006-04-20 01:03
(Received via mailing list)
Hi,

In message "Re: Array#each Looping Gotcha"
    on Thu, 20 Apr 2006 07:43:46 +0900, Nathan Olberding
<nathan.olberding@gmail.com> writes:

|items.each do |x|
|	if x == ""
|		items.delete(x)
|	end
|end
|
|
|If items.each referred to items[] by reference, this would make sense.
|But since it references by value, you're literally changing the array
|live, as you're iterating through it. This means that, if you delete an
|item in mid-iteration, you change the index of items[]. Since you
|deleted something, you skip the next item.
|
|1) Do I have this right?

I'm not sure what you mean by the word "right" here.  It's ok for you
to write that kind of program, but I don't (can't) guarantee you will
get what you expect, although I try my best.  It might cost you much
to guarantee loop safety.  It shouldn't crash in any case though.

|2) Am I right in assuming that it's possible to create an infinite loop
|this way by continually push()ing things on to items[]?

Yes, Ruby obeys you if you command it to loop infinitely.

							matz.
7863582496e914f1ffce56f98d468f72?d=identicon&s=25 Nathan Olberding (nko)
on 2006-04-20 01:17
Yukihiro Matsumoto wrote:
> I'm not sure what you mean by the word "right" here.  It's ok for you
> to write that kind of program, but I don't (can't) guarantee you will
> get what you expect, although I try my best.  It might cost you much
> to guarantee loop safety.  It shouldn't crash in any case though.

Though I was confused for half of the day today, I have to admit that
the current implementation is probably for the best. Where this:

arr.each do |x|
    if y; arr.delete(x); end
end

Can be made to work the way I personally thought it would, it's much
safer to do something like this:

delete_list = []
arr.each do |x|
    if y; delete_list.push(x); end
end
arr -= delete_list

Or better yet, since Ruby includes the feature, use Array#delete_if.

> Yes, Ruby obeys you if you command it to loop infinitely.

I was more asking if that's what would happen, but I suppose the wording
of my question made its own answer :-)
1fba4539b6cafe2e60a2916fa184fc2f?d=identicon&s=25 unknown (Guest)
on 2006-04-20 04:19
(Received via mailing list)
Hi --

On Thu, 20 Apr 2006, Nathan Olberding wrote:

>    if y; arr.delete(x); end
>
> Or better yet, since Ruby includes the feature, use Array#delete_if.

Also, don't forget (among reasons deleting from an array during an
iteration is bad) that delete deletes all instances:

irb(main):027:0> a = [1,2,3,1,4,5,1,6,7]
=> [1, 2, 3, 1, 4, 5, 1, 6, 7]
irb(main):028:0> a.delete(1)
=> 1
irb(main):029:0> a
=> [2, 3, 4, 5, 6, 7]    # all the 1's deleted

Also, something like each_with_index would probably cease to make
sense....


David

--
David A. Black (dblack@wobblini.net)
Ruby Power and Light, LLC (http://www.rubypowerandlight.com)

"Ruby for Rails" PDF now on sale!  http://www.manning.com/black
Paper version coming in early May!
35594c037eba2fa48f7129d5fded828b?d=identicon&s=25 Peter Szinek (Guest)
on 2006-04-20 11:00
(Received via mailing list)
> items.each do |x|
> 	if x == ""
> 		items.delete(x)
> 	end
> end
Wow, in Java you this would throw ConcurrentModificationException (i.e.
you can not iterate and modify a list at the same time, says Java) -
AFAIK, in java there is no workaround for this. It is interesting that
Ruby handles this as well, although i am not sure to which extent this
is (black) magic ;-)

Cheers,
Peter
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2006-04-20 11:28
(Received via mailing list)
2006/4/20, Peter Szinek <peter@rt.sk>:
> > items.each do |x|
> >       if x == ""
> >               items.delete(x)
> >       end
> > end
> Wow, in Java you this would throw ConcurrentModificationException (i.e.
> you can not iterate and modify a list at the same time, says Java) -
> AFAIK, in java there is no workaround for this.

Not exactly true. This special scenario is well covered by Java:

http://java.sun.com/j2se/1.4.2/docs/api/java/util/...)

Std. collections all support remove().

IMHO iterating and deleting like shown is a very bad idea.  Since
there is a method for doing deletions that's exactly the way to go.

Kind regards

robert
35594c037eba2fa48f7129d5fded828b?d=identicon&s=25 Peter Szinek (Guest)
on 2006-04-20 11:44
(Received via mailing list)
Robert Klemme wrote:
> 2006/4/20, Peter Szinek <peter@rt.sk>:
>>> items.each do |x|
>>>       if x == ""
>>>               items.delete(x)
>>>       end
>>> end
> Not exactly true. This special scenario is well covered by Java:
I stand corrected. I had so many problems with similar
iterate-but-dont-touch scenarios in J that i threw remove() in the same
bag automatically ;-).

Anyway, i agree that iterating and deleting  is not a good idea...

Cheers,
Peter
This topic is locked and can not be replied to.