ブログ記事からの転載です。
元ネタ
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?
を考慮する必要がある