Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Set.include? for custom objects in Ruby

Tags:

ruby

I have a class roughly like this:

class C
    attr_accessor :board # board is a multidimensional array (represents a matrix)

    def initialize
        @board = ... # initialize board
    end   

    def ==(other)
        @board == other.board
    end
end

Still, when I do:

s = Set.new
s.add(C.new)
s.include?(C.new) # => false

Why?

like image 836
whatyouhide Avatar asked Oct 16 '13 18:10

whatyouhide


3 Answers

Set uses eql? and hash, not ==, to test two objects for equality. See, for example, this documentation of Set: "The equality of each couple of elements is determined according to Object#eql? and Object#hash, since Set uses Hash as storage."

If you want two different C objects to be the same for set membership, you'll have to override those two methods.

class C
  attr_accessor :board 

  def initialize
    @board = 12
  end

  def eql?(other)
    @board == other.board
  end

   def hash
    @board.hash
  end
end

s = Set.new
s.add C.new
s.include? C.new   # => true
like image 51
Hew Wolff Avatar answered Oct 20 '22 16:10

Hew Wolff


You need to do something below :

require 'set'
class C
  attr_accessor :board 

  def initialize
    @board = 12
  end

  def ==(other)
    @board == other.board
  end
end
s = Set.new
c = C.new
s.add(c)
s.include? c # => true

The reason below will not work:

s.add(C.new)
s.include?(C.new) # => false

Using C.new you create 2 different objects. If you do run C.new thrice then you will get 3 different objects:

C.new.object_id # => 74070710
C.new.object_id # => 74070360
C.new.object_id # => 74070030

Summary : The instance of C you added to s,using Set#add and the instance of C you are checking using Set#include? are 2 different objects. So the result you got is more obvious.

like image 26
Arup Rakshit Avatar answered Oct 20 '22 16:10

Arup Rakshit


class C
    attr_accessor :board # board is a multidimensional array (represents a matrix)

    def initialize
        board = [[1],[2]] # initialize board
        p @board #=> nil !!

    end 
    def ==(other)
        @board == other.board
    end

    def eql?(other) # not used
        puts "eql? called"
        @board == other.board
    end
    def ==(other) # not used
        puts "== called"
        @board == other.board
    end

    def hash
      puts "hash called"
      board.hash
    end
end
require 'set'
s = Set.new
s.add(c = C.new)
p s.include?(c) 

Set uses a Hash as storage underneath. Output:

nil
hash called
hash called
true
like image 29
steenslag Avatar answered Oct 20 '22 15:10

steenslag