Python:查找表的列表与字典


169

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

我知道您可以为这两种方法执行以下操作:

if something in dict_of_stuff:
    pass

if something in list_of_stuff:
    pass

我的想法是,该字典将更快,更高效。

谢谢你的帮助。

编辑1
我正在尝试做的更多信息。 欧拉问题92。我正在查找表,以查看所计算的值是否已全部准备好。

编辑2
查找效率。

编辑3
没有与值相关的值...那么集合会更好吗?


1
效率方面呢?插?抬头?内存消耗?您是在检查价值的纯粹存在,还是有任何与之相关的元数据?
truppo

附带说明一下,对于该特定问题,您不需要一千万个列表或词典,而只需要一个小得多的列表或词典。
sfotiadis 2014年

Answers:


222

速度

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

记忆

字典和集合都使用哈希,并且它们使用的内存比仅用于对象存储的更多。根据Beautiful Code的 AM Kuchling的说法,该实现尝试使哈希2/3保持完整,因此您可能会浪费一些内存。

如果您不立即添加新条目(根据更新的问题进行操作),则可能需要对列表进行排序并使用二进制搜索。这是O(log n),对于字符串来说可能更慢,对于没有自然顺序的对象则不可能。


6
是的,但是如果内容永不更改,这是一次性的操作。二进制搜索为O(log n)。
Torsten Marek

1
@John Fouhy:整数不存储在哈希表中,只有指针,即,您有40M的整数(好吧,当它们很多时,不是真的)和60M的哈希表。我同意,如今的问题已经不那么重要了,仍然值得牢记。
Torsten Marek

2
这是一个古老的问题,但是我认为分摊O(1)可能不适用于非常大的集合/格。根据wiki.python.org/moin/TimeComplexity的最坏情况是O(n)。我猜这取决于内部哈希实现,平均时间在哪一点上偏离O(1)并开始收敛于O(n)。您可以通过根据一些易于辨别的属性(例如,只要需要获得最佳集合大小的第一个数字的值,然后是第二个,第三个数字的值)将全局集合划分为较小的部分来帮助提高查找性能。。
Nisan.H 2012年

3
@TorstenMarek这让我感到困惑。在此页面上,列表查找为O(1),字典查找为O(n),这与您所说的相反。我误会了吗
临时用户名

3
@Aerovistae我认为您误读了该页面上的信息。在列表下,我看到“ x in s”的O(n)(查找)。它还将设置和字典查找显示为O(1)平均情况。
丹尼斯2014年

45

dict是哈希表,因此查找密钥确实非常快。因此,在字典和列表之间,字典会更快。但是,如果您没有关联的值,则最好使用集合。它是一个散列表,没有“表”部分。


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



31

我做了一些基准测试,结果表明dict比列出和设置大型数据集都快,它在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倍。但是,在某些应用程序中,您可能仍想选择设置以美观。如果数据集非常小(<1000个元素),则列表的效果会很好。


难道不是完全相反吗?列表:10 * 64.2 * 1000 = 642000 usec,dict:10000000 * 0.0759 = 759000 usec并设置:1000000 * 0.262 = 262000 usec ... 还是我错过了什么?
andzep

1
...但是这里的问题是:这段时间实际上在衡量什么?不是给定列表,字典或集合的访问时间,而是创建列表,字典,集合以及最终查找和访问一个值的时间和循环。那么,这是否与问题有关?……虽然很有趣……
andzep 2012年

8
@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的性能相同。但是,初始化字典所需的时间比设置的时间长(总时间13.580s v。11.803s)
sleblanc 2013年

1
为什么不使用内置集?实际上,与内置set()相比,set.Set()的结果要差得多
Thomas Guyot-Sionnest

2
@ ThomasGuyot-Sionnest内置集合是在python 2.4中引入的,因此我不确定为什么我没有在建议的解决方案中使用它。python -mtimeit -s "d=set(range(10**7))" "5*10**6 in d"使用Python 3.6.0(10000000循环,最好3:每个循环0.0608微秒),我获得了良好的性能,与dict基准大致相同,因此感谢您的评论。
EriF89

6

你想要一个字典。

对于Python中的(未排序)列表,“输入”操作需要O(n)时间-如果您有大量数据,则不好。另一方面,字典是哈希表,因此您可以期望O(1)查找时间。

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

有关:

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

1
即使对于已排序的列表,“ in”也为O(n)。

2
对于链表,是的---但是Python中的“列表”是大多数人所说的向量,向量在排序时在O(1)中提供索引访问,并在O(log n)中提供查找操作。
zweiterlinde,2009年

您是说in应用于排序列表的运算符比应用于未排序列表(用于搜索随机值)的性能更好吗?(我认为它们是否在内部实现为矢量或作为链表中的节点都没有关系。)
martineau 2010年

4

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


我意识到我看到自己的答案发布%)
SilentGhost

2
@SilentGhost如果答案错误,为什么不删除它?太糟糕了,不能接受投票,但是那件事发生了(很好,发生了
让·弗朗索瓦·法布尔

3

这些年来,作为一组新的测试表明@ 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

在这里,我们还比较了一个tuplelists在某些用例中,它比(并且使用更少的内存)更快。对于查找表,tuple整流罩没有更好的选择。

无论是dictset表现非常出色。这带来了一个与@SilentGhost有关唯一性的答案有关的有趣观点:如果OP在数据集中具有10M值,并且不知道它们中是否存在重复项,则值得将其元素的集合/ dict并行保存使用实际数据集,并测试该数据集中是否存在该数据。10M数据点可能只有10个唯一值,这是一个很小的搜索空间!

SilentGhost关于dict的错误实际上是有启发性的,因为人们可以使用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]

对于您的最后一段,虽然读起来很有意义,但是很高兴(而且可能更容易理解)来查看您试图解释的实际代码。
kaiser

0

实际上,您实际上不需要在表中存储1000万个值,因此这两种方法都没什么大不了的。

提示:考虑一下在第一次平方和运算后结果可能有多大。最大可能的结果将是小于1000万。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.