len()关于集合和列表的复杂性

时间:2015-08-27 12:03:43

标签: python python-3.x time-complexity python-internals

len()关于集合和列表的复杂性同样为O(1)。为什么需要更多的时间来处理集合?

~$ python -m timeit "a=[1,2,3,4,5,6,7,8,9,10];len(a)"
10000000 loops, best of 3: 0.168 usec per loop
~$ python -m timeit "a={1,2,3,4,5,6,7,8,9,10};len(a)"
1000000 loops, best of 3: 0.375 usec per loop

它是否与特定基准相关,因为它需要更多时间来构建集合而不是列表,基准也考虑到了这一点?

如果创建一个set对象比创建一个列表需要更多时间,那么底层原因是什么?

6 个答案:

答案 0 :(得分:110)

首先,您还没有测量len()的速度,您已经测量了创建列表/集的速度以及 {{1>}的速度1}}。

使用len()的{​​{1}}参数:

--setup

您传递给timeit的语句会在衡量$ python -m timeit --setup "a=[1,2,3,4,5,6,7,8,9,10]" "len(a)" 10000000 loops, best of 3: 0.0369 usec per loop $ python -m timeit --setup "a={1,2,3,4,5,6,7,8,9,10}" "len(a)" 10000000 loops, best of 3: 0.0372 usec per loop 的速度之前运行。

其次,您应该注意--setup是一个非常快速的陈述。测量其速度的过程可能受到“噪音”的影响。请注意the code executed (and measured) by timeit等同于以下内容:

len()

由于len(a)for i in itertools.repeat(None, number): len(a) 都是快速操作且速度可能相似,因此len(a)的速度可能会影响时间。

出于这个原因,你最好测量itertools.repeat(...).__next__()(重复100次左右),以便for循环的主体比迭代器花费更多的时间:

itertools.repeat(...).__next__()

(结果仍然表示len(a); len(a); ...; len(a)在列表和集合上具有相同的表现,但现在您确定结果是正确的。)

第三,确实“复杂性”和“速度”是相关的,但我相信你会产生一些困惑。 $ python -m timeit --setup "a=[1,2,3,4,5,6,7,8,9,10]" "$(for i in {0..1000}; do echo "len(a)"; done)" 10000 loops, best of 3: 29.2 usec per loop $ python -m timeit --setup "a={1,2,3,4,5,6,7,8,9,10}" "$(for i in {0..1000}; do echo "len(a)"; done)" 10000 loops, best of 3: 29.3 usec per loop 列表和集合的 O(1)复杂性这一事实并不意味着它必须以相同的速度在列表和集合上运行。

这意味着,平均而言,无论列表len()有多长,len()都会执行相同的渐近步数。无论集合a有多长,len(a)执行相同的渐近步数。但是用于计算列表和集合大小的算法可能会有所不同,从而产生不同的性能(时间表明情况并非如此,但这可能是一种可能性)。

<强>最后,

  

如果创建一个set对象比创建一个列表需要更多时间,那么底层原因是什么?

如您所知,一组不允许重复元素。 CPython中的集合实现为哈希表(以确保平均 O(1)插入和查找):构建和维护哈希表要比向列表中添加元素复杂得多。

具体来说,在构造集合时,您必须计算哈希值,构建哈希表,查找以避免插入重复事件等。相比之下,CPython中的列表实现为一个简单的指针数组,根据需要blen(b)

答案 1 :(得分:20)

相关行是http://svn.python.org/view/python/trunk/Objects/setobject.c?view=markup#l640

640     static Py_ssize_t
641     set_len(PyObject *so)
642     {
643         return ((PySetObject *)so)->used;
644     }

http://svn.python.org/view/python/trunk/Objects/listobject.c?view=markup#l431

431     static Py_ssize_t
432     list_length(PyListObject *a)
433     {
434         return Py_SIZE(a);
435     }

两者都只是静态查找。

那么你可能会有什么不同。您也可以测量对象的创建。创建一个集合比列表要花费更多时间。

答案 2 :(得分:6)

将此标记与-s标记结合使用,而不用考虑第一个字符串:

~$ python -mtimeit -s "a=range(1000);" "len(a)"
10000000 loops, best of 3: 0.0424 usec per loop
                           ↑ 
~$ python -mtimeit -s "a={i for i in range(1000)};" "len(a)"
10000000 loops, best of 3: 0.0423 usec per loop
                           ↑ 

现在它只考虑len函数,结果几乎相同,因为我们没有考虑集/列表的创建时间。

答案 3 :(得分:5)

是的,你是对的,更多的是因为python创建setlist对象所需的时间不同。作为更公平的基准,您可以使用timeit模块并使用setup参数传递对象:

from timeit import timeit

print '1st: ' ,timeit(stmt="len(a)", number=1000000,setup="a=set([1,2,3]*1000)")
print '2nd : ',timeit(stmt="len(a)", number=1000000,setup="a=[1,2,3]*1000")

结果:

1st:  0.04927110672
2nd :  0.0530669689178

如果你想知道为什么会这样,让我们​​来看看python世界。实际上set对象使用hash table并且哈希表使用哈希函数来创建项的哈希值并将它们映射到值,并且在此交易中调用函数并计算哈希值以及另外一些额外的任务将花很多时间。在创建列表python时,只需创建一系列对象,您可以使用索引来访问它们。

您可以从Cpython source code查看有关set_lookkey功能的更多详细信息。

另请注意,如果两个算法具有相同的复杂度,则并不意味着两个算法的运行时间或执行速度完全相同。 1

<子> 因为big O符号描述limiting behavior of a function并且没有显示确切的复杂性等式。 例如,以下等式f(x)=100000x+1f(x)=4x+20的复杂度为O(1) 并且它意味着两者都是线性方程式bur,因为您可以看到第一个函数具有相当大的斜率,并且对于相同的输入它们将给出不同的结果。

答案 4 :(得分:3)

让我在这里复合出色的答案:O(1)仅告诉您有关输入大小的order of growth

O(1)特别仅表示常量时间 与输入的大小相关。 对于任何输入,方法总是需要0.1秒,而对于任何输入,方法可能需要1000年,并且它们都是O(1)

在这种情况下,虽然文档有一定程度的歧义,但这意味着该方法大致同时处理大小为1的列表处理大小1000 的列表;同样地,处理大小为1的字典需要相同的时间来处理大小为1000的字典。

不保证不同的数据类型

这并不奇怪,因为在调用堆栈的某个时刻len()的实现可能因数据类型而异。

顺便提一下,这种模糊性在静态类型语言中被消除其中ClassA.size()ClassB.size()适用于所有意图和使用两种不同的方法。

答案 5 :(得分:1)

删除len(a)声明。结果几乎相同。需要对一组进行哈希处理以仅保留不同的项目,因此速度较慢。