クラスを定義したとき、作成したインスタンス同士が等しいかどうかを判定するために==
を定義することは多いかと思いますが、==
のみを定義しても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!でハマった話でした。