__hash__()
を実装するための正しい方法は何ですか?
私はハッシュコードを返す関数について話しています。ハッシュコードは、オブジェクトをハッシュテーブル、つまり辞書に挿入するために使用されます。
__hash__()
は整数を返し、オブジェクトをハッシュテーブルに「ビニング」するために使用されるため、(衝突を最小限に抑えるために)返される整数の値は共通データに対して均一に分散される必要があると思います。そのような値を取得するための良い習慣は何ですか?衝突は問題ですか?私の場合、int、float、stringを保持するコンテナクラスとして機能する小さなクラスがあります。
__hash__()
を実装する簡単で正しい方法は、キータプルを使用することです。特殊なハッシュほど高速ではありませんが、必要な場合はおそらくCで型を実装する必要があります。
ハッシュと等価性にキーを使用する例を次に示します。
class A:
def __key(self):
return (self.attr_a, self.attr_b, self.attr_c)
def __hash__(self):
return hash(self.__key())
def __eq__(self, other):
if isinstance(other, A):
return self.__key() == other.__key()
return NotImplemented
また、 __hash__
には詳細情報があり、特定の状況で価値がある場合があります。
ジョンミリキンはこれに似たソリューションを提案しました。
_class A(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
def __eq__(self, othr):
return (isinstance(othr, type(self))
and (self._a, self._b, self._c) ==
(othr._a, othr._b, othr._c))
def __hash__(self):
return hash((self._a, self._b, self._c))
_
このソリューションの問題は、hash(A(a, b, c)) == hash((a, b, c))
です。言い換えれば、ハッシュはその主要メンバーのタプルのハッシュと衝突します。たぶん、これは実際にはそれほど重要ではないでしょうか?
___hash__
_に関するPythonドキュメント は、XORのようなものを使用してサブコンポーネントのハッシュを結合することを提案しています。
_class B(object):
def __init__(self, a, b, c):
self._a = a
self._b = b
self._c = c
def __eq__(self, othr):
return (isinstance(othr, type(self))
and (self._a, self._b, self._c) ==
(othr._a, othr._b, othr._c))
def __hash__(self):
return (hash(self._a) ^ hash(self._b) ^ hash(self._c) ^
hash((self._a, self._b, self._c)))
_
更新:Blckknghtが指摘しているように、a、b、cの順序を変更すると問題が発生する可能性があります。ハッシュされる値の順序をキャプチャするために、^ hash((self._a, self._b, self._c))
を追加しました。この最後の^ hash(...)
は、結合される値を再配置できない場合(たとえば、値のタイプが異なるため、__a
_の値が__b
_に割り当てられない場合、または__c
_など)。
Microsoft ResearchのPaul Larsonは、さまざまなハッシュ関数を研究しました。彼はこう言った
for c in some_string:
hash = 101 * hash + ord(c)
さまざまな弦に対して驚くほどうまく機能しました。同様の多項式手法が、異なるサブフィールドのハッシュを計算するのにうまく機能することがわかりました。
あなたの質問の2番目の部分に答えてみることができます。
衝突は、おそらくハッシュコード自体ではなく、ハッシュコードをコレクション内のインデックスにマッピングすることに起因します。たとえば、ハッシュ関数は1〜10000のランダムな値を返すことができますが、ハッシュテーブルに32エントリしかない場合、挿入時に衝突が発生します。
さらに、衝突はコレクションによって内部的に解決されると思いますが、衝突を解決する方法はたくさんあります。最も単純な(そして最悪の)場合、インデックスiに挿入するエントリが与えられ、空の場所が見つかるまでiに1を追加してそこに挿入します。その後、検索は同じように機能します。これにより、一部のエントリの検索が非効率的になります。検索するためにコレクション全体を走査する必要があるエントリがある可能性があるためです。
他の衝突解決方法は、項目を挿入して物を広げるときにハッシュテーブル内のエントリを移動することにより、取得時間を短縮します。これにより、挿入時間が長くなりますが、挿入するよりも多くを読むことを前提としています。エントリが特定の場所に集中するように、異なる衝突エントリを試行して分岐する方法もあります。
また、コレクションのサイズを変更する必要がある場合は、すべてを再ハッシュするか、動的なハッシュメソッドを使用する必要があります。
要するに、ハッシュコードを使用しているものによっては、独自の衝突解決メソッドを実装する必要がある場合があります。コレクションに保存していない場合は、非常に広い範囲のハッシュコードを生成するだけのハッシュ関数を使用して回避できます。その場合、メモリの問題に応じて、コンテナが必要以上に大きいことを確認できます(もちろん大きいほど良い)。
さらに興味がある場合は、次のリンクをご覧ください。
ウィキペディアには、さまざまな衝突解決方法の summary もあります。
また、Tharpによる「 File Organization And Processing 」は、衝突解決方法の多くを広範囲にカバーしています。 IMOは、ハッシュアルゴリズムの優れたリファレンスです。
返すハッシュ値のサイズに依存します。 4つの32ビット整数のハッシュに基づいて32ビット整数を返す必要がある場合は、衝突が発生するという単純なロジックです。
私はビット演算を好むでしょう。同様に、次のC擬似コード:
int a;
int b;
int c;
int d;
int hash = (a & 0xF000F000) | (b & 0x0F000F00) | (c & 0x00F000F0 | (d & 0x000F000F);
そのようなシステムは、実際に浮動小数点値を表すのではなく、単にビット値として使用する場合、フロートでも動作する可能性があります。
文字列については、私はほとんど/まったく考えがありません。