Python 集与列表

Posted

技术标签:

【中文标题】Python 集与列表【英文标题】:Python Sets vs Lists 【发布时间】:2011-02-19 08:19:49 【问题描述】:

在 Python 中,哪种数据结构更高效/更快?假设顺序对我来说并不重要,并且无论如何我都会检查重复项,那么 Python 集是否比 Python 列表慢?

【问题讨论】:

【参考方案1】:

这取决于你打算用它做什么。

在确定对象是否存在于集合中时,集合要快得多(如 x in s),但在迭代其内容时比列表慢。

您可以使用timeit module 查看哪种方式更适合您的情况。

【讨论】:

对于您的观点:“集合明显更快”,使它更快的底层实现是什么? 脚本语言喜欢隐藏底层实现,但这种明显的简单性并不总是一件好事,在设计软件时确实需要一些“数据结构”意识。 Set 在迭代时并不比 list 慢很多。 集合和列表都有线性时间迭代。说一个比另一个“慢”是错误的,并且会使阅读此答案的新程序员感到困惑。 它们在迭代时都有一个 O(n) 的运行 time complexity,但迭代集的 average-case complexity 比迭代列表大(慢)~28%【参考方案2】:

当您只想迭代值时,列表比集合稍快。

然而,如果你想检查一个项目是否包含在其中,集合比列表要快得多。但它们只能包含独特的物品。

事实证明,元组的执行方式与列表几乎完全相同,除了它们的不变性。

迭代

>>> def iter_test(iterable):
...     for i in iterable:
...         pass
...
>>> from timeit import timeit
>>> timeit(
...     "iter_test(iterable)",
...     setup="from __main__ import iter_test; iterable = set(range(10000))",
...     number=100000)
12.666952133178711
>>> timeit(
...     "iter_test(iterable)",
...     setup="from __main__ import iter_test; iterable = list(range(10000))",
...     number=100000)
9.917098999023438
>>> timeit(
...     "iter_test(iterable)",
...     setup="from __main__ import iter_test; iterable = tuple(range(10000))",
...     number=100000)
9.865639209747314

确定对象是否存在

>>> def in_test(iterable):
...     for i in range(1000):
...         if i in iterable:
...             pass
...
>>> from timeit import timeit
>>> timeit(
...     "in_test(iterable)",
...     setup="from __main__ import in_test; iterable = set(range(1000))",
...     number=10000)
0.5591847896575928
>>> timeit(
...     "in_test(iterable)",
...     setup="from __main__ import in_test; iterable = list(range(1000))",
...     number=10000)
50.18339991569519
>>> timeit(
...     "in_test(iterable)",
...     setup="from __main__ import in_test; iterable = tuple(range(1000))",
...     number=10000)
51.597304821014404

【讨论】:

