From: Robert Klemme Date: 2007-08-18T21:14:57+09:00 Subject: Re: Problem with Array#delete On 18.08.2007 11:44, Matthew B Gardner wrote: > 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) => # irb(main):008:0> f2 = Foo.new("a", 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