ブログ記事からの転載です。
元ネタ
Rubyむずい…うむむむむむむむうむ
— 異常者 (@h1manoa) 2017年6月8日
[Wandbox]三へ( へ՞ਊ ՞)へ ハッハッ https://t.co/buooxLKDHW
と、いうことでちょっとやってみたら見事にハマったので覚書
元のコード
class Hoge
def initialize(num)
@num = num
end
def ==(other)
if @num == other
true
else
false
end
end
end
p [Hoge.new(1), Hoge.new(2), 1, 2, 5 ,6].uniq
# => [#<Hoge:0x000000024871f8 @num=1>, #<Hoge:0x000000024871d0 @num=2>, 1, 2, 5, 6]
元のコードはユーザ定義クラスと Integer が入り混じった配列をいい感じに #uniq したいという内容でした。
#uniq の条件
#uniq は要素の Object#eql? を使用して重複を判定します。
また、 #eql? を定義した場合は Object#hash も再定義する必要があります。
と、いうことでこれに沿って修正してみました。
class Hoge
def initialize(num)
@num = num
end
def hash
@num.hash
end
def eql?(other)
if @num == other
true
else
false
end
end
end
p [Hoge.new(1), Hoge.new(2), 1, 2, 5 ,6].uniq
# => [#<Hoge:0x000000024871f8 @num=1>, #<Hoge:0x000000024871d0 @num=2>, 1, 2, 5, 6]
しかし、これでもまだ意図した動作になりません。
#hash と #eql? は双方向で true になる必要がある
調べていたらわかったんですが
Hoge.new(1).eql? 1 # => true
だけではなくて
1.eql? Hoge.new(1).eql? # => true
のように双方向で true になる必要があるみたいです(当然 #hash に関しても同様
完成
と、言うことで Integer#eql? を拡張することで無事に意図する動作になりました。
class Hoge
def initialize(num)
@num = num
end
def hash
@num.hash
end
def eql?(other)
if @num == other
true
else
false
end
end
end
class Integer
def eql?(other)
return super(other) unless Hoge === other
other.eql? self
end
end
_1 = Hoge.new(1)
# この条件をすべて満たす必要がある
p _1.hash == 1.hash
p _1.eql? 1
p 1.hash == _1.hash
p 1.eql? _1
p [Hoge.new(1), Hoge.new(2), 1, 2, 5 ,6].uniq
# => [#<Hoge:0x000000024871f8 @num=1>, #<Hoge:0x000000024871d0 @num=2>, 5, 6]
流石に #uniq のためだけに Integer をクラス拡張するのはツラいですね…。
まとめ
-
#uniqは#eql?を参照して重複のチェックを行う -
#eql?を書き換えた場合は#hashも書き換える必要がある -
#uniqは双方向でチェックするので要素すべてのオブジェクトに対して#eql?を考慮する必要がある