在 Python 中从列表前面删除一个元素是不是便宜?
Posted
技术标签:
【中文标题】在 Python 中从列表前面删除一个元素是不是便宜?【英文标题】:Is removing an element from the front of a list cheap in Python?在 Python 中从列表前面删除一个元素是否便宜? 【发布时间】:2017-06-20 04:24:57 【问题描述】:我正在编写一个程序,它在数据列表的前面或后面进行大量删除,而不是中间。
我知道删除最后一个元素很便宜,但是删除第一个元素呢?例如,假设列表A
的地址位于4000
,那么元素0
位于4000
,元素1
位于4001
。
将删除元素0
,然后让编译器将列表A
的地址放在4001
,或者将4001
的元素1
移动到4000
的位置,然后将所有其他元素下降了1
?
【问题讨论】:
不,当您从中间或前面删除时,它会调整大小。此外,Python 列表不等同于 C 数组。虽然,在内心深处,有一个 Py_Object 指针数组。 【参考方案1】:不,它并不便宜。从列表的前面删除一个元素(例如使用list.pop(0)
)是一个O(N)
操作,应该避免。同样,在开头插入元素(使用list.insert(0, <value>)
)同样效率低下。
这是因为,在调整列表大小后,它的元素必须移动。对于 CPython,在 l.pop(0)
的情况下,this is done with memmove
而对于 l.insert(0, <value>)
,the shifting is implemented with a loop through the items stored。
列表是为快速随机访问和O(1)
在其结束操作而构建的。
由于您经常执行此操作,因此您应该考虑使用deque
from the collections
module(正如@ayhan 在评论中建议的那样)。 deque
上的文档还强调了 list
对象不适合这些操作:
虽然列表对象支持类似的操作,但它们针对快速固定长度操作进行了优化,并且为
pop(0)
和insert(0, v)
操作带来了O(n)
内存移动成本这会改变底层数据表示的大小和位置。
(强调我的)
deque
数据结构为双方(开始和结束)提供O(1)
复杂性,appendleft
/popleft
和 append
/pop
方法分别用于开始和结束。
当然,对于小尺寸,这会产生一些额外的空间要求(由于 deque
的结构),这通常应该是无关紧要的(正如@juanpa 在评论中指出的那样,并不总是成立)列表的大小会增长。最后,正如@ShadowRanger 富有洞察力的评论所指出的那样,对于非常小的序列大小,从前面弹出或插入的问题会变得微不足道,以至于它变得完全无关紧要。
因此,简而言之,对于包含许多项目的列表,如果您需要从两侧快速追加/弹出,请使用deque
,否则,如果您随机访问并追加到末尾,请使用list
s。
【讨论】:
实际上,根据我的经验,deque
s 比列表小。尝试一个简单的测试,for i in range(0, 10000000, 100000): l,d = list(range(i)), deque(range(i)); print(sys.getsizeof(l), sys.getsizeof(d))
。列表总是稍大一些。
@juanpa.arrivillaga 您正在使用 list
初始化程序,它总是创建更大的列表,尝试使用理解(或文字,对于较小的列表)并将其提供给 deque
并计数尺寸再次。较大的大小似乎存在一些差异(可能是由于这些分配内存的方式),但总的来说,我认为在较小的大小中列表较小的断言是成立的。
我确实尝试过使用列表理解。最后阅读:81528056 82500632
。本质上,在这种情况下,列表理解将优化存储,并且存在具有相同大小的列表运行。在运行开始时,它比等效的双端队列略小。在运行的中间,双端队列变小了。它可能平均大约洗一次。但基本上,我的结论是双端队列通常更小或大小相同。
实际上,列表在运行的开始时更大,相对于结束时的双端队列更小。
注意:对于一个永远不会超过个位数长度的序列,使用list
或deque
无关紧要;当然,前面的pop
ing 将是O(n)
,但是当n
是6
时,Python 的固定开销超过了避免memmove
的任何好处,事实上,新的@987654359当您到达当前块的末尾时,@ 块将间歇性地为 deque
分配(其中长度范围较窄的 list
永远不需要重新分配)会使性能不太一致。在长度有意义之前,与解释器开销相比,它只是噪音。【参考方案2】:
在 Python 中从列表的前面删除元素是 O(n),而从 collections.deque 的末尾删除元素只是 O(1)。因此,双端队列非常适合您的目的,但是应该注意,从双端队列中间访问或添加/删除比列表更昂贵。
删除的 O(n) 成本是因为 CPython 中的列表简单地实现为指针数组,因此您对每个元素的移动成本的直觉是正确的。
这可以在 Wiki 上的 Python TimeComplexity page 中看到。
【讨论】:
以上是关于在 Python 中从列表前面删除一个元素是不是便宜?的主要内容,如果未能解决你的问题,请参考以下文章