Python 按索引删除列表元素是否比按值删除更有效?
在python中,假设我们有以下列表:Python 按索引删除列表元素是否比按值删除更有效?,python,performance,runtime,python-internals,Python,Performance,Runtime,Python Internals,在python中,假设我们有以下列表: my_list=['a','b','c','d',…] my\u list.pop(3)会比my\u list.remove('d')更有效吗?对于小列表来说没有多大关系: [wander@box ~]$ python -m timeit '[1, 2, 3].pop(1)' 10000000 loops, best of 3: 0.167 usec per loop [wander@box ~]$ python -m timeit '[1, 2, 3].
my_list=['a','b','c','d',…]
my\u list.pop(3)
会比my\u list.remove('d')更有效吗?对于小列表来说没有多大关系:
[wander@box ~]$ python -m timeit '[1, 2, 3].pop(1)'
10000000 loops, best of 3: 0.167 usec per loop
[wander@box ~]$ python -m timeit '[1, 2, 3].remove(2)'
10000000 loops, best of 3: 0.129 usec per loop
如果您的列表非常大,或者比较列表中的元素需要很长时间,那么可能会有更大的差异。删除将较慢,因为它必须通过(并比较)所有元素:
[wander@box ~]$ python -m timeit -n 100000 'list(range(1, 100)).pop(98)'
100000 loops, best of 3: 0.916 usec per loop
[wander@box ~]$ python -m timeit -n 100000 'list(range(1, 100)).remove(98)'
100000 loops, best of 3: 2.05 usec per loop
这就是整数,比较起来很快。如果列表中的元素具有更有趣的\uuuu eq\uuu
方法,删除
可能需要很长时间:
class Foo:
def __eq__(self, other):
time.sleep(1)
return False
[Foo(), Foo(), Foo(), Foo(), 20].remove(20)
因此,如果您知道索引,请使用pop
对于小列表来说没有多大关系:
[wander@box ~]$ python -m timeit '[1, 2, 3].pop(1)'
10000000 loops, best of 3: 0.167 usec per loop
[wander@box ~]$ python -m timeit '[1, 2, 3].remove(2)'
10000000 loops, best of 3: 0.129 usec per loop
如果您的列表非常大,或者比较列表中的元素需要很长时间,那么可能会有更大的差异。删除将较慢,因为它必须通过(并比较)所有元素:
[wander@box ~]$ python -m timeit -n 100000 'list(range(1, 100)).pop(98)'
100000 loops, best of 3: 0.916 usec per loop
[wander@box ~]$ python -m timeit -n 100000 'list(range(1, 100)).remove(98)'
100000 loops, best of 3: 2.05 usec per loop
这就是整数,比较起来很快。如果列表中的元素具有更有趣的\uuuu eq\uuu
方法,删除
可能需要很长时间:
class Foo:
def __eq__(self, other):
time.sleep(1)
return False
[Foo(), Foo(), Foo(), Foo(), 20].remove(20)
因此,如果您知道索引,请使用pop
根据,这两种方法的复杂性是:
pop O(n - i)
remove O(n)
使用列表长度n
和元素的索引i
因此,根据列表的大小,pop
从长远来看可能会更快。根据,这两种方法的复杂性是:
pop O(n - i)
remove O(n)
使用列表长度n
和元素的索引i
因此,根据列表的大小,从长远来看,pop
可能会更快。查看和的实际C代码(您指的是CPython,对吧?),您可以看到:
.remove
需要迭代列表(因此按O(i)
进行缩放,其中i
是项目的索引)
.pop
采用快捷方式,如下所示:
- 判断索引是否超出范围(第928行)很简单,但是
.remove
必须检查整个列表以找到它的目标(或者不这样做);及
- 在特殊情况下,索引是列表中的最后一项(第933行)
listremove
调用了PyObject\u richcomarebool
(第2200行),这相对比较昂贵,因为它需要检查当前索引中的对象是否等于该项;及
一旦找到合适的切片位置,这两个(除了上面提到的.pop
的特殊情况)最终都会委托(第941行和第2202行-后面没有咯咯笑);这必须将数组其余部分中的项洗牌,因此将O(n-i)
在此基础上,.pop
将更快,尤其是对于列表中的最后一项;但是,如果您从项目本身开始,并且已经进行了O(n)
操作,并进行了丰富的比较,以查找其.index
,那么您在回旋处失去的摆动中获得了什么
此外,重新排列数组中剩余的所有内容(即列表\u ass\u slice
)以弥补您删除的内容的操作,.pop
和.remove
都需要执行,这可能比首先确定要删除的项目要昂贵得多
请注意,在不深入源代码的情况下,只要从逻辑上考虑每个操作所涉及的内容,几乎可以解决上述所有问题。查看实际的C代码(您指的是CPython,对吧?),您可以看到:
.remove
需要迭代列表(因此按O(i)
进行缩放,其中i
是项目的索引)
.pop
采用快捷方式,如下所示:
- 判断索引是否超出范围(第928行)很简单,但是
.remove
必须检查整个列表以找到它的目标(或者不这样做);及
- 在特殊情况下,索引是列表中的最后一项(第933行)
listremove
调用了PyObject\u richcomarebool
(第2200行),这相对比较昂贵,因为它需要检查当前索引中的对象是否等于该项;及
一旦找到合适的切片位置,这两个(除了上面提到的.pop
的特殊情况)最终都会委托(第941行和第2202行-后面没有咯咯笑);这必须将数组其余部分中的项洗牌,因此将O(n-i)
在此基础上,.pop
将更快,尤其是对于列表中的最后一项;但是,如果您从项目本身开始,并且已经进行了O(n)
操作,并进行了丰富的比较,以查找其.index
,那么您在回旋处失去的摆动中获得了什么
此外,重新排列数组中剩余的所有内容(即列表\u ass\u slice
)以弥补您删除的内容的操作,.pop
和.remove
都需要执行,这可能比首先确定要删除的项目要昂贵得多
请注意,在不深入源代码的情况下,只要从逻辑上考虑每个操作所涉及的内容,几乎可以解决上述所有问题。您是否尝试过使用timeit
对其进行计时?仅仅因为在某些任意输入下,某些功能会更好,并不意味着它更高效。我在寻找一个更具理论性的答案。它们不是等价的——你已经知道索引或项目了吗?我希望将数组中的所有其他内容都移动到d