Python中使用pop()进行列表操作

11 投票
3 回答
31457 浏览
提问于 2025-04-16 12:47

简单来说,我需要根据索引从一个列表中删除多个项目。不过,我不能用pop方法,因为它会改变索引的位置(如果不使用一些复杂的补偿系统的话)。有没有办法可以同时删除多个项目呢?

我有一个算法,它会遍历一个列表,如果条件符合,就通过pop方法删除那个项目。问题是,这个过程是在一个循环中进行的。一旦使用了pop,列表就会少一个元素,所有的值都会向前移动一位。这样一来,循环就会超出范围。有没有办法可以同时删除多个项目,或者有没有其他的解决方案呢?

这是我遇到的问题的一个例子:

L = ['a', 'b', 'c', 'd']

for i in range(len(L)):
    print L
    if L[i] == 'a' or L[i] == 'c':
        L.pop(i)

3 个回答

7

总结

  • 可以使用列表推导式(或者生成器表达式)来从列表中删除多个项目
  • 如果你的输入是一个很大的字节串,可以使用 str.translate() 来删除字符
  • 一次删除一个项目 del L[i] 对于大列表来说速度很慢

如果项目是字节,就像你例子中的那样,你可以使用 str.translate()

def remove_bytes(bytestr, delbytes):
    """
    >>> remove_bytes(b'abcd', b'ac') == b'bd'
    True
    """
    return bytestr.translate(None, delbytes)

一般来说,可以使用切片来删除多个项目:

def remove_inplace_without_order(L, delitems):
    """Remove all items from `L` that are in `delitems` (not preserving order).

    >>> L = list(range(4)); remove_inplace_without_order(L, [0,2]); L
    [3, 1]
    """
    idel = len(L) # items idel.. to be removed
    for i in reversed(range(len(L))):
        if L[i] in delitems:
            idel -= 1
            L[i] = L[idel] # save `idel`-th item
    del L[idel:] # remove items all at once
    #NOTE: the function returns `None` (it means it modifies `L` inplace)

正如 @phooji@senderle 已经提到的,列表推导式(或者生成器表达式)在你的情况下更好:

def remove_listcomp(L, delitems):
    return [x for x in L if x not in delitems]

这是一个性能比较,针对 L=list("abcd"*10**5); delitems="ac"

| function                     | time, msec |  ratio |
|------------------------------+------------+--------|
| list                         |       4.42 |    0.9 |
| remove_bytes                 |       4.88 |    1.0 |
| remove                       |       27.3 |    5.6 |
| remove_listcomp              |       36.8 |    7.5 |
| remove_inplace_without_order |       71.2 |   14.6 |
| remove_inplace_senderle2     |       83.8 |   17.2 |
| remove_inplace_senderle      |      15000 | 3073.8 |
#+TBLFM: $3=$2/@3$2;%.1f

其中

try:
    from itertools import ifilterfalse as filterfalse
except ImportError:
    from itertools import filterfalse # py3k

def remove(L, delitems):
    return filterfalse(delitems.__contains__, L)

def remove_inplace_senderle(L, delitems):
    for i in reversed(range(len(L))):
        if L[i] in delitems:
            del L[i]

def remove_inplace_senderle2(L, delitems):
    write_i = 0
    for read_i in range(len(L)):
        L[write_i] = L[read_i]
        if L[read_i] not in delitems:
             write_i += 1
    del L[write_i:]

remove_inplace_senderle() 速度慢是因为它使用了 O(N**2) 的算法。每次 del L[i] 可能会导致右边的所有项目都要向左移动来填补空缺。

上表中的时间列包括创建新输入列表所需的时间(第一行),因为某些算法会就地修改输入。

这是相同输入的时间,但在每次迭代时不创建新列表:

 | function        | time, msec | ratio |
 |-----------------+------------+-------|
 | remove_bytes    |      0.391 |     1 |
 | remove          |       24.3 |    62 |
 | remove_listcomp |       33.4 |    85 |
 #+TBLFM: $3=$2/@2$2;%d

表格显示 itertools.ifilterfalse() 对于列表推导式并没有显著的提升。

一般来说,除非性能分析工具证明这段代码是瓶颈,并且对你的程序很重要,否则考虑这种任务的性能是没有必要的,甚至可能是有害的。不过,了解一些替代方法可能会对速度有显著的提升是有用的。

15

你想要一个列表推导式:

L = [c for c in L if c not in ['a', 'c']]

或者,如果你真的不想创建一个副本,可以反向操作:

for i in reversed(range(len(L))):
    if L[i] in ['a', 'c']:
        L.pop(i)    # del L[i] is more efficient

感谢ncoghlan提供的reversed()方法,以及phooji提供的del L[i]建议。(我决定保留L.pop(i),因为这就是问题最初的表述方式。)

另外,正如J.S. Sebastian正确指出的,反向操作在空间上是高效的,但在时间上却不太高效;大多数情况下,使用列表推导式或生成器(L = (...)而不是L = [...])是最好的选择。

编辑:

好吧,既然大家似乎想要一种比上面提到的反向方法更快的方式(我真想不通为什么... :) ),这里有一个保持顺序的原地过滤方法,它的速度应该只比列表推导式慢一个常数。(这就像我在C语言中过滤字符串时会做的事情。)

write_i = 0
for read_i in range(len(L)):
    L[write_i] = L[read_i]
    if L[read_i] not in ['a', 'c']:
         write_i += 1

del L[write_i:]
print L
# output: ['b', 'd']
17

你的列表很大吗?如果是的话,可以使用 ifilter,这个东西来自于 itertools,它可以懒惰地过滤掉你不想要的元素(也就是说,不需要提前消耗资源)。

如果列表不大,那就直接用列表推导式吧:

 newlist = [x for x in oldlist if x not in ['a', 'c'] ]

这样会创建一个新列表的副本。一般来说,这对效率影响不大,除非你特别在意内存的使用。

如果你想在语法上方便一点,又想保持效率(特别是对于大列表),可以用 ( ) 来构造一个生成器,而不是用 [ ] 来构造列表:

interestingelts = (x for x in oldlist if x not in ['a', 'c'])

这样做之后,你可以遍历 interestingelts,但是不能通过索引来访问它:

 for y in interestingelts:    # ok
    print y

 print interestingelts[0]     # not ok: generator allows sequential access only

撰写回答