我发现 (Initializing set -> 5.5300979614257812) (Initializing list -> 1.8846848011016846) (Initializing tuple -> 1.8730108737945557) 大小为 10,000 GB 的项目在我的英特尔核心 i5 四核上。这也应该考虑在内。 我已经更新了代码以移除对象的创建。 timeit 循环的设置阶段仅调用一次 (docs.python.org/2/library/timeit.html#timeit.Timer.timeit)。【参考方案3】:

Set 因近乎即时的“包含”检查而获胜:https://en.wikipedia.org/wiki/Hash_table

列表实现:通常是一个数组,低级 close to the metal 适合迭代和按元素索引随机访问

Set 实现:https://en.wikipedia.org/wiki/Hash_table,它不会对列表进行迭代,而是通过从 key 计算 hash 来找到元素,所以取决于性质关键元素和散列函数。类似于用于 dict 的内容。我怀疑list 如果元素很少(set 执行包含检查的效果就越好。元素添加和删除也很快。还要始终牢记,构建一个系列是有成本的!

注意:如果list 已经排序,则在小列表中搜索list 可能会非常快,但如果数据更多,set 对包含检查的速度会更快。

【讨论】:

接近金属?这在 Python 的上下文中意味着什么?列表比集合更接近金属? @roganjosh,python 仍然在机器上运行,并且一些实现,如 list as 'array' 更接近硬件擅长的:***.com/questions/176011/…,但这总是取决于你想要实现什么,了解一些实现是很好的,而不仅仅是抽象。 "如果 list 已经排序,则在小列表中搜索 list 可能会非常快,但如果数据更多,set 对于包含检查会更快。"为避免混淆,您可能应该明确表示,只有在您利用 bisect 模块之类的排序顺序时,排序才会有所帮助;一个普通的inlist 的检查是O(n),无论它是否已排序,而inset 的检查是O(1)bisect 模块可以在预先排序的 list 上将测试降至 O(log n),但它比简单的 in 检查更复杂。【参考方案4】:

榜单表现:

>>> import timeit
>>> timeit.timeit(stmt='10**6 in a', setup='a = range(10**6)', number=100000)
0.008128150348026608

设置性能:

>>> timeit.timeit(stmt='10**6 in a', setup='a = set(range(10**6))', number=100000)
0.005674857488571661

您可能需要考虑 元组,因为它们类似于列表但无法修改。它们占用的内存略少,访问速度更快。它们没有列表那么灵活,但比列表更有效。它们的正常用途是用作字典键。

集合也是序列结构,但与列表和元组有两个不同。尽管集合确实有顺序,但该顺序是任意的,不受程序员的控制。第二个区别是集合中的元素必须是唯一的。

set 根据定义。 [python | wiki]。

>>> x = set([1, 1, 2, 2, 3, 3])
>>> x
1, 2, 3

【讨论】:

首先,您应该更新到 set 内置类型链接 (docs.python.org/2/library/stdtypes.html#set) 而不是已弃用的 sets 库。其次,“集合也是序列结构”,从内置类型链接中阅读以下内容:“作为无序集合,集合不记录元素位置或插入顺序。因此,集合不支持索引、切片或其他类似序列的行为。” range 不是listrange 是一个具有自定义__contains__ 魔术方法的特殊类。 @RyneWang 这是真的,但仅适用于 Python3。在 Python2 范围内返回一个普通列表(这就是为什么存在像 xrange 这样可怕的东西)【参考方案5】:

tl;博士

数据结构 (DS) 很重要,因为它们用于对数据执行操作,这基本上意味着:接受一些输入处理它,然后回馈输出

在某些特定情况下,某些数据结构比其他数据结构更有用。因此,问哪个(DS)更有效/更快是很不公平的。这就像问刀叉之间哪种工具更有效。我的意思是一切都取决于情况。

Lists

列表是可变序列通常用于存储同类项的集合

Sets

集合对象是不同的可散列对象的无序集合。它通常用于测试成员资格、从序列中删除重复项以及计算数学运算,例如交、并、差和对称差。

用法

从一些答案中可以清楚地看出,在迭代值时,列表比集合快得多。另一方面,在检查项目是否包含在其中时,集合比列表更快。因此,您唯一可以说的是,对于某些特定操作,列表优于集合,反之亦然。

【讨论】:

【参考方案6】:

当我用 CPython 检查一个值是否是少数文字之一时,我对结果很感兴趣。 set 在 Python 3 中胜出 vs tuplelistor

from timeit import timeit

def in_test1():
  for i in range(1000):
    if i in (314, 628):
      pass

def in_test2():
  for i in range(1000):
    if i in [314, 628]:
      pass

def in_test3():
  for i in range(1000):
    if i in 314, 628:
      pass

def in_test4():
  for i in range(1000):
    if i == 314 or i == 628:
      pass

print("tuple")
print(timeit("in_test1()", setup="from __main__ import in_test1", number=100000))
print("list")
print(timeit("in_test2()", setup="from __main__ import in_test2", number=100000))
print("set")
print(timeit("in_test3()", setup="from __main__ import in_test3", number=100000))
print("or")
print(timeit("in_test4()", setup="from __main__ import in_test4", number=100000))

输出:

tuple
4.735646052286029
list
4.7308746771886945
set
3.5755991376936436
or
4.687681658193469

对于 3 到 5 个字面量,set 仍然以较大优势获胜,or 成为最慢的。

在 Python 2 中,set 总是最慢的。 or 对于 2 到 3 个字面量是最快的,tuplelist 对于 4 个或更多字面量更快。我无法区分tuplelist 的速度。

当要测试的值被缓存在函数外的全局变量中时,而不是在循环中创建文字,set 每次都赢了,即使在 Python 2 中也是如此。

这些结果适用于 Core i7 上的 64 位 CPython。

【讨论】:

您的测试取决于此处的实现细节(并被他们弄乱了)。根据语言的自然规则,listset 案例需要在每次测试(这会破坏它们的性能)和旧 Python(肯定是 2.x,不确定是否旧 3.x省略了优化)它实际上在每次传递时都会重建 set 文字,使其变慢(Python 3 将其缓存为常量 frozenset 以避免工作)。在这两个版本上,您的 list 测试实际上正在优化为 tuple 常量,因此它与 tuple 情况相同。 @ShadowRanger 当然取决于实现细节;这就是基准测试的重点,以检查实现的性能。这是一个实际测试,有助于决定如何使用 CPython 编写这些类型的比较,我经常遇到这种情况。【参考方案7】:

Sets 更快,而且您可以通过 Sets 获得更多功能,例如假设您有两个 Sets:

set1 = "Harry Potter", "James Bond", "Iron Man"
set2 = "Captain America", "Black Widow", "Hulk", "Harry Potter", "James Bond"

我们可以轻松加入两组:

set3 = set1.union(set2)

找出两者的共同点:

set3 = set1.intersection(set2)

找出两者的不同之处:

set3 = set1.difference(set2)

还有更多!试试看,它们很有趣!此外,如果您必须处理 2 个列表中的不同值或 2 个列表中的公共值,我更喜欢将您的列表转换为集合,许多程序员都这样做。 希望对你有帮助:-)

【讨论】:

【参考方案8】:

我会推荐 Set 实现,其中用例仅限于引用或搜索存在,而 Tuple 实现则用例需要您执行迭代。列表是一种低级实现,需要大量内存开销。

【讨论】:

确实,正确区分何时使用 Sets 和何时使用 Tuple 确实至关重要。除非我正在编写较低级别的 API 脚本,否则我不会担心所涉及的内存开销和占用空间。【参考方案9】:
from datetime import datetime
listA = range(10000000)
setA = set(listA)
tupA = tuple(listA)
#Source Code

def calc(data, type):
start = datetime.now()
if data in type:
print ""
end = datetime.now()
print end-start

calc(9999, listA)
calc(9999, tupA)
calc(9999, setA)

比较所有 3 的 10 次迭代后的输出: Comparison

【讨论】:

【参考方案10】:

与@Ellis Percival's tests 一样,我想补充一点,在添加元素时,列表的执行方式与集合类似。

添加元素

>>> def add_test_set(iterable):
...     for i in range(10000):
...         iterable.add(i)
...
>>> def add_test_list(iterable):
...     for i in range(10000):
...         iterable.append(i)
...
>>> timeit("add_test_set(iterable)",
...     setup="from __main__ import add_test_set; iterable = set()",
...     number=10000)
7.073143866999999
>>> timeit("add_test_list(iterable)",
...     setup="from __main__ import add_test_list; iterable = list()",
...     number=10000)
6.80650725000001

(我会编辑他的帖子以包含此内容,但编辑队列已满)

【讨论】:

以上是关于Python 集与列表的主要内容,如果未能解决你的问题,请参考以下文章

大数据Scala学习—列表 集与映射

二数据集与数据类型R与统计

acm算法总结zon列表

Caffe︱构建lmdb数据集与各类文件路径名设置细解

Python内部函数集与非本地[重复]

redis学习