【问题标题】:Python: List vs Dict for look up tablePython:用于查找表的列表与字典
【发布时间】:2010-10-05 13:26:46
【问题描述】:

我有大约 1000 万个值需要放入某种类型的查找表中,所以我想知道 listdict 哪个更有效?

我知道你可以为两者做这样的事情:

if something in dict_of_stuff:
    pass

if something in list_of_stuff:
    pass

我的想法是 dict 会更快更高效。

感谢您的帮助。

编辑 1
关于我正在尝试做的事情的更多信息。 Euler Problem 92。我正在制作一个查找表,以查看计算的值是否已全部计算完毕。

编辑 2
查找效率。

编辑 3
没有与该值相关联的值...那么 set 会更好吗?

【问题讨论】:

  • 效率在什么方面?插入?抬头?内存消耗?您是在检查价值的纯粹存在,还是有任何与之相关的元数据?
  • 附带说明一下,您不需要针对特定​​问题的 1000 万个列表或字典,而需要一个小得多的列表。

标签: python performance


【解决方案1】:

速度

列表中的查找是 O(n),字典中的查找是摊销 O(1),关于数据结构中的项目数。如果您不需要关联值,请使用集合。

内存

字典和集合都使用散列,它们使用的内存比仅用于对象存储要多得多。据 A.M.在 Beautiful Code 中,Kuchling 的实现试图保持 2/3 的散列满,所以你可能会浪费相当多的内存。

如果您不即时添加新条目(根据您更新的问题您会这样做),那么对列表进行排序并使用二分搜索可能是值得的。这是 O(log n),对于字符串可能会更慢,对于没有自然顺序的对象是不可能的。

【讨论】:

  • 是的,但如果内容永远不会改变,这是一次性操作。二分查找是 O(log n)。
  • @John Fouhy:整数不存储在哈希表中,只有指针,即你有 40M 的整数(嗯,当它们很多都很小时)和 60M 的哈希桌子。我同意现在这不是什么大问题,但仍然值得牢记。
  • 这是一个老问题,但我认为 amortized O(1) 可能不适用于非常大的集合/字典。根据wiki.python.org/moin/TimeComplexity,最坏的情况是 O(n)。我猜这取决于内部哈希实现在什么时候平均时间偏离 O(1) 并开始收敛于 O(n)。您可以通过基于一些易于辨别属性(如第一位数字的值,然后是第二位、第三位等)将全局集划分为更小的部分来提高查找性能,只要您需要获得最佳的集合大小)。
  • @TorstenMarek 这让我很困惑。从this page,列表查找是O(1),字典查找是O(n),这与你所说的相反。我是不是误会了?
  • @Aerovistae 我认为您误读了该页面上的信息。在列表下,我看到“x in s”(查找)的 O(n)。它还将 set 和 dict 查找显示为 O(1) 平均情况。
【解决方案2】:

dict 是一个哈希表,因此查找键非常快。所以在 dict 和 list 之间,dict 会更快。但是,如果您没有要关联的值,则使用集合会更好。它是一个哈希表,没有“表”部分。


编辑:对于您的新问题,是的,一组会更好。只需创建 2 个集合,一个用于以 1 结尾的序列,另一个用于以 89 结尾的序列。我已经使用集合成功解决了这个问题。

