Problem with Array#delete?

Hi all,

In messing around with Array#delete I think I've uncovered a problem.
Consider this example:

class Foo
   def ==(other)
      $array.clear
      false
   end
end

arr = [1, 2, 3]
arr << Foo.new
Arr << 'b'

$array = arr

arr.delete('a')

p arr # => []
p $array # => []

That's what I would expect. It did the comparison, and called the
Foo#== method, which in turn cleared the receiver.

But if you do this instead:

arr.delete(1)

You end up with:

[nil, nil, #<Foo:0x2865ff4>]
[nil, nil, #<Foo:0x2865ff4>]

What's happening?

Thanks,

Dan

Well, I haven't dug into the implementation of Array#delete enough to fully
explain the results but.

You do know that after the assignment

$array = arr

You've made the global variable $array reference the same object to which
you are sending delete.

So the $array.clear in the Foo#== method is clearing that object which is
the receiver of the delete method.

Now in general ruby iterator methods don't expect the enumerated object to
change during the iteration and that's just what's happening.

This is no doubt confusing the delete method.

···

On Tue, Jun 24, 2008 at 4:43 PM, Daniel Berger <djberg96@gmail.com> wrote:

Hi all,

In messing around with Array#delete I think I've uncovered a problem.
Consider this example:

class Foo
  def ==(other)
     $array.clear
     false
  end
end

arr = [1, 2, 3]
arr << Foo.new
Arr << 'b'

$array = arr

arr.delete('a')

p arr # =>
p $array # =>

That's what I would expect. It did the comparison, and called the
Foo#== method, which in turn cleared the receiver.

But if you do this instead:

arr.delete(1)

You end up with:

[nil, nil, #<Foo:0x2865ff4>]
[nil, nil, #<Foo:0x2865ff4>]

What's happening?

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/

And having now looked at the Ruby 1.8 implementation of Array#delete, I
think I know exactly what's going on.

Here's a pseudo-ruby translation of the C code:

class Array
  def delete(obj)
      i1 = i2 = 0
      while i1 < self.length
         e = self[i1]
          unless e == obj
             self[i2] = e
             i2 += 1
          end
          i1 += 1
       end
       if i2 = self.length # We didn't find any matches
          return yield(obj) if block_given?
       else
          self.length = i2
       end
    end

    def length=(l)
        # A method to set the length of the array and make any necessary
internal adjustments
    end
end

So, you start out with arr = $array = [1, 2, 3, aFoo, 'b']

And then call
arr.delete(1)

Now lets's step through the delete method

i1 i2 self e e == 1 Action
0 0 [1, 2, 3, aFoo, 'b'] 1 true so we just
increment i1 giving
1 0 [1, 2, 3, aFoo, 'b'] 2 false so we
change self[i0] and increment i2 giving
2 1 [2, 2, 3, aFoo, 'b'] 3 false so we change
self[i0] and increment i2 giving
3 2 [2, 3, 3, aFoo, 'b'] Foo BANG! this clears
$array which is also self which is also arr, since all reference the same
object. The state is now:
3 2 [nil, nil, nil, nil] Foo false Foo#==
returns false so we set self[i2] = e and increment i2 giving:
4 3 [nil, nil, aFoo, nil]

And the loop ends and self.length = 3 adjusts things so that self == arr ==
$array == [nil, nil, aFoo]

···

On Tue, Jun 24, 2008 at 5:53 PM, Rick DeNatale <rick.denatale@gmail.com> wrote:

Well, I haven't dug into the implementation of Array#delete enough to fully
explain the results but.

--
Rick DeNatale

My blog on Ruby
http://talklikeaduck.denhaven2.com/