Issues using array.delete within a loop of the same array

J

james.d.masters

This is a simplified example for what I'm trying to do but gets the
point across:

irb(main):001:0> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb(main):002:0> a.each {|e| a.delete e}
=> [2, 4]

I would expect the array to be emptied. Insight please...

Yes, I know that there are other ways to empty an array (i.e. a =
[]). My code is more complex and I'm more interested in why this is
happening and if there is a work-around for removing array items while
iterating over the same array. There will be other cases in this code
where I will only want to remove some of the items.

Thanks in advance...
 
T

Timothy Hunter

This is a simplified example for what I'm trying to do but gets the
point across:

irb(main):001:0> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb(main):002:0> a.each {|e| a.delete e}
=> [2, 4]

I would expect the array to be emptied. Insight please...

Yes, I know that there are other ways to empty an array (i.e. a =
[]). My code is more complex and I'm more interested in why this is
happening and if there is a work-around for removing array items while
iterating over the same array. There will be other cases in this code
where I will only want to remove some of the items.

Thanks in advance...
Use #delete_if instead.
 
M

MenTaLguY

Yes, I know that there are other ways to empty an array (i.e. a =
[]).

a = [] creates a new, empty array; it does not empty the existing array.
My code is more complex and I'm more interested in why this is
happening

It's disallowed by design -- removing elements in the midst of iteration would be a bit like trying to remove carpet tiles while you are standing on them.
and if there is a work-around for removing array items while
iterating over the same array.

Have you considered using Array#reject! to iterate over the array? You can use the result of the block for each iteration (true or false) to control whether each element is dropped or retained once the iteration completes.

a.reject! { |e|
# ... do stuff
e > 3 # discard elements greater than three
}

-mental
 
J

james.d.masters

Use #delete_if instead.

Thanks - I tried that and it also did not work. Sorry for not posting
this attempt in my original:

irb(main):001:0> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb(main):002:0> a.each {|e| a.delete_if {|e2| e2 == e}}
=> [2, 4]
 
J

james.d.masters

a = [] creates a new, empty array; it does not empty the existing array.

Yup, good point.
It's disallowed by design -- removing elements in the midst of iteration would be a bit like trying to remove carpet tiles while you are standing on them.

OK, I'll take that... I had a feeling that would be the final answer.
Although I can't see why it wouldn't be feasible in a language -
especially as I'm removing the current item of iteration (i.e. not one
further down the list) and when there are no duplicates of the same
object in the array.
Have you considered using Array#reject! to iterate over the array? You can use the result of the block for each iteration (true or false) to control whether each element is dropped or retained once the iteration completes.

I vaguely remember reading about Array#reject! but haven't used it
much - I'll have to give that a shot. Thanks...
 
P

Phrogz

Use #delete_if instead.

Thanks - I tried that and it also did not work. Sorry for not posting
this attempt in my original:

irb(main):001:0> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb(main):002:0> a.each {|e| a.delete_if {|e2| e2 == e}}
=> [2, 4]

Kindly suggest that you RTFM by typing "ri Array#delete_if" into your
console. :)

If that doesn't make it clear, try this:

a.delete_if{ true }
 
T

Timothy Hunter

Use #delete_if instead.

Thanks - I tried that and it also did not work. Sorry for not posting
this attempt in my original:

irb(main):001:0> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb(main):002:0> a.each {|e| a.delete_if {|e2| e2 == e}}
=> [2, 4]
#delete_if itself iterates over the array elements. You don't need to
use #each at all.

$ irb
irb(main):001:0> a = [0,1,2,3,4,5]
=> [0, 1, 2, 3, 4, 5]
irb(main):002:0> a.delete_if {|e| e %2 == 0}
=> [1, 3, 5]
irb(main):003:0>
 
P

Phrogz

This is a simplified example for what I'm trying to do but gets the
point across:

irb(main):001:0> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb(main):002:0> a.each {|e| a.delete e}
=> [2, 4]

a = [1,2,3,4,5]
(a.length-1).downto(0){ |i|
a.delete_at( i )
}
p a
#=> []
 
R

Robert Dober

Use #delete_if instead.

Thanks - I tried that and it also did not work. Sorry for not posting
this attempt in my original:

irb(main):001:0> a = [1,2,3,4,5]
=> [1, 2, 3, 4, 5]
irb(main):002:0> a.each {|e| a.delete_if {|e2| e2 == e}}
=> [2, 4]

Kindly suggest that you RTFM by typing "ri Array#delete_if" into your
console. :)

If that doesn't make it clear, try this:

a.delete_if{ true }
This is a nice alias for a.clear ;)
Robert
 
R

Robert Dober

Now this is pure speculation on my part and I'm not offering a solution to
the problem, rather a likely explanation. The other solutions all look good
to me as alternatives.

It would make sense that what Array#each is doing behind the scenes is
simply using an index and a for loop up to the size of the array, and then
passing you a. So then during the first step you delete a[0], which
renumbers the elements in the array. During the second step you delete a[1],
which is now actually 3, since that's now the second element in the array.
Continue in this manner you'll skip every other element in the array.

Let us see

VALUE
rb_ary_each(ary)
VALUE ary;
{
long i;

for (i=0; i<RARRAY(ary)->len; i++) {
rb_yield(RARRAY(ary)->ptr);
}
return ary;
}

your speculation was correct :) fortuntely i < RARRAY(ary)-> len is
reavaluated after each yield!

Cheers
Robert<snip>
 
J

james.d.masters

Kindly suggest that you RTFM by typing "ri Array#delete_if" into your
console. :)

I did RTFM before posting. The question in my original posting was
related to the behavior of deleting items from a list during iteration
so that's why I posted Array#delete_if within the context of
Array#each. To use Array#delete_if outside of the iteration does the
job yet the original question remained. I think that the answer is
that deleting from the same array during an iteration is a no-no using
Array#each... I'm OK with that. I'll use Array#delete_if or
Array.reject!

Thanks...
 
G

Gary Wright

It's disallowed by design -- removing elements in the midst of
iteration would be a bit like trying to remove carpet tiles while
you are standing on them.

Maybe better to say that the behavior is implementation
dependent and therefore should be avoided.

The word "disallow" implies that it is syntactically
invalid or that it will raise an exception yet, as was
shown by other posters, neither occurs.

Gary Wright
 
R

Robert Dober

I did RTFM before posting. The question in my original posting was
related to the behavior of deleting items from a list during iteration
so that's why I posted Array#delete_if within the context of
Array#each. To use Array#delete_if outside of the iteration does the
job yet the original question remained. I think that the answer is
that deleting from the same array during an iteration is a no-no using
Array#each... I'm OK with that. I'll use Array#delete_if or
Array.reject!

Thanks...

The code I posted was version 1.8.5 p12, and of course I should have
added that the behavior is not defined.
YARV does exactly the same right now though.

Good to point it out clearly.

Cheers
Robert
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
474,237
Messages
2,571,190
Members
47,827
Latest member
wyton

Latest Threads

Top