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

2024-04-19 08:46:31 发布

您现在位置:Python中文网/ 问答频道 /正文

简而言之,我需要根据索引从列表中删除多个项。但是,我不能使用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)

Tags: 项目方法in算法列表forlen系统
3条回答

你的单子大吗?如果是的话,使用^{}中的ifilter来过滤掉那些您不需要的元素(无需预先支付费用)。

单子不那么大?使用列表理解:

 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

你想要一个列表理解:

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的建议。(我决定将其保留为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']

小结

  • 使用列表理解(或genexpr)从列表中删除多个项
  • 如果输入的是大字节字符串,则使用str.translate()删除字符
  • 对于大型列表,一次删除一个项del L[i]很慢

如果项是与示例中相同的字节,则可以使用^{}

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:]

由于使用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

该表显示^{}与listcomp相比没有显著改进。

一般来说,考虑此类任务的性能是不值得的,甚至是有害的,除非探查器证明此代码是一个瓶颈,并且对您的程序非常重要。但是了解能够在速度上提供超过一个数量级改进的替代方法可能是有用的。

相关问题 更多 >