Problem with Array#delete

M

Matthew B Gardner

Hello, I'm baffled by the following problem:

I have an Account class and an external array that keeps them in a list. I'm
trying to delete the account objects one at a time as needed, by:

class Account
def quit
array_name.delete self
end
end

However, this is deleting every account in the list (array_name == []). I've
verified that the account objects are unique. Just for an example, this is
the equivalent of what's happening:

class Account
def quit arr
arr.delete self
end
end

a1 = Account.new
a2 = Account.new
arr = []
arr << a1
arr << a2
a1.quit arr
arr #=> []


Has anyone else experienced this problem, or know what the likely cause is? I
have other arrays that work in the exact same fashion and work as expected.

Thank you for any help,
Matt
 
M

Morton Goldberg

Hello, I'm baffled by the following problem:

I have an Account class and an external array that keeps them in a
list. I'm
trying to delete the account objects one at a time as needed, by:

class Account
def quit
array_name.delete self
end
end

However, this is deleting every account in the list (array_name ==
[]). I've
verified that the account objects are unique. Just for an example,
this is
the equivalent of what's happening:

class Account
def quit arr
arr.delete self
end
end

a1 = Account.new
a2 = Account.new
arr = []
arr << a1
arr << a2
a1.quit arr
arr #=> []

Has anyone else experienced this problem, or know what the likely
cause is? I
have other arrays that work in the exact same fashion and work as
expected.

I ran your code and I didn't have a problem: it did what you
expected. Are you sure that the code you posted is exactly the same
as the code that is giving you trouble?

Regards, Morton
 
D

Dan Zwell

Matthew said:
Hello, I'm baffled by the following problem:

I have an Account class and an external array that keeps them in a list. I'm
trying to delete the account objects one at a time as needed, by:

class Account
def quit
array_name.delete self
end
end

However, this is deleting every account in the list (array_name == []). I've
verified that the account objects are unique. Just for an example, this is
the equivalent of what's happening:

class Account
def quit arr
arr.delete self
end
end

a1 = Account.new
a2 = Account.new
arr = []
arr << a1
arr << a2
a1.quit arr
arr #=> []


Has anyone else experienced this problem, or know what the likely cause is? I
have other arrays that work in the exact same fashion and work as expected.

Thank you for any help,
Matt

Wow, that was a well-asked question. This seems like a Ruby bug, though
I can't reproduce it. When I run the code you posted, arr[] still
contains a2 (as it should). I am using Ruby 1.8.6 (on Linux). What are
you using?

Dan
 
M

Matthew B Gardner

Sorry, I only meant the code I posted as an example of what is happening --
the actual code would be a little hard to gather and format, but I will do so
if the following isn't enough information:

ruby -v #=> ruby 1.8.4 (2005-12-24) [i486-linux]

Ok, I threw this code in to gather some info, and following it is the
printout:

p world.characters.class
p world.characters.size
world.characters.each do |ch|
p ch.class
p ch.object_id
end
world.characters.delete(self)
p world.characters

Array
2
Character
-607336504
Character
-607410834
[]

Do I need to format up the code, or is there something telling from the
information here?

Thanks,
Matt

Hello, I'm baffled by the following problem:

I have an Account class and an external array that keeps them in a
list. I'm
trying to delete the account objects one at a time as needed, by:

class Account
def quit
array_name.delete self
end
end

However, this is deleting every account in the list (array_name ==
[]). I've
verified that the account objects are unique. Just for an example,
this is
the equivalent of what's happening:

class Account
def quit arr
arr.delete self
end
end

a1 = Account.new
a2 = Account.new
arr = []
arr << a1
arr << a2
a1.quit arr
arr #=> []

Has anyone else experienced this problem, or know what the likely
cause is? I
have other arrays that work in the exact same fashion and work as
expected.

I ran your code and I didn't have a problem: it did what you
expected. Are you sure that the code you posted is exactly the same
as the code that is giving you trouble?

Regards, Morton
 
M

Matthew B Gardner

I went a little different route, but that was indeed the problem. I just
defined == in my class, which I guess was comparing the class of the object
instead of the object_id. I did:

def == obj
self.equal?(obj)
end

