高效的字典搜索?

时间:2013-09-30 21:02:58

标签: python search optimization dictionary

我有一个关于在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语句?

9 个答案:

答案 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/elsetry/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提出的解决方案。