Ruby で自分で作ったクラスをハッシュのキーに設定する場合の話

今回以下の様なクラスを作成して、

インスタンス変数(今回は @name )の値が同じ場合にハッシュで同一キーと判定してほしかった。

class HashKey
  def initialize(name)
    @name = name
  end
end

事前にいくつか必要なメソッドを見ていきます。

Object#equal? と Object#eql?

Object#equal?

  • 2つのオブジェクトが同一のものか調べる。
  • Object#object_id を比較する。
  • 再定義は付加
p("foo".equal?("bar")) #=> false
p("foo".equal?("foo")) #=> false

p(4.equal?(4)) #=> true
p(4.equal?(4.0)) #=> false

p(:foo.equal? :foo) #=> true

http://docs.ruby-lang.org/ja/2.0.0/method/Object/i/equal=3f.html

Object#eql?

  • デフォルトでは equal? と同じオブジェクトが同一のものか調べる。
  • Hash で二つのキー が等しいかどうかを判定するのに使われる。
  • 各クラスの性質に合わせて再定義すべき。
  • このメソッドを再定義した時には Object#hash メソッドも再定義しなければならない。

http://docs.ruby-lang.org/ja/2.0.0/method/Object/i/eql=3f.html

では、Object#hash を見てみます。

Object#hash

  • オブジェクトのハッシュ値を返す。
  • デフォルトでは Object#object_id と同じ値を返す。
  • A.eql?(B) ならば A.hash == B.hash
  • eql? を再定義した時には必ずこちらも合わせて再定義しなければならない。

つまり、eql? と hash が定義されていて、同一の値を判定させればいいみたいです。

上記を読んだ上で、実際にいくつかのパターンを試してみる

キーが文字列の場合

h2 = Hash.new
h2["key1"] = "fuga"
h2["key1"] = "piyo"
p h2 #=>{"key1"=>"piyo"}

同一のキーに値を設定した場合上書きになります。

自分で作ったクラスの場合

クラス定義

class HashKey
  def initialize(name)
    @name = name
  end
end
key1 = HashKey.new("hoge")

h1 = Hash.new
h1[key1] = "fuga"
h1[key1] = "piyo"
p h1 #=>{#<HashKey:0x007f9294058c90 @name="hoge">=>"piyo"}

同一の object_id なのでこちらも上書きになります。

同じインスタンス変数を持った別のオブジェクトの場合

key1 = HashKey.new("hoge")
key2 = HashKey.new("hoge")

h1 = Hash.new
h1[key1] = "fuga"
h1[key2] = "piyo"
p key1.eql? key2 #=>false
p h1 #=>{#<HashKey:0x007fdea2858c60 @name="hoge">=>"fuga", #<HashKey:0x007fdea2858c10 @name="hoge">=>"piyo"}

eql? で比較した場合に false になるので違うキーとして設定されます。

今回はこのパターンの時に同一のキーとしたかったんです。

では、冒頭に書いた eql? メソッドと hash メソッドを定義します。

class HashKey
  attr_accessor :name
  def initialize(name)
    @name = name
  end

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

  def hash
    @name.hash
  end
end
key1 = HashKey.new("hoge")
key2 = HashKey.new("hoge")

h1 = Hash.new
h1[key1] = "fuga"
h1[key2] = "piyo"
p key1.eql? key2 #=>true
p h1 #=>{#<HashKey:0x007fc24c1fc820 @name="hoge">=>"piyo"}

今回は同一のキーと判定されて 後から追加した値で上書きされました。

ちなみに hash メソッドを定義しなかった場合は

p key1.eql? key2 #=>true
p h1 #=>{#<HashKey:0x007fde91058a08 @name="hoge">=>"fuga", #<HashKey:0x007fde910589b8 @name="hoge">=>"piyo"}

となりました。

eql? メソッドは true になっているけど、hash の値が違うので同一のキーと判定されなかったみたいです。

参考にさせていただいたページ

http://d.hatena.ne.jp/shunsuk/20100412/1271073263

ありがとうございました。