我有一个关于在Python中搜索大型词典的效率的快速问题。我正在读取一个以逗号分隔的大文件,并从每一行获取一个键和值。如果我的密钥已经在字典中,我将值添加到字典中列出的值,如果字典中不存在该键,我只需添加该值。以前我用过这个:
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
这是无限快的,可以在3秒内读/写超过350,000行代码。
我的问题是为什么if key in data_dict.keys():
命令比调用try: data_dict[keyStr]
要长得多?为什么Python在字典中搜索密钥时不会使用try
语句?
答案 0 :(得分:30)
问题是,对于每个测试,您都会使用.keys()
生成新的密钥列表。随着密钥列表变长,所需时间也会增加。同样as noted by dckrooney,对密钥的搜索变为线性,而不是利用字典的哈希表结构。
替换为:
if key in data_dict:
答案 1 :(得分:7)
data_dict.keys()
会在字典中返回未排序的键列表。因此,每次检查给定键是否在字典中时,您都要在键列表中进行线性搜索(O(n)操作)。列表越长,搜索给定密钥所需的时间就越长。
将此与data_dict[keyStr]
对比。这执行散列查找,这是O(1)操作。它(直接)不依赖于字典中的键数;即使你添加了更多的键,检查给定键是否在字典中的时间也保持不变。
答案 2 :(得分:5)
您也可以使用
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
答案 3 :(得分:4)
这不回答问题,而是避免它。尝试使用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)
答案 4 :(得分:4)
正如其他几位人士所指出的,问题在于key in data_dict.keys()
使用从{{1}返回的无序 list
}方法(在Python 2.x中),它使linear time O(n) 进行搜索,这意味着运行时间随着字典的大小线性增加而且,随着尺寸的增加,生成密钥列表本身会花费更长时间。
另一方面,keys()
平均只需要恒定时间 O(1) 来执行搜索,无论字典大小如何因为在内部它会进行hash table查找。此外,这个哈希表已经存在,因为它是字典内部表示的一部分,因此不必在使用它之前生成。
Python不会自动执行此操作,因为key in data_dict
运算符只知道其两个操作数的类型,而不知道它们的来源,因此它无法自动优化第一种情况,它所看到的只是键和列表。
但是,在这种情况下,通过将数据存储在内置in
模块中的defaultdict
字典的专用版本中,可以完全避免搜索速度问题。以下是您使用过的代码的外观:
collections
如果from collections import defaultdict
input_data = defaultdict(float) # (guessing factory type)
...
data_dict[keyStr] = input_data[keyStr] + load_val
没有预先存在的条目,则会自动生成一个默认值(在此示例中为input_data[keyStr]
0.0
)。如您所见,代码更短,速度更快,无需任何float
测试或异常处理。
答案 5 :(得分:3)
这是因为data_dict.keys()
返回包含字典中键的列表(至少在Python 2.x中)。其中,为了查找列表中是否有密钥,需要进行线性搜索。
然而,尝试直接访问dict的元素会利用字典的强大属性,因此访问几乎是即时的。
答案 6 :(得分:2)
过去我们使用setdefault
:
data_dict.setdefault(keyStr, []).append(load_val)
答案 7 :(得分:1)
有一些类似于try函数的东西可以帮助你:
dict.get(key, default)
data_dict[keyStr] = data_dict.get(keyStr, '') + load_val
答案 8 :(得分:0)
作为额外的分析,我进行了一个简单的性能测试,以了解问题中提到的try / except方法与使用“ if key in data_dict”而不是“ if key in data_dict.keys()”的建议解决方案相比如何。 (我正在使用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提出的解决方案。