Mysterious behavior with loops

E

ericlin852

Enter these in irb:

a = [1,2,3]

a.each do |i|
a.delete(i)
end


Result?

a => [2]

Shouldn't a be [] ?

Eric
 
S

Stefano Crocco

Enter these in irb:

a = [1,2,3]

a.each do |i|
a.delete(i)
end


Result?

a => [2]

Shouldn't a be [] ?

Eric

Iterating on an array while deleting elements from it is usually a bad idea,
because items may be skipped, as it happens in your case. In particular, this
is the C code for Array#each:

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

If you don't understand C code, this is what it means: for all the numbers
from 0 to the number of elements in the array (excluded), take the element of
the array with that index and pass it to the block.

Now, look what happens for the first element of the array. The index is 0,
corresponding to the element 1. The element is passed to the block, which
deletes it. Now, the array contains only two elements: 2 and 3, with 2
corresponding to the index 0 and 3 to the index 1. But the index used by each
to iterate on the array elements is increased to 1 (since Array#each can't
know that you deleted an item). This means that the next element which will be
passed to the block will be the one corresponding to index 1, which is 3. This
means that one item won't be passed to the block and won't be deleted.

If you want to delete all items of the arryay, you can simply use the delete
method. If you want to delete only some items, you can use Array#delete_if,
which takes a block and removes from the array all the elements for which the
block returns true.

I hope this helps

Stefano
 
E

ericlin852

Thanks Stefano, that definitely answered my question. It's quite
obvious when the index variable is made explicit.

Eric

Enter these in irb:
a = [1,2,3]
a.each do |i|
  a.delete(i)
end

a => [2]
Shouldn't a be [] ?

Iterating on an array while deleting elements from it is usually a bad idea,
because items may be skipped, as it happens in your case. In particular, this
is the C code for Array#each:

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

}

If you don't understand C code, this is what it means: for all the numbers
from 0 to the number of elements in the array (excluded), take the elementof
the array with that index and pass it to the block.

Now, look what happens for the first element of the array. The index is 0,
corresponding to the element 1. The element is passed to the block, which
deletes it. Now, the array contains only two elements: 2 and 3, with 2
corresponding to the index 0 and 3 to the index 1. But the index used by each
to iterate on the array elements is increased to 1 (since Array#each can't
know that you deleted an item). This means that the next element which will be
passed to the block will be the one corresponding to index 1, which is 3. This
means that one item won't be passed to the block and won't be deleted.

If you want to delete all items of the arryay, you can simply use the delete
method. If you want to delete only some items, you can use Array#delete_if,
which takes a block and removes from the array all the elements for which the
block returns true.

I hope this helps

Stefano
 

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
473,768
Messages
2,569,575
Members
45,054
Latest member
LucyCarper

Latest Threads

Top