【问题标题】:Efficient Dictionary Searching?高效的字典搜索?
【发布时间】:2013-10-06 21:08:31
【问题描述】:

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

这速度无限快,可以在 3 秒内读取/写入超过 350,000 行代码。

我的问题是为什么if key in data_dict.keys(): 命令比调用try: data_dict[keyStr] 花费的时间要长得多?为什么 Python 在字典中搜索键时不使用 try 语句?

【问题讨论】:

  • 一般来说,您不想捕获所有异常,而只想捕获您“期望”并在发现时处理的异常。在这里,例如,使用:except KeyError: ...
  • 您的示例代码令人困惑。在第一个 sn-p 中,您正在检查 key 是否在 data_dict 中,但在第二个中,唯一会给您一个 KeyError 异常的事情是如果 key 不在 @987654331 中@。这使得很难提供完整的答案......

标签: python search optimization dictionary


【解决方案1】:

问题在于,对于每个测试,您都会使用.keys() 生成一个新的键列表。随着密钥列表变长,所需时间也会增加。同样as noted by dckrooney,对键的搜索变为线性搜索,而不是利用字典的哈希表结构。

替换为:

if key in data_dict:

【讨论】:

  • 万岁,还添加了最合适的方法!
  • 啊,很好。我知道 .keys() 重新生成了键列表,所以我认为这就是问题所在,但我不知道你可以只执行“if key in dict:”。感谢您的帮助。
  • for every test you're generating a new list of keys with .keys() 所以每次都会调用key()函数?
  • @GrijeshChauhan,每个if 只调用一次,但我认为if 被调用了很多次。
  • @Brumdog22,是的,我复制了您的结果:ideone.com/imxdbe,正如我所料,因为keys() 生成了一个列表,而for 循环正在遍历该列表。然而,Python 3 中的情况发生了变化,keys() 返回一个生成器,它不允许您更改循环内的字典:ideone.com/wDfRLn
【解决方案2】:

data_dict.keys() 返回字典中键的未排序列表。因此,每次检查给定键是否在字典中时,您都在对键列表进行线性搜索(O(n) 操作)。列表越长,搜索给定键所需的时间就越长。

将此与data_dict[keyStr] 进行对比。这将执行哈希查找,这是一个 O(1) 操作。它不(直接)取决于字典中的键数;即使您添加更多键,检查给定键是否在字典中的时间也保持不变。

【讨论】:

    【解决方案3】:

    你也可以简单地使用

    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
    

    【讨论】:

      【解决方案4】:

      正如其他几个人所指出的,问题在于key in data_dict.keys() 使用从keys() 方法返回的无序list(在Python 2 中) .x),这需要linear time O(n) 来搜索,这意味着运行时间随着字典的大小线性增加,加上自己生成键列表随着尺寸的增加,需要的时间会越来越长。

      另一方面,key in data_dict 平均只需要恒定时间 O(1) 来执行搜索,而不管字典的大小,因为在内部它会进行hash table 查找。此外,这个哈希表已经存在,因为它是字典内部表示的一部分,因此在使用它之前不必生成。

      Python 不会自动执行此操作,因为 in 运算符只知道它的两个操作数的类型,而不知道它们的来源,因此它不能自动优化它所看到的只是键和列表的第一种情况。

      但是,在这种情况下,可以通过将数据存储在内置collections 模块中称为defaultdict 的字典的专用版本中来完全避免搜索速度问题。如果您使用了一个,您的代码可能如下所示:

      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)。如您所见,代码更短,而且很可能更快,这一切都不需要任何if 测试或异常处理。

      【讨论】:

        【解决方案5】:

        这并没有回答问题,而是避免了它。尝试使用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)
        

        【讨论】:

        • 或者 - data_dict[keyStr] = input_data.get(keyStr, [load_val])
        【解决方案6】:

        这是因为data_dict.keys() 返回一个包含字典中键的列表(至少在Python 2.x 中)。其中,为了查找某个键是否在列表中,需要进行线性搜索。

        然而,尝试直接访问字典的元素会利用字典的强大属性,因此访问几乎是即时的。

        【讨论】:

        • 在python3中data_dict.keys()返回迭代器。
        • @defuz 不知道,我还是主要使用 Python 2.7。更新答案,谢谢!
        【解决方案7】:

        过去我们使用setdefault

        data_dict.setdefault(keyStr, []).append(load_val)
        

        【讨论】:

          【解决方案8】:

          有一些类似于 try 函数的东西可以帮助你: dict.get(key, default)

          data_dict[keyStr] = data_dict.get(keyStr, '') + load_val
          

          【讨论】:

            【解决方案9】:

            作为额外的分析,我做了一个简单的性能测试,看看问题中提到的 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()' 需要更多的时间!所以,我赞同马克提出的解决方案。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2020-06-01
              • 1970-01-01
              • 2018-05-22
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多