If you have time, or if someone else does, is there a quick explanation of why
this happened in this instance, but not with similar classes that function
fine with essentially duplicate methods of adding to and deleting from
arrays? Hopefully I can avoid future problems if I understand what's
happening (I'm a hobby programmer, so I lack some technical expertise).

Thank you, and the other repliers, for the help -- it's very much appreciated.
-Matt

Sorry, I only meant the code I posted as an example of what is happening
-- the actual code would be a little hard to gather and format, but I
will do so if the following isn't enough information:

ruby -v #=> ruby 1.8.4 (2005-12-24) [i486-linux]

Ok, I threw this code in to gather some info, and following it is the
printout:

p world.characters.class
p world.characters.size
world.characters.each do |ch|
p ch.class
p ch.object_id
end
world.characters.delete(self)
p world.characters

Array
2
Character
-607336504
Character
-607410834
[]

Do I need to format up the code, or is there something telling from the
information here?

The crucial bit is missing: how did you define ==, eql? and hash in your
class? Did you define them? If not, using Struct might help because
that gives you those methods for free:

YourClass = Struct.new :name, :eek:ther_field do
def method_you_need
end
end

Then eql?, == and hash will be implemented in terms of "name" and
"other_field".

Kind regards

robert
 
R

Robert Klemme

I went a little different route, but that was indeed the problem. I just
defined == in my class, which I guess was comparing the class of the object
instead of the object_id. I did:

def == obj
self.equal?(obj)
end

If you have time, or if someone else does, is there a quick explanation of why
this happened in this instance,

If you just defined == the way you presented above then the issue should
not have occurred because equal? tests for identity. It's difficult to
comment on fragments when the whole code is not presented.
but not with similar classes that function
fine with essentially duplicate methods of adding to and deleting from
arrays?

I am not sure what you mean by this. Can you elaborate?
Hopefully I can avoid future problems if I understand what's
happening (I'm a hobby programmer, so I lack some technical expertise).

There are some concepts you should keep in mind that have some
similarities and will typically wreck havoc on your code when confused.
Both define relations (in the mathematical sense) on objects.

First, there is "identity". Two objects are identical when they are
actually just one, i.e. the same instance.

Then, there is equivalence. Equivalence is defined per class. For
example two strings containing the same sequence of characters are
equivalent. Sometimes only identical instances are equivalent.

Now, these different concepts are implemented in Ruby via different methods:
eql? and == implement equivalence
equal? implements identity

Most containers (an Array is a container) use equivalence, namely
implemented via eql? to test whether some objects match (e.g. for
deletion), because it is the more flexible and more useful concept.
(Think of an Array of Strings and you want to delete one of them with a
certain character sequence, you would want to provide a string with that
sequence as template and not the exact same object in the array - which
you might not know beforehand.)

Now, there is a slight twist: since for some algorithms it's not
efficient to compare something against all elements in the container
(for example, Array#uniq would have to compare every element of the
array with every other element which is O(n*n), i.e. if you double the
elements in the Array you quadruple the number of comparisons
necessary). In those cases (unfortunately they are not all documented)
typically a Hash is used behind the scenes. For objects to work
properly as Hash key methods eql? *and* hash need to be implemented
properly.

Consequence is, that you should always implement eql? and hash (and also
== for consistency) reasons *if* you plan to use instances of your class
in these circumstances *and* want to define equivalence different than
via identity (which happens to be the default implementation in class
Object). Typically you will choose some fields for this and you must
also make sure that equivalent instances yield the same (!) hash code.
Normally you do that by applying some math operation (binary XOR is
frequently used, because it's fast and guarantees that all values used
influence the result) on the hash values of those members that you
determine as key elements for equivalence.

The easiest way to do that is by using Struct, because that will
generate a class with all the necessary methods. Example:

# name and age are key for Foo
Foo = Struct.new :name, :age do
attr_accessor :unimportant_other_attribute
end


irb(main):007:0> f1 = Foo.new("a", 10)
=> #<struct Foo name="a", age=10>
irb(main):008:0> f2 = Foo.new("a", 10)
=> #<struct Foo name="a", age=10>
irb(main):009:0> f1.hash
=> -2186440
irb(main):010:0> f2.hash
=> -2186440
irb(main):011:0> f1.eql? f2
=> true
irb(main):012:0> f1 == f2
=> true
irb(main):013:0> f1.equal? f2
=> false
irb(main):014:0> f1.unimportant_other_attribute = "bar"
=> "bar"
irb(main):015:0> f1.eql? f2
=> true
irb(main):016:0> f1.name = "hello"
=> "hello"
irb(main):017:0> f1.eql? f2
=> false

There is another thing you should be aware: numbers in Ruby actually
implement *two* different equivalence relations:

irb(main):018:0> 1 == 1.0
=> true
irb(main):019:0> 1.eql? 1.0
=> false
irb(main):020:0> 1.hash
=> 3
irb(main):021:0> 1.0.hash
=> 233071

But most classes treat == and eql? synonym.

Next week we'll dive into ordering and operator <=>. :))

Kind regards

robert
 
M

Matthew B Gardner

I went a little different route, but that was indeed the problem. I just=
=20
defined =3D=3D in my class, which I guess was comparing the class of the = object=20
instead of the object_id. I did:
=20
def =3D=3D obj
=A0=A0=A0=A0=A0=A0self.equal?(obj)
end

Ok, so it looks like this didn't work so well after all. Initially, I thoug=
ht=20
it did because I reloaded the character.rb file while the program was runni=
ng=20
and it worked fine. However, when I shutdown the program and started it bac=
k=20
up, I experienced the same problem (though it again worked after reloading=
=20
the file). I'll post all the necessary code:

def world
returns the world object
end

class World
def initialize
characters =3D []
end
end

class Account
def foo
@character =3D Character.new()
world.characters << @character
end
end

class Character
def quit
p self.object_id
p world.characters.class
p world.characters.size
world.characters.each do |ch|
p ch.class
p ch.object_id
end
world.characters.delete(self)
p world.characters
end
end

This is the output from Character#quit, with two character objects in the=20
world#characters array:

=2D607170236
Array
2
Character
=2D607170236
Character
=2D607240286
[]

As you can see, both Character objects are being deleted when only the firs=
t=20
should be (at least as far as my intention). In an attempt to fix this, I=20
added the following method:

class Character
def =3D=3D obj
self.equal?(obj)
end
end

I thought this did fix it, because I reloaded the file and tested it -- and=
it=20
only deleted the targeted object. However, when I restarted the program, it=
=20
didn't work and deleted both objects again. Without changing anything, and=
=20
just reloading the file while the program was still running, it worked as=20
intended again and deleted only the targeted object.

I have an Account class that utilizes World#accounts (an Array object) in t=
he=20
exact same way that Character utilizes World#characters, and the deletion o=
f=20
accounts works fine.

Robert, thank you for your reply -- I hope that this answers the noted=20
questions that you had, and maybe even provides some more insight into what=
=20
the problem might be. Is there an explanation for why the Character#=3D=3D =
method=20
works after being reloaded, but not at start time? Also, Robert, I reviewed=
=20
the portion of your reply regarding a Struct object, and it doesn't look li=
ke=20
it would be beneficial in my case -- I'm just trying to add and delete sing=
le=20
instances of a class from an array -- but I could be wrong?

Thanks again for any help,
Matt
 
D

David A. Black

--1926193751-2036615464-1187462143=:4504
Content-Type: MULTIPART/MIXED; BOUNDARY="1926193751-2036615464-1187462143=:4504"

This message is in MIME format. The first part should be readable text,
while the remaining parts are likely unreadable without MIME-aware tools.

--1926193751-2036615464-1187462143=:4504
Content-Type: TEXT/PLAIN; charset=X-UNKNOWN; format=flowed
Content-Transfer-Encoding: QUOTED-PRINTABLE

Hi --

I went a little different route, but that was indeed the problem. I just
defined =3D=3D in my class, which I guess was comparing the class of the= object
instead of the object_id. I did:

def =3D=3D obj
=A0=A0=A0=A0=A0=A0self.equal?(obj)
end

Ok, so it looks like this didn't work so well after all. Initially, I tho= ught
it did because I reloaded the character.rb file while the program was run= ning
and it worked fine. However, when I shutdown the program and started it b= ack
up, I experienced the same problem (though it again worked after reloadin= g
the file). I'll post all the necessary code:

def world
=09returns the world object
end

class World
=09def initialize
=09=09characters =3D []
=09end
end

Can you un-pseudo-code the code to the point where it does (or doesn't
do) what you want? I can't make the thing you're describing happen
when I try to get the code up and running, so a complete (non-)working
example would be good.


David

--=20
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
--1926193751-2036615464-1187462143=:4504--
--1926193751-2036615464-1187462143=:4504--
 
M

Matthew B Gardner

Thank you to everyone who has tried to help me with this today -- I know for
certain -what- the problem is now, after adding in some testing. My Character
class, for whatever reason, is using == to evaluate class. So, when I am
trying to use Array#delete (array.delete(self) for a Character object), it's
deleting every Character object in that array instead of just that single
instance. I've tried to fix this issue by defining the == method in my
Character class like this:

def Character
def == obj
self.equal?(obj)
end
end

However, this doesn't initially work -- I have to reload the file during
runtime for it to utilize the above method. For sake of clarity, this is what
I mean:

Start program...
a = Character.new
b = Character.new
a == b #=> true
Reload character.rb file while program is running...
a == b #=> false

Hopefully that gets my meaning across. I'm not sure why Character#== isn't
initially getting used, or if there's something wrong with my declaration.
I'm also not sure why Character#== (prior to my defining Character#==) is
comparing class and my other classes aren't.

I hope that this description is satisfactory -- and thank you for any help
that can be offered.
-Matt

On Saturday 18 August 2007 14:35, David A. Black wrote:
 
D

David A. Black

Hi --

Thank you to everyone who has tried to help me with this today -- I know for
certain -what- the problem is now, after adding in some testing. My Character
class, for whatever reason, is using == to evaluate class. So, when I am
trying to use Array#delete (array.delete(self) for a Character object), it's
deleting every Character object in that array instead of just that single
instance. I've tried to fix this issue by defining the == method in my
Character class like this:

def Character
def == obj
self.equal?(obj)
end
end

However, this doesn't initially work -- I have to reload the file during
runtime for it to utilize the above method. For sake of clarity, this is what
I mean:

Start program...
a = Character.new
b = Character.new
a == b #=> true
Reload character.rb file while program is running...
a == b #=> false

Hopefully that gets my meaning across. I'm not sure why Character#== isn't
initially getting used, or if there's something wrong with my declaration.
I'm also not sure why Character#== (prior to my defining Character#==) is
comparing class and my other classes aren't.

I hope that this description is satisfactory -- and thank you for any help
that can be offered.

I still can't tell what's happening without seeing actual code that's
doing what you're describing. Have you got a running example?


David

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
M

Matthew B Gardner

Hello -
On Saturday 18 August 2007 17:56, David A. Black wrote:
Hi --



I still can't tell what's happening without seeing actual code that's
doing what you're describing. Have you got a running example?


David

Here's the pertinent code:

require 'singleton'

class Engine
include Singleton
attr_reader :world
def initialize
@world = nil
end

def startup
@world = World.new
end
end

class World
attr_accessor :characters
def initialize
@characters = []
end
end

class Root
def initialize name
@name = name
end
end

class Account
attr_accessor :character
def initialize
@character = nil
end
def foo
@character = Character.new "testname", self
world.characters << @character
end
end

class Mind < Root
def initialize name
super(name)
end
end

class Character < Mind
def initialize name, acc
super(name)
@account = acc
end
def quit
world.characters.delete self
end
end

def world
Engine.instance.world
end

Engine.instance.startup
acc1 = Account.new
acc1.foo
acc2 = Account.new
acc2.foo
p world.characters.size
acc1.character.quit
p world.characters.size


However, this isn't reproducing my error (as you can see from the printout).
As I've said before, I have other World#array variables that function the
exact same way and don't run into the error I've been experiencing. Hopefully
the code helps pinpoint (or at least hint at) why Character#== is evaluating
class instead of identity, or why I have to reload the file for my defined
Character#== to take effect.

I appreciate any help...I'm going a little crazy trying to solve this problem.
-Matt
 
D

David A. Black

Hi --

Hello -
On Saturday 18 August 2007 17:56, David A. Black wrote:
Hi --



I still can't tell what's happening without seeing actual code that's
doing what you're describing. Have you got a running example?


David

Here's the pertinent code:

require 'singleton'

class Engine
include Singleton
attr_reader :world
def initialize
@world = nil
end

def startup
@world = World.new
end
end

class World
attr_accessor :characters
def initialize
@characters = []
end
end

class Root
def initialize name
@name = name
end
end

class Account
attr_accessor :character
def initialize
@character = nil
end
def foo
@character = Character.new "testname", self
world.characters << @character
end
end

class Mind < Root
def initialize name
super(name)
end
end

class Character < Mind
def initialize name, acc
super(name)
@account = acc
end
def quit
world.characters.delete self
end
end

def world
Engine.instance.world
end

Engine.instance.startup
acc1 = Account.new
acc1.foo
acc2 = Account.new
acc2.foo
p world.characters.size
acc1.character.quit
p world.characters.size


However, this isn't reproducing my error (as you can see from the printout).
As I've said before, I have other World#array variables that function the
exact same way and don't run into the error I've been experiencing. Hopefully
the code helps pinpoint (or at least hint at) why Character#== is evaluating
class instead of identity, or why I have to reload the file for my defined
Character#== to take effect.

I'm feeling a little stupid, but where do you define Character#== ?


David

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
M

Matthew B Gardner

Hello -

Hi --

Hello -
On Saturday 18 August 2007 17:56, David A. Black wrote:
Hi --

On Sun, 19 Aug 2007, Matthew B Gardner wrote:
Thank you to everyone who has tried to help me with this today -- I
know for certain -what- the problem is now, after adding in some
testing. My Character class, for whatever reason, is using == to
evaluate class. So, when I am trying to use Array#delete
(array.delete(self) for a Character object), it's deleting every
Character object in that array instead of just that single instance.
I've tried to fix this issue by defining the == method in my Character
class like this:

def Character
def == obj
self.equal?(obj)
end
end

However, this doesn't initially work -- I have to reload the file
during runtime for it to utilize the above method. For sake of clarity,
this is what I mean:

Start program...
a = Character.new
b = Character.new
a == b #=> true
Reload character.rb file while program is running...
a == b #=> false

Hopefully that gets my meaning across. I'm not sure why Character#==
isn't initially getting used, or if there's something wrong with my
declaration. I'm also not sure why Character#== (prior to my defining
Character#==) is comparing class and my other classes aren't.

I hope that this description is satisfactory -- and thank you for any
help that can be offered.

I still can't tell what's happening without seeing actual code that's
doing what you're describing. Have you got a running example?


David

Here's the pertinent code:

require 'singleton'

class Engine
include Singleton
attr_reader :world
def initialize
@world = nil
end

def startup
@world = World.new
end
end

class World
attr_accessor :characters
def initialize
@characters = []
end
end

class Root
def initialize name
@name = name
end
end

class Account
attr_accessor :character
def initialize
@character = nil
end
def foo
@character = Character.new "testname", self
world.characters << @character
end
end

class Mind < Root
def initialize name
super(name)
end
end

class Character < Mind
def initialize name, acc
super(name)
@account = acc
end
def quit
world.characters.delete self
end
end

def world
Engine.instance.world
end

Engine.instance.startup
acc1 = Account.new
acc1.foo
acc2 = Account.new
acc2.foo
p world.characters.size
acc1.character.quit
p world.characters.size


However, this isn't reproducing my error (as you can see from the
printout). As I've said before, I have other World#array variables that
function the exact same way and don't run into the error I've been
experiencing. Hopefully the code helps pinpoint (or at least hint at) why
Character#== is evaluating class instead of identity, or why I have to
reload the file for my defined Character#== to take effect.

I'm feeling a little stupid, but where do you define Character#== ?


David

I didn't include it with my code, but I wrote it in like this:

class Character < Mind
def initialize name, acc
super(name)
@account = acc
end
def == obj
self.equal?(obj)
end
def quit
world.characters.delete self
end
end

This addition only works after I reload the character.rb file while the
program is running. Initially (until I reload the file),
world#characters.delete deletes all of the Character objects -- after the
reload it only delete the instance.

Does this help at all?

Thanks again,
Matt
 
D

David A. Black

Hi --

I didn't include it with my code, but I wrote it in like this:

class Character < Mind
def initialize name, acc
super(name)
@account = acc
end
def == obj
self.equal?(obj)
end
def quit
world.characters.delete self
end
end

This addition only works after I reload the character.rb file while the
program is running. Initially (until I reload the file),
world#characters.delete deletes all of the Character objects -- after the
reload it only delete the instance.

Does this help at all?

I'm afraid I can't reproduce the error, so I'm not sure where to start
looking for trouble. I get:

2
1

when I run it.


David

--
* Books:
RAILS ROUTING (new! http://www.awprofessional.com/title/0321509242)
RUBY FOR RAILS (http://www.manning.com/black)
* Ruby/Rails training
& consulting: Ruby Power and Light, LLC (http://www.rubypal.com)
 
F

Florian Frank

Matthew said:
class Character < Mind
def initialize name, acc
super(name)
@account = acc
end
def == obj
self.equal?(obj)
end
def quit
world.characters.delete self
end
end

def ==(other)
@name == other.instance_variable_get:)@name)
end
 

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,820
Latest member
GilbertoA5

Latest Threads

Top