Tuesday, October 24, 2006

Ruby, intersection of two arrays of objects

One, serious obstacle I have encounter working with Ruby, was how to perform intersection of two arrays of objects.

Ruby comes with overloaded operator &, using which intersection of two array is childish.

For example:

#!/usr/bin/env ruby

a=[1, 2, 3, 4]
b=[1, 2, 2 ,3 ,4, 5, 6]
c= a & b  #intersection of 'a' and 'b' arrays
print c

will produce: 1 2 3 4

And for example this: 

#!/usr/bin/env ruby

a=['aa', 'ac', 'acb', 'acbd']
b=[ 'ac', 'acb', 'bca', 'zwx']
c= a & b  #intersection of 'a' and 'b' arrays
puts c

will produce: ac acb  

Everything is OK, when you perform intersection of standard types, like String or Integers, etc.

However, there is problem when you create your own class, and try to perform intersection of  array of objects of your class.  

#!/usr/bin/env ruby

class MyClass #my own very simple class
   attr_reader :var1, :var2   
   def initialize(arg1,arg2)     
      @var1 = arg1
      @var2 = arg2
    end
    def to_s
        "#{var1}: #{var2}"
    end
end
a = [MyClass.new('house',3), MyClass.new('mouse',5)]
b = [MyClass.new('cat',3), MyClass.new('house',5), MyClass.new('mouse',5)]
c = a & b #INTERSECTION !!!
puts c

This, unfortunately gives nothing!

After a while I found solution to that. The solution is: you must overload eql? and hash functions in you class. Java programmers should now something, because as I remember in Java, you do something similar while working especially with Comparable interface.

So, to make intersection, final example my program looks like this:

#!/usr/bin/env ruby

class MyClass attr_reader :var1, :var2
    def initialize(arg1,arg2)
        @var1 = arg1
        @var2 = arg2
    end
    def to_s
        "#{var1}: #{var2}"
    end
    def eql? other #OVERLOADED eql? !!!
       other.kind_of?(self.class) && @var1 == other.var1
    end
    def hash #OVERLOADED hash !!!
        @var1.hash #use var1 hash value,
                   #as hash for MyClass
    end
end
a = [MyClass.new('house',3), MyClass.new('mouse',5)]
b = [MyClass.new('cat',3), MyClass.new('house',5), MyClass.new('mouse',5)]
c = a & b #INTERSECTION !!!
puts c

And this finaly produces what I wanted:

house: 3
mouse: 5

I used only standard hash value from @var1 in my class, because I was only interested in making intersection based on @var1, nevertheless there is noting against to make more complex hash and eql? function.