Pythonでlarge辞書を検索する効率について簡単な質問がありました。大きなコンマ区切りファイルを読んで、各行からキーと値を取得しています。キーが既に辞書にある場合は、辞書にリストされている値に値を追加します。キーが辞書にない場合は、単に値を追加します。以前はこれを使用していました:
_if key in data_dict.keys():
add values
else:
data_dict[key] = value
_
これは非常に高速に開始されますが、辞書が大きくなるにつれて遅くなり、まったく使用できなくなります。辞書でキーを検索する方法を次のように変更しました。
_try:
# This will fail if key not present
data_dict[keyStr] = input_data[keyStr] + load_val
except:
data_dict[keyStr] = load_val
_
これは無限に高速で、350,000行を超えるコードを3秒で読み書きできます。
私の質問は、なぜif key in data_dict.keys():
コマンドが_try: data_dict[keyStr]
_の呼び出しよりもずっと長くかかるのかということでした。そして、なぜPython辞書でキーを検索するときにtry
ステートメントを利用しないのでしょうか?
問題は、すべてのテストで.keys()
を使用して新しいキーのリストを生成していることです。キーのリストが長くなると、必要な時間が長くなります。また、 dckrooneyで述べたように の場合、キーの検索は辞書のハッシュテーブル構造を利用する代わりに線形になります。
と置換する:
if key in data_dict:
data_dict.keys()
は、ディクショナリ内のキーの未ソートリストを返します。したがって、特定のキーが辞書にあるかどうかを確認するたびに、キーのリスト全体で線形検索を実行しています(O(n)操作)。リストが長いほど、特定のキーを検索するのに時間がかかる。
これとは対照的にdata_dict[keyStr]
。これはO(1)操作です)ハッシュルックアップを実行します。辞書内のキーの数に(直接)依存しません;キーをさらに追加しても、時間特定のキーがディクショナリにあるかどうかを確認するには、一定のままです。
単に使用することもできます
if key in data_dict:
の代わりに
if key in data_dict.keys():
前述のように、最初は直接ハッシュルックアップです-目的のオフセットは直接計算され、チェックされます-それはおおよそO(1)ですが、キーのチェックはO(n)である線形検索です。
In [258]: data_dict = dict([(x, x) for x in range(100000)])
In [259]: %timeit 999999 in data_dict.keys()
100 loops, best of 3: 3.47 ms per loop
In [260]: %timeit 999999 in data_dict
10000000 loops, best of 3: 49.3 ns per loop
他のいくつかの人が指摘したように、問題はkey in data_dict.keys()
が順不同list
を使用するという事実にありますkeys()
メソッドから(Python 2.x)で、 線形時間O(n)で検索します。つまり、実行時間は辞書のサイズに比例して増加し、さらにキーのリスト自体の生成にはサイズが大きくなるにつれて時間がかかります。 。
一方、key in data_dict
は一定の時間O(1)のみを必要とし、内部的に hash table ルックアップを行うため、辞書のサイズに関係なく検索します。さらに、このハッシュテーブルは辞書の内部表現の一部であるため既に存在しているため、使用する前に生成する必要はありません。
in
演算子はソースではなく2つのオペランドの型のみを知っているため、Pythonはこれを自動的に行いません。したがって、最初に表示されるのはキーとリストのみである場合は自動的に最適化できません。
ただし、この場合、組み込みのdefaultdict
モジュールにある collections
と呼ばれる辞書の専用バージョンにデータを保存することで、おそらく検索速度の問題を完全に回避できます。コードを使用した場合、コードは次のようになります。
from collections import defaultdict
input_data = defaultdict(float) # (guessing factory type)
...
data_dict[keyStr] = input_data[keyStr] + load_val
input_data[keyStr]
の既存のエントリがない場合、デフォルト値(この例ではfloat
の0.0
)で自動的に生成されます。ご覧のとおり、コードはより短く、おそらくより高速であり、すべてif
テストや例外処理を必要としません。
これは質問に答えませんが、むしろそれを避けます。 collections.defaultdict
を使用してみてください。 if/else
やtry/except
は必要ありません。
from collections import defaultdict
data_dict = defaultdict(list)
for keyStr, load_val in data:
data_dict[keyStr].append(load_val)
これは、data_dict.keys()
が辞書のキーを含むlistを返すためです(少なくともPython 2.x)で。キーがリストにあるかどうかを調べるには、線形検索が必要です。
一方、辞書の要素にアクセスしようとすると、辞書の素晴らしい特性を直接利用するため、アクセスはほとんど瞬時に行われます。
昔はsetdefault
を使用していました:
data_dict.setdefault(keyStr, []).append(load_val)
Try関数に似た何かが役立つはずです: dict.get(key, default)
data_dict[keyStr] = data_dict.get(keyStr, '') + load_val
追加の分析として、質問で言及されているtry/exceptメソッドが、「if key in data_dict.keys()」ではなく「if key in data_dict」() Python 3.7)を使用:
import timeit
k = '84782005' # this keys exists in the dictionary
def t1():
if k in data_dict:
pass
def t2():
if k in data_dict.keys():
pass
def t3():
try:
a = data_dict[k]
except:
pass
print(timeit.timeit(t1,number= 100000))
print(timeit.timeit(t2,number= 100000))
print(timeit.timeit(t3,number= 100000))
>> 0.01741484600097465
>> 0.025949209000827977
>> 0.017266065000512754
辞書にすでに存在するキーの場合、try/exceptおよび提供されたソリューションの検索時間は同じようです。ただし、キーが存在しない場合:
k = '8' # this keys does NOT exist in the dictionary
def t1():
if k in data_dict:
pass
def t2():
if k in data_dict.keys():
pass
def t3():
try:
a = data_dict[k]
except:
pass
print(timeit.timeit(t1,number= 100000))
print(timeit.timeit(t2,number= 100000))
print(timeit.timeit(t3,number= 100000))
>> 0.014406295998924179
>> 0.0236777299996902
>> 0.035819852999338764
例外は、 '。keys()'を使用するよりもはるかに時間がかかるようです!そこで、Markが提案した解決策を2番目に取り上げます。