LoginSignup
1
0

More than 5 years have passed since last update.

Ruby で任意の異なるクラスのオブジェクトを #uniq するときの注意

Last updated at Posted at 2017-06-14

ブログ記事からの転載です。

元ネタ

と、いうことでちょっとやってみたら見事にハマったので覚書

元のコード

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? を考慮する必要がある
1
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0