Problem with Array#delete ?

D

Daniel Berger

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
 
R

Rick DeNatale

[Note: parts of this message were removed to make it a legal post.]

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?


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.
 
R

Rick DeNatale

[Note: parts of this message were removed to make it a legal post.]

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

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]
 

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,995
Messages
2,570,230
Members
46,819
Latest member
masterdaster

Latest Threads

Top