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
模块之类的排序顺序时,排序才会有所帮助;一个普通的in
对list
的检查是O(n)
,无论它是否已排序,而in
对set
的检查是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
不是list
。 range
是一个具有自定义__contains__
魔术方法的特殊类。
@RyneWang 这是真的,但仅适用于 Python3。在 Python2 范围内返回一个普通列表(这就是为什么存在像 xrange
这样可怕的东西)【参考方案5】:
tl;博士
数据结构 (DS) 很重要,因为它们用于对数据执行操作,这基本上意味着:接受一些输入,处理它,然后回馈输出。
在某些特定情况下,某些数据结构比其他数据结构更有用。因此,问哪个(DS)更有效/更快是很不公平的。这就像问刀叉之间哪种工具更有效。我的意思是一切都取决于情况。
Lists
列表是可变序列,通常用于存储同类项的集合。
Sets
集合对象是不同的可散列对象的无序集合。它通常用于测试成员资格、从序列中删除重复项以及计算数学运算,例如交、并、差和对称差。
用法
从一些答案中可以清楚地看出,在迭代值时,列表比集合快得多。另一方面,在检查项目是否包含在其中时,集合比列表更快。因此,您唯一可以说的是,对于某些特定操作,列表优于集合,反之亦然。
【讨论】:
【参考方案6】:当我用 CPython 检查一个值是否是少数文字之一时,我对结果很感兴趣。 set
在 Python 3 中胜出 vs tuple
、list
和 or
:
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 个字面量是最快的,tuple
和 list
对于 4 个或更多字面量更快。我无法区分tuple
和list
的速度。
当要测试的值被缓存在函数外的全局变量中时,而不是在循环中创建文字,set
每次都赢了,即使在 Python 2 中也是如此。
这些结果适用于 Core i7 上的 64 位 CPython。
【讨论】:
您的测试取决于此处的实现细节(并被他们弄乱了)。根据语言的自然规则,list
和 set
案例需要在每次测试(这会破坏它们的性能)和旧 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 集与列表的主要内容,如果未能解决你的问题,请参考以下文章