通过索引删除列表元素比通过值删除它更有效吗?

时间:2015-08-18 16:41:13

标签: python performance runtime python-internals

在python中,假设我们有以下列表: my_list = ['a', 'b', 'c', 'd', ...]

my_list.pop(3)会比my_list.remove('d')更有效吗?

3 个答案:

答案 0 :(得分:8)

根据this answer,这两种方法的复杂性是:

pop     O(n - i)
remove  O(n)

列表长度n和元素i的索引。

因此,根据列表的大小,pop 可能从长远来看会更快。

答案 1 :(得分:6)

小清单并不重要:

[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

这是整数,这比较快。如果列表中的元素包含更有趣的__eq__方法,则remove可能需要很长时间:

class Foo:
    def __eq__(self, other):
        time.sleep(1)
        return False

[Foo(), Foo(), Foo(), Foo(), 20].remove(20)

因此,如果您知道索引,请使用pop

答案 2 :(得分:4)

查看listpoplistremove的实际C代码(您的意思是CPython,对吗?),您可以看到:

  1. .remove需要遍历列表(因此按O(i)进行缩放,其中i是项目的索引);

  2. .pop采用快捷方式,如:

    • 弄清楚索引是否超出界限(第928行)是微不足道的,但.remove必须检查整个列表以找到其目标(或未能这样做);和

    • 特殊情况下索引是列表中的最后一项(第933行);

  3. listremove调用PyObject_RichCompareBool(第2200行),这是相对昂贵的,因为它需要检查当前索引处的对象是否等于该项;以及

  4. 一旦找到合适的切片位置,两者(除了上面提到的.pop的特殊情况外)最终委托给list_ass_slice(第941行和第2202行 - 并且后面没有傻笑) ;这必须将数组的其余部分中的项目随机化,因此将O(n - i)

  5. 在此基础上.pop会更快,特别是对于列表中的最后一项;但是,如果您已经开始使用该项目,并且已经进行了O(n)操作并进行了丰富的比较以找到其.index,那么您在环形交叉口已经丢失的波动中获得了什么。

    此外,重新排列数组中剩余的所有内容(即list_ass_slice)以弥补您已移除的内容(.pop.remove需要执行的操作,可能比首先确定要删除哪个项目要贵得多。

    请注意,如果不深入了解源代码,几乎所有上述内容都可以通过逻辑思考每个操作涉及的内容来解决。