リストは変更可能であるため、dict
キー(およびset
メンバー)はハッシュ可能である必要があります。ハッシュ値はインスタンス属性に基づいて計算する必要があるため、変更可能なオブジェクトのハッシュはお勧めできません。
この回答では、いくつかの具体的な例を挙げます。うまくいけば、既存の回答に加えて付加価値を加えます。すべての洞察は、データ構造の要素にもset
適用されます。
例1:可変オブジェクトのハッシュ。ハッシュ値はオブジェクトの可変特性に基づいています。
>>> class stupidlist(list):
... def __hash__(self):
... return len(self)
...
>>> stupid = stupidlist([1, 2, 3])
>>> d = {stupid: 0}
>>> stupid.append(4)
>>> stupid
[1, 2, 3, 4]
>>> d
{[1, 2, 3, 4]: 0}
>>> stupid in d
False
>>> stupid in d.keys()
False
>>> stupid in list(d.keys())
True
を変更した後stupid
、ハッシュが変更されたため、dictでそれを見つけることができなくなりました。dictのキーのリストに対する線形スキャンのみが見つけstupid
ます。
例2:...しかし、なぜ一定のハッシュ値ではないのですか?
>>> class stupidlist2(list):
... def __hash__(self):
... return id(self)
...
>>> stupidA = stupidlist2([1, 2, 3])
>>> stupidB = stupidlist2([1, 2, 3])
>>>
>>> stupidA == stupidB
True
>>> stupidA in {stupidB: 0}
False
dict
またはでそれらを見つけることができるように、等しいオブジェクトは同一にハッシュする必要があるため、これも良い考えではありませんset
。
例3:...わかりました。すべてのインスタンスで定数ハッシュはどうですか?!
>>> class stupidlist3(list):
... def __hash__(self):
... return 1
...
>>> stupidC = stupidlist3([1, 2, 3])
>>> stupidD = stupidlist3([1, 2, 3])
>>> stupidE = stupidlist3([1, 2, 3, 4])
>>>
>>> stupidC in {stupidD: 0}
True
>>> stupidC in {stupidE: 0}
False
>>> d = {stupidC: 0}
>>> stupidC.append(5)
>>> stupidC in d
True
物事は期待どおりに機能しているように見えますが、何が起こっているのかを考えてください。クラスのすべてのインスタンスが同じハッシュ値を生成すると、にキーとして、dict
またはに存在するインスタンスが2つを超えると、ハッシュの衝突が発生しますset
。
my_dict[key]
or key in my_dict
(またはitem in my_set
)を使用して適切なインスタンスを見つけるには、stupidlist3
(最悪の場合)辞書のキーにあるインスタンスと同じ数の等価性チェックを実行する必要があります。この時点で、辞書の目的(O(1)ルックアップ)は完全に無効になっています。これは、次のタイミングで示されます(IPythonで実行)。
例3のタイミング
>>> lists_list = [[i] for i in range(1000)]
>>> stupidlists_set = {stupidlist3([i]) for i in range(1000)}
>>> tuples_set = {(i,) for i in range(1000)}
>>> l = [999]
>>> s = stupidlist3([999])
>>> t = (999,)
>>>
>>> %timeit l in lists_list
25.5 µs ± 442 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit s in stupidlists_set
38.5 µs ± 61.2 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
>>> %timeit t in tuples_set
77.6 ns ± 1.5 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
ご覧のとおり、のメンバーシップテストstupidlists_set
は全体の線形スキャンよりもさらに低速lists_list
ですが、ハッシュの衝突をロードすることなく、予想される超高速ルックアップ時間(係数500)がセットにあります。
TL; DR:タプルは不変でハッシュ可能であるためtuple(yourlist)
、dict
キーとして使用できます。