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
ありがとうございました。