クラスを定義したとき、作成したインスタンス同士が等しいかどうかを判定するために==を定義することは多いかと思いますが、==のみを定義してもArray#uniq等の一部比較が意図したとおりに行われません。
失敗するバージョン
クラス定義
このクラスではunique_idが等しいかどうかを比較して同一性を決定するようにしています。
class Hoge
attr_accessor :unique_id
def initialize(uid)
@unique_id = uid
end
def ==(other)
@unique_id == other.unique_id
end
# 出力結果をわかりやすくするためにオーバーライド
def to_s
@unique_id
end
end
通常比較(成功する)
特に特別なことはありませんね。==を定義したことにより、hoge1とhoge4は同じものとみなされるようになりました。
hoge1 = Hoge.new('aaa')
hoge2 = Hoge.new('bbb')
hoge3 = Hoge.new('ccc')
hoge4 = Hoge.new('aaa')
hoge1 == hoge2
=> false
hoge1 == hoge3
=> false
hoge1 == hoge4
=> true
uniq/uniq!(失敗する)
hoge1とhoge4は同じunique_idなので、uniqしたら重複排除されるともーじゃん?
hoge1 = Hoge.new('aaa')
hoge2 = Hoge.new('bbb')
hoge3 = Hoge.new('ccc')
hoge4 = Hoge.new('aaa')
array = [hoge1, hoge2, hoge3, hoge4]
array.uniq!
puts array # 期待する結果はhoge1, hoge2, hoge3の3つ
------------------------------------------
# 出力: なんで4つあるんや!
# aaa
# bbb
# ccc
# aaa
------------------------------------------
されねーんです。
成功するバージョン
クラス定義
結論から言うと、==の他にeql?とhashメソッドもオーバーライドしないといけません。
hashメソッドについては@unique_idを元にハッシュ値を生成することで、同じ@unique_idなら必ず同じハッシュ値が生成されるようになります。
eql?メソッドについてはメソッドだけ用意して、中身は単にオーバーライドした==のエイリアスとして動作するようにします。
class Hoge
attr_accessor :unique_id
def initialize(uid)
@unique_id = uid
end
def hash
@unique_id.hash
end
def eql?(other)
self == other
end
def ==(other)
@unique_id == other.unique_id
end
def to_s
@unique_id
end
end
追記@scivolaさん、@jnchitoさん コメントありがとうございます。こういうことですね!
(uniqはインスタンスのハッシュ値が等しいかどうかでオブジェクトが等しいかどうかを判定しているのかな?でもそれなら==とhashだけオーバーライドすればいいと思うんだけど、eql?を定義しないとやっぱりうまくいかない。この辺詳しい人いたら教えて下さい。)
-
uniqの判定にはeql?が使用される
公式リファレンスマニュアル Array#uniq
要素の重複判定は、Object#eql? により行われます。
-
eql?をオーバーライドするときは、hashをセットでオーバーライドする必要がある
公式リファレンスマニュアル Object#eql?
このメソッドを再定義した時には Object#hash メソッ ドも再定義しなければなりません。
uniq/uniq!(成功する)
hoge1とhoge4は同じunique_idなので、uniqしたら重複排除されるともーじゃん?
hoge1 = Hoge.new('aaa')
hoge2 = Hoge.new('bbb')
hoge3 = Hoge.new('ccc')
hoge4 = Hoge.new('aaa')
array = [hoge1, hoge2, hoge3, hoge4]
array.uniq!
puts array # 期待する結果はhoge1, hoge2, hoge3の3つ
------------------------------------------
# 出力:
# aaa
# bbb
# ccc
------------------------------------------
されるやん!!!いけるやん!!!
というわけで、uniq/uniq!でハマった話でした。