【问题标题】:search efficiently in a dictionary在字典中高效搜索
【发布时间】:2016-06-16 04:22:00
【问题描述】:

我有一个很长的表单字符串字典,如

{'a':'b' , 'c':'d'}

我想有效地搜索一个键,这样我就可以从字典中得到相应的答案,但我想要一个比仅仅遍历整个字典更好的搜索。有没有更好的方法,比如存储在一个集合中,这样我就可以有效地搜索。我查看了集合,但我只能找到存储单个字符串而不是字典元素的方法。

【问题讨论】:

  • iterating over 是什么字典?从什么时候开始它不是一种有效的方法?
  • 你不需要“搜索”一个键,你只是通过它访问字典。它甚至在教程中。
  • 通过迭代我的意思是像运行一个循环。 for i in range(0,len(dict))
  • Python 集合 建立在字典之上,因为字典具有快速查找功能,并且不能有重复的键 - 所以集合对您没有帮助。 for key in mydict.keys(): print key 将遍历键而不是键和值。但是你是什么意思'搜索',你在做什么?这听起来有点像这项工作的错误数据结构。
  • set 和 dict 的时间复杂度差不多。平均而言,它是 O(1),但在最坏的情况下是 O(n)。这一切都取决于用于查找“键”插槽的散列函数的质量和时间复杂度。如果散列函数很慢,那么应该是“快速通道”的字符串没问题,当条目产生许多冲突(散列函数的质量)时,你将越来越接近 O(n) 机制。这在插入时也会很明显。您是否已经测量过任何有问题的插入/检索时间?大小 n 是 10^3, 10^4, 10^5, ... ?

标签: python list set


【解决方案1】:

如果您有字典 d 并想测试密钥 k 的成员资格,则可以使用 k in dk not in d。例如:

>>> d = {'a':'b' , 'c':'d'}
>>> 'a' in d
True
>>> 'x' in d
False
>>> 'a' not in d
False
>>> 'x' not in d
True
>>>

这些检查应该非常有效,因为字典(和集合)是使用哈希表实现的。

【讨论】:

  • 但是搜索效率高还是遍历整个列表?
  • 效率很高。这就是字典的全部意义所在。
【解决方案2】:

How are Python's Built In Dictionaries Implemented 上已经有一个有趣的任务...

但为什么不在这里尝试测量(在阅读了有关 python 实现中的时间复杂性之后):

#! /usr/bin/env python
from __future__ import division, print_function

import dis  # for disassembling (bottom)
import string  # string.printable as sample character source
import timeit


chars = tuple(string.printable)
cartesian = tuple(a + b for a in chars for b in chars)
assert 10000 == len(cartesian), print(len(cartesian))
d = dict((a + b, b) for a in cartesian for b in chars)

assert 1000000 == len(d), print(len(d))
assert d['zzz'] == 'z'

setup = """
import string
chars = tuple(string.printable)
d = dict((a + b, b) for a in chars for b in chars)
"""

assert 1000000 / 10000 == 100
setup_100x = """
import string
chars = tuple(string.printable)
cartesian = tuple(a + b for a in chars for b in chars)
d = dict((a + b, b) for a in cartesian for b in chars)
"""

stmt = """
'zzz' in d
"""


t = timeit.timeit(stmt=stmt, setup=setup, timer=timeit.default_timer,
                  number=timeit.default_number)

print("# Timing[secs] for 1x10000:", t)

t_100x = timeit.timeit(stmt=stmt, setup=setup_100x, timer=timeit.default_timer,
                       number=timeit.default_number)

print("# Timing[secs] for 100x10000:", t_100x)

disassemble_me = "'zzz' in {'a': 'b'}"
print("# Disassembly(lookup in dict with 1 string entry):")
print("#", disassemble_me)
dis.dis(disassemble_me)

disassemble_me = "'zzz' in {'a': 'b', 'c': 'd'}"
print("# Disassembly(lookup in dict with 2 string entries):")
print("#", disassemble_me)
dis.dis(disassemble_me)

disassemble_me = "'zzz' in {'a': 'b', 'c': 'd', 'e': 'f'}"
print("# Disassembly(lookup in dict with 3 string entries):")
print("#", disassemble_me)
dis.dis(disassemble_me)

