Techioz Blog

ハッシュ内のオブジェクトが同一でないことを保証する方法

概要

Ruby のドキュメントには、Book クラスに 3 番目の属性を追加して以下に変更したハッシュの使用方法の例と、以下の 3 番目の書籍レビューが含まれています。元のコードはここで見ることができます。

元のコードでクラスに 2 つの属性がある場合、ブック クラスのハッシュ メソッドは 2 つのインスタンス値の間に ^ を使用しました。ハッシュ メソッドを変更せずにクラスに 3 番目の属性を追加しました。この非常に限定されたデータ セットでは、同一のオブジェクトをハッシュに追加しないという点でコードは引き続き機能します。

そこで私の質問は、オブジェクトに 3 番目の属性がある場合、同一のオブジェクトがハッシュに追加されないことを保証するために、ハッシュ メソッドを変更する必要があるかということです。もしそうなら、どのようにして? (注: ^ について読んでも、このハッシュ メソッドでどのように機能するのかはよくわかりません)

class Book
    attr_reader :author, :title, :year
  
    def initialize(author, title, year)
      @author = author
      @title = title
      @year = year
    end
  
    def ==(other)
      self.class === other &&
        other.author == @author &&
        other.title == @title &&
        other.year == @year
    end
  
    alias eql? ==
  
    def hash
      @author.hash ^ @title.hash # XOR
    end
end
      
book1 = Book.new 'matz', 'Ruby in a Nutshell', 1987
book2 = Book.new 'matz', 'Ruby in a Nutshell', 1987
book3 = Book.new 'matz', 'Ruby in a Nutshell', 2015  # added by me
      
reviews = {
    book1 => 'Great reference!',
    book2 => 'Nice and compact!',
    book3 => 'Holy Moly, my additional review',
}
       
puts reviews.length #=> 2

解決策

まさに、たとえば次のようにハッシュ メソッドに年も追加する必要があります。

def hash
  @author.hash ^ @title.hash ^ @year.hash
end

または、Array#hash を使用して次のようにします。

def hash
  [@author, @title, @year].hash
end

クラスのハッシュをどのように計算するかはそれほど重要ではないことに注意してください。同じ入力に対して常に同じハッシュを計算することが重要です。また、ハッシュ衝突の可能性が非常に低いことを確認する必要があります。