views:

273

answers:

1

If I have two arrays a and b, what method should the object contained have to override so the subtract method - works properly?

Is it enough with eql?

EDIT

I'm adding more detail to my question.

I have this class defined:

class Instance
    attr_reader :id, :desc 
    def initialize( id ,  desc  )
        @id = id.strip
        @desc = desc.strip
    end

    def sameId?( other )
        @id == other.id
    end

    def eql?( other )
        sameId?( other ) and @desc == other.desc
    end

    def to_s()
        "#{id}:#{desc}"
    end
end

Ok?

Then I have filled my two arrays from different parts and I want to get the difference.

a = Instance.new("1","asdasdad")
b = Instance.new("1","a")
c = Instance.new("1","a")

p a.eql?(b) #false
p b.eql?(c) #true 

x = [a,b]
y = [c]

z = x - y # should leave a because b and c "represent" the same object

But this is not working, because a and b are being kept in the array. I'm wondering what method to I need to override in my class for this to work properly.

+3  A: 

You need to redefine #eql? and the hash method.

You may define it as:

def hash
    id.hash + 32 * desc.hash
end

Details:

To see what's being called in Ruby 1.9:

 % irb
 >> class Logger < BasicObject
 >>   def initialize(delegate)
 >>     @delegate = delegate
 >>   end
 >>   def method_missing(m,*args,&blk)
 >>     ::STDOUT.puts [m,args,blk].inspect
 >>     @delegate.send(m,*args,&blk)
 >>   end
 >> end
 => nil
 >> object = Logger.new(Object.new)
 [:inspect, [], nil]
 => #<Object:0x000001009a02f0>
 >> [object] - [0]
 [:hash, [], nil]
 [:inspect, [], nil]
 => [#<Object:0x000001009a02f0>]
 >> zero = Logger.new(0)
 [:inspect, [], nil]
 => 0
 >> [zero] - [0]
 [:hash, [], nil]
 [:eql?, [0], nil]
 => []

The same is true in ruby 1.8.7

 % irb18
 >> class Logger < Object
 >>   instance_methods.each { |m| undef_method m }
 >>   def initialize(delegate)
 >>     @delegate = delegate
 >>   end
 >>   def method_missing(m,*args,&blk)
 >>     ::STDOUT.puts [m,args,blk].inspect
 >>     @delegate.send(m,*args,&blk)
 >>   end
 >> end
 (irb):2: warning: undefining `__send__' may cause serious problem
 (irb):2: warning: undefining `__id__' may cause serious problem
 => nil
 >> object = Logger.new(Object.new)
 [:inspect, [], nil]
 => #<Object:0x100329690>
 >> [object] - [0]
 [:hash, [], nil]
 [:inspect, [], nil]
 => [#<Object:0x100329690>]
 >> zero = Logger.new(0)
 [:inspect, [], nil]
 => 0
 >> [zero] - [0]
 [:hash, [], nil]
 [:eql?, [0], nil]
 => []
rampion
mmhh let's see... ruby -version : I have 1.8.7 :(
OscarRyz
uhh... :) I think that's a bit too advanced for me just yet..... let me chew it for a while ;)
OscarRyz
Ok, I did `def hash` and include this: `id.has + 32 * desc.hash` and still it is not working. What am I missing?
OscarRyz
Well at least I can see it is being called. Am I to override eql? also?
OscarRyz
Nice one! So actually you should define a good hash-function for objects, and then the eql? function is indeed straightforward (comparing the hashes).
nathanvda
@nathanvda: No. Hashes are finite, the number of possible objects is infinite, therefore the pigeonhole principle applies: there will be at least two objects which are not `eql?` but have the same `hash`.
Jörg W Mittag