リストは変更可能であるため、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
またはに存在するキーとして3つ以上のインスタンスがある場合は常に、ハッシュの衝突が発生しますset
。
my_dict[key]
またはkey in my_dict
(または)を使用して適切なインスタンスを見つけるには、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