python内部のハッシュ関数を理解しようとしています。すべてのインスタンスが同じハッシュ値を返すカスタムクラスを作成しました。
class C(object):
def __hash__(self):
return 42
上記のクラスのインスタンスはいつでも1つのセットにしか存在できないと想定しましたが、実際には、同じハッシュを持つ複数の要素をセットに含めることができます。
c, d = C(), C()
x = {c: 'c', d: 'd'}
print x
# {<__main__.C object at 0x83e98cc>:'c', <__main__.C object at 0x83e98ec>:'d'}
# note that the dict has 2 elements
もう少し実験して、クラスのすべてのインスタンスが等しくなるように__eq__
メソッドをオーバーライドすると、セットでは1つのインスタンスしか許可されないことがわかりました。
class D(C):
def __eq__(self, other):
return hash(self) == hash(other)
p, q = D(), D()
y = {p:'p', q:'q'}
print y
# {<__main__.D object at 0x8817acc>]: 'q'}
# note that the dict has only 1 element
だから私は辞書が同じハッシュを持つ複数の要素を持つことができる方法を知りたいです。ありがとう!
注:回答のすべての議論は辞書に関するものであるため、質問を編集して(セットではなく)辞書の例を示しました。ただし、セットにも同じことが当てはまります。セットは、同じハッシュ値を持つ複数の要素を持つこともできます。
Pythonのハッシュがどのように機能するかの詳細な説明については、 なぜ早期リターンが他より遅いのですか?
基本的には、ハッシュを使用してテーブル内のスロットを選択します。スロットに値があり、ハッシュが一致する場合、アイテムを比較して等しいかどうかを確認します。
ハッシュが一致しないか、アイテムが等しくない場合、別のスロットを試します。これを参照する式があり(参照する回答で説明します)、ハッシュ値の未使用部分を徐々に取り込みます。しかし、それらをすべて使用すると、最終的にはハッシュテーブル内のすべてのスロットを処理します。最終的に、一致するアイテムまたは空のスロットを見つけることが保証されます。検索で空のスロットが検出されると、値が挿入されるか放棄されます(値を追加するか取得するかによって異なります)。
注意すべき重要な点は、リストやバケットがないことです。特定のスロット数を持つハッシュテーブルがあり、各ハッシュは候補スロットのシーケンスを生成するために使用されます。
Python私が組み立てることができた辞書(おそらく誰もが知りたいと思うよりも多い;しかし、答えは包括的です)についてのすべてです。 ダンカン = Python dictsはスロットを使用し、このうさぎの穴に私を導きます。
O(1)
ルックアップを実行できます)。次の図は、pythonハッシュテーブルの論理表現です。次の図では、左側の0、1、...、i、...は、slotsハッシュテーブル(これらは単に説明のためのものであり、テーブルと一緒に保存されないことは明らかです!)。
_# Logical model of Python Hash table
-+-----------------+
0| <hash|key|value>|
-+-----------------+
1| ... |
-+-----------------+
.| ... |
-+-----------------+
i| ... |
-+-----------------+
.| ... |
-+-----------------+
n| ... |
-+-----------------+
_
新しい辞書が初期化されると、8slotsで始まります。 ( dictobject.h:49 を参照)
i
スロットから始めます。 CPythonは初期のi = hash(key) & mask
を使用します。ここで_mask = PyDictMINSIZE - 1
_ですが、それはそれほど重要ではありません)。チェックされる最初のスロットiは、キーのhashに依存することに注意してください。<hash|key|value>
_)。しかし、そのスロットが占有されている場合はどうでしょう!?ほとんどの場合、別のエントリが同じハッシュを持っているためです(ハッシュ衝突!)==
_比較ではなく挿入する現在のエントリのキーに対するスロット内のエントリのis
比較)( dictobject.c:337 、 44-345 )。 bothが一致する場合、エントリはすでに存在するとみなし、あきらめて、挿入する次のエントリに進みます。ハッシュまたはキーのいずれかが一致しない場合、probingが開始されます。行くぞ! Python dictの実装は、アイテムを挿入するときに2つのキーのハッシュ等価性とキーの通常の等価性(_==
_)の両方をチェックします。 a
とb
とhash(a)==hash(b)
ですが、_a!=b
_の場合、両方がPython dictに調和して存在できます。 hash(a)==hash(b)
and_a==b
_、両方を同じ辞書に入れることはできません。
ハッシュ衝突のたびにプローブする必要があるため、ハッシュ衝突が多すぎる場合の副作用の1つは、ルックアップと挿入が非常に遅くなることです(ダンカンが comments で指摘しているように)。
私の質問に対する簡単な答えは、「それがソースコードに実装されている方法だからです;)」
これは知っておくと良いですが(オタクのポイントですか?)、実際の生活でどのように使用できるかわかりません。明示的に何かを壊そうとしない限り、等しくない2つのオブジェクトが同じハッシュを持つのはなぜですか?
Edit:以下の答えはハッシュ衝突に対処するための可能な方法の1つですが、not方法Pythonそれを実行します。以下で参照するPythonのwikiも正しくありません。以下の@Duncanが提供する最適なソースは実装自体です: http:// svn .python.org/projects/python/trunk/Objects/dictobject.c 混乱して申し訳ありません。
ハッシュに要素のリスト(またはバケット)を保存し、そのリストで実際のキーが見つかるまでそのリストを反復処理します。写真には1000以上の言葉があります:
John Smith
とSandra Dee
の両方が152
にハッシュされています。バケット152
には両方が含まれています。 Sandra Dee
を検索すると、最初にバケット152
でリストを見つけ、次にSandra Dee
が見つかるまでそのリストをループして521-6955
を返します。
以下は間違っていますが、ここではコンテキストのみです:On Python's wiki you can find(pseudo?)code how Pythonはルックアップを実行します。
実際、この問題にはいくつかの解決策があります。ニースの概要については、ウィキペディアの記事をご覧ください。 http://en.wikipedia.org/wiki/Hash_table#Collision_resolution
ハッシュテーブルは、一般にハッシュの衝突を考慮しなければなりません!あなたは不運になり、2つのことが最終的に同じものにハッシュされます。その下には、同じハッシュキーを持つアイテムのリストにオブジェクトのセットがあります。通常、そのリストには1つしかありませんが、この場合、同じものにスタックし続けます。それらが異なることを知る唯一の方法は、等号演算子を使用することです。
これが発生すると、パフォーマンスは時間の経過とともに低下します。そのため、ハッシュ関数をできるだけ「ランダム」にしたいのです。
スレッドでは、キーとして辞書に入れたときにユーザー定義クラスのインスタンスでpythonが正確に何をするのかわかりませんでした。いくつかのドキュメントを読んでみましょう。ハッシュ可能はすべて不変の組み込みクラスおよびすべてのユーザー定義クラスです。
ユーザー定義クラスには、デフォルトで__cmp __()および__hash __()メソッドがあります。それらを使用して、すべてのオブジェクトは等しくない(それ自体を除く)を比較し、x .__ hash __()はid(x)から派生した結果を返します。
したがって、クラスに常に__hash__があり、__ cmp__または__eq__メソッドを提供していない場合、すべてのインスタンスは辞書に対して等しくありません。一方、__ cmp__メソッドまたは__eq__メソッドを提供するが、__ hash__を提供しない場合、インスタンスは辞書の観点からはまだ等しくありません。
class A(object):
def __hash__(self):
return 42
class B(object):
def __eq__(self, other):
return True
class C(A, B):
pass
dict_a = {A(): 1, A(): 2, A(): 3}
dict_b = {B(): 1, B(): 2, B(): 3}
dict_c = {C(): 1, C(): 2, C(): 3}
print(dict_a)
print(dict_b)
print(dict_c)
出力
{<__main__.A object at 0x7f9672f04850>: 1, <__main__.A object at 0x7f9672f04910>: 3, <__main__.A object at 0x7f9672f048d0>: 2}
{<__main__.B object at 0x7f9672f04990>: 2, <__main__.B object at 0x7f9672f04950>: 1, <__main__.B object at 0x7f9672f049d0>: 3}
{<__main__.C object at 0x7f9672f04a10>: 3}