在我使用 Python 2.7.11 的机器上,这给出了:

# Timing[secs] for 1x10000: 0.0406861305237
# Timing[secs] for 100x10000: 0.0472030639648
# Disassembly(lookup in dict with 1 string entry):
# 'zzz' in {'a': 'b'}
        0 <39>           
        1 SETUP_FINALLY   31354 (to 31358)
        4 <39>           
        5 SLICE+2        
        6 BUILD_MAP        8302
        9 <123>           24871
       12 <39>           
       13 INPLACE_DIVIDE 
       14 SLICE+2        
       15 <39>           
       16 DELETE_GLOBAL   32039 (32039)
# Disassembly(lookup in dict with 2 string entries):
# 'zzz' in {'a': 'b', 'c': 'd'}
        0 <39>           
        1 SETUP_FINALLY   31354 (to 31358)
        4 <39>           
        5 SLICE+2        
        6 BUILD_MAP        8302
        9 <123>           24871
       12 <39>           
       13 INPLACE_DIVIDE 
       14 SLICE+2        
       15 <39>           
       16 DELETE_GLOBAL   11303 (11303)
       19 SLICE+2        
       20 <39>           
       21 DUP_TOPX        14887
       24 SLICE+2        
       25 <39>           
       26 LOAD_CONST      32039 (32039)
# Disassembly(lookup in dict with 3 string entries):
# 'zzz' in {'a': 'b', 'c': 'd', 'e': 'f'}
        0 <39>           
        1 SETUP_FINALLY   31354 (to 31358)
        4 <39>           
        5 SLICE+2        
        6 BUILD_MAP        8302
        9 <123>           24871
       12 <39>           
       13 INPLACE_DIVIDE 
       14 SLICE+2        
       15 <39>           
       16 DELETE_GLOBAL   11303 (11303)
       19 SLICE+2        
       20 <39>           
       21 DUP_TOPX        14887
       24 SLICE+2        
       25 <39>           
       26 LOAD_CONST      11303 (11303)
       29 SLICE+2        
       30 <39>           
       31 LOAD_NAME       14887 (14887)
       34 SLICE+2        
       35 <39>           
       36 BUILD_TUPLE     32039

所以 10000 个条目在大约 10^4 个条目字典中查找“zz”。平均 40 毫秒(timeit.default_number == 1000000),低于 50 毫秒,100 倍,即 10^6 个条目('zzz'lookup)。

# Timing[secs] for 1x10000: 0.0406861305237
# Timing[secs] for 100x10000: 0.0472030639648

测量意味着可重复性 :-) 因此再次运行它:

# Timing[secs] for 1x10000: 0.0441079139709
# Timing[secs] for 100x10000: 0.0460820198059

它只是解决了(这里没有显示其他运行围绕具有这些键类型和长度关系的字典(较大字典的键也更长!)这里存在 no 线性最坏情况实现了。对于 100 倍大的 dict(qua 条目计数)和 50% 大的密钥长度,运行时间更像是 10%。

看起来不错。建议在有疑问时始终测量和拆卸。 HTH。

PS:OP 可能仍希望在未来的问题中提供更多代码上下文,因为最好在知道如何使用数据结构的情况下选择它;-)

PPS:Raymond Hettinger 等人。经常用“热爱细节”来优化 CPython 实现(对不起,我没有更好的英文表达),所以总是期望针对小“尺寸”问题的特定展开实现,这就是玩具变体问题的反汇编可能有很大差异的原因从一开始,就实现了大型任务。这就是为什么我更喜欢 timeit 和(配置文件测量)而不是反汇编,但我们应该习惯于读取字节码,以便在测量的性能无法满足我们的期望时获得想法。

否则:享受查找的反汇编:-)

更新:...如果您更改声明时间,即小 dict 收到命中 'zz' 而大 dict 没有(反之亦然),您也可能遇到这些时间:

# Timing[secs] for 1x10000: 0.0533709526062
# Timing[secs] for 100x10000: 0.0458760261536

'zz' in d 的测试需要 53 毫秒(小)和 46 毫秒(大)(平均 1000000 次试验)。

【讨论】:

    【解决方案3】:

    您可以使用str.translate 函数使用表(在本例中为dict)将字符串的字符从键替换为值。

    【讨论】:

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