【讨论】:

    【解决方案3】:

    set() 正是您想要的。 O(1) 次查找,并且比字典小。

    【讨论】:

      【解决方案4】:

      我做了一些基准测试,结果发现 dict 比 list 和 set 对于大型数据集更快,在 linux 上的 i7 CPU 上运行 python 2.7.3:

      • python -mtimeit -s 'd=range(10**7)' '5*10**6 in d'

        10 次循环,最好的 3 次:每个循环 64.2 毫秒

      • python -mtimeit -s 'd=dict.fromkeys(range(10**7))' '5*10**6 in d'

        10000000 次循环,3 次中的最佳:每个循环 0.0759 微秒

      • python -mtimeit -s 'from sets import Set; d=Set(range(10**7))' '5*10**6 in d'

        1000000 次循环,3 次中的最佳:每个循环 0.262 微秒

      如您所见,dict 比 list 快得多,比 set 快大约 3 倍。不过,在某些应用程序中,您可能仍希望选择 set 以使其美观。如果数据集非常小(

      【讨论】:

      • ...但对我来说这里的问题是:这个时间实际测量的是什么?不是给定列表、字典或集合的访问时间,而是更多的时间和循环来创建列表、字典、集合并最终找到和访问一个值。那么,这和这个问题有关系吗? ...虽然很有趣...
      • @andzep,你错了,-s 选项是设置timeit 环境,即不计入总时间。 -s 选项只运行一次。在 Python 3.3 上,我得到以下结果:gen (range) -> 0.229 usec,list -> 157 ms,dict -> 0.0806 usec,set -> 0.0807 usec。 Set 和 dict 的性能是一样的。然而,Dict 的初始化时间比设置时间长(总时间 13.580s v. 11.803s)
      • 为什么不使用内置集?实际上,我使用 sets.Set() 得到的结果比使用内置 set() 更糟糕
      • @ThomasGuyot-Sionnest 内置集合是在 python 2.4 中引入的,所以我不确定为什么我没有在我提出的解决方案中使用它。我使用 Python 3.6.0(10000000 次循环,最好的 3:0.0608 次循环)在python -mtimeit -s "d=set(range(10**7))" "5*10**6 in d" 上获得了良好的性能,与 dict 基准测试大致相同,所以感谢您的评论。
      • 相当肯定范围产生一个范围对象..不是一个列表
      【解决方案5】:

      你想要一个字典。

      对于 Python 中的(未排序的)列表,“in”操作需要 O(n) 时间——当你有大量数据时不好。另一方面,dict 是一个哈希表,因此您可以预期 O(1) 查找时间。

      正如其他人所指出的,如果您只有键而不是键/值对,您可能会选择一个集合(一种特殊类型的 dict)。

      相关:

      • Python wiki:有关 Python 容器操作时间复杂度的信息。
      • SO:Python 容器操作时间和内存复杂度

      【讨论】:

      • 即使对于排序列表,“in”也是 O(n)。
      • 对于链表,是的——但是 Python 中的“列表”是大多数人所说的向量,它提供 O(1) 中的索引访问和 O(log n) 中的查找操作, 排序后。
      • 您是说应用于排序列表的in 运算符比应用于未排序列表(用于搜索随机值)时表现更好? (我不认为它们在内部是作为向量还是作为链表中的节点实现的。)
      【解决方案6】:

      作为一组新的测试表明@EriF89 经过这么多年仍然正确:

      $ python -m timeit -s "l={k:k for k in xrange(5000)}"    "[i for i in xrange(10000) if i in l]"
      1000 loops, best of 3: 1.84 msec per loop
      $ python -m timeit -s "l=[k for k in xrange(5000)]"    "[i for i in xrange(10000) if i in l]"
      10 loops, best of 3: 573 msec per loop
      $ python -m timeit -s "l=tuple([k for k in xrange(5000)])"    "[i for i in xrange(10000) if i in l]"
      10 loops, best of 3: 587 msec per loop
      $ python -m timeit -s "l=set([k for k in xrange(5000)])"    "[i for i in xrange(10000) if i in l]"
      1000 loops, best of 3: 1.88 msec per loop
      

      这里我们还比较了tuple,它在某些用例中比lists 更快(并且使用更少的内存)。在查找表的情况下,tuple 没有更好的表现。

      dictset 的表现都非常好。这带来了一个有趣的点,与@SilentGhost 关于唯一性的答案相关:如果 OP 在数据集中有 10M 值,并且不知道其中是否存在重复项,那么值得保持其元素的集合/字典并行使用实际数据集,并测试该集/字典中的存在。 10M 数据点可能只有 10 个唯一值,这是一个更小的搜索空间!

      SilentGhost 关于 dicts 的错误实际上是有启发性的,因为可以使用 dict 将重复数据(值中)关联到非重复集(键)中,从而保留一个数据对象来保存所有数据,但仍然可以快速查找桌子。例如,一个 dict 键可以是要查找的值,而该值可以是该值出现的虚构列表中的索引列表。

      例如,如果要搜索的源数据列表是l=[1,2,3,1,2,1,4],则可以通过将其替换为以下dict来优化搜索和内存:

      >>> from collections import defaultdict
      >>> d = defaultdict(list)
      >>> l=[1,2,3,1,2,1,4]
      >>> for i, e in enumerate(l):
      ...     d[e].append(i)
      >>> d
      defaultdict(<class 'list'>, {1: [0, 3, 5], 2: [1, 4], 3: [2], 4: [6]})
      

      有了这个字典,可以知道:

      1. 如果一个值在原始数据集中(即2 in d 返回True
      2. 在哪里该值在原始数据集中(即d[2] 返回在原始数据列表中找到数据的索引列表:[1, 4]

      【讨论】:

      • 对于你的最后一段,虽然阅读它很有意义,但如果你能看到你试图解释的实际代码会很好(而且可能更容易掌握)。
      【解决方案7】:

      如果数据是唯一的,set() 将是最有效的,但是是两个字典(这也需要唯一性,哎呀 :)

      【讨论】:

      • 当我看到我发布的答案时我意识到 %)
      • @SilentGhost 如果答案是错误的,为什么不删除它呢?对赞成票来说太糟糕了,但这确实发生了(嗯,发生了
      【解决方案8】:

      您实际上并不需要在表中存储 1000 万个值,因此这不是什么大问题。

      提示:想想在第一次平方和运算之后你的结果可以有多大。最大可能的结果将远小于 1000 万...

      【讨论】:

        猜你喜欢
        • 2014-07-21
        • 1970-01-01
        • 2021-12-14
        • 2017-11-13
        • 1970-01-01
        • 2021-02-27
        • 2013-05-09
        • 2014-03-14
        • 1970-01-01
        相关资源
        最近更新 更多