我在Python中迭代一个元组列表,并试图删除它们,如果它们满足某些条件。

for tup in somelist:
    if determine(tup):
         code_to_remove_tup

我应该用什么来代替code_to_remove_tup?我不知道怎么用这种方式把东西拿掉。


当前回答

你可以反过来尝试for- loops,这样对于some_list,你就可以这样做:

list_len = len(some_list)
for i in range(list_len):
    reverse_i = list_len - 1 - i
    cur = some_list[reverse_i]

    # some logic with cur element

    if some_condition:
        some_list.pop(reverse_i)

这样索引是对齐的,并且不会受到列表更新的影响(无论是否弹出cur元素)。

其他回答

您可以使用列表推导式创建一个新列表,其中只包含您不想删除的元素:

somelist = [x for x in somelist if not determine(x)]

或者,通过分配slice somlist[:],你可以改变现有的列表,只包含你想要的项:

somelist[:] = [x for x in somelist if not determine(x)]

如果对某个列表的其他引用需要反映更改,则此方法可能很有用。

你也可以使用itertools来代替理解函数。在Python 2中:

from itertools import ifilterfalse
somelist[:] = ifilterfalse(determine, somelist)

或者在python3中:

from itertools import filterfalse
somelist[:] = filterfalse(determine, somelist)

我能想到三种方法来解决你的问题。例如,我将创建元组somelist =[(1,2,3),(4,5,6),(3,6,6),(7,8,9),(15,0,0),(10,11,12)]的随机列表。我选择的条件是元组元素之和= 15。在最终的列表中,我们将只有那些和不等于15的元组。

我所选择的是一个随机选择的例子。请随意更改元组列表和我所选择的条件。

方法1。使用你建议的框架(在for循环中填充代码)。我使用一个带del的小代码来删除满足上述条件的元组。然而,如果两个连续放置的元组满足给定条件,该方法将错过一个元组(满足上述条件)。

for tup in somelist:
    if ( sum(tup)==15 ): 
        del somelist[somelist.index(tup)]

print somelist
>>> [(1, 2, 3), (3, 6, 6), (7, 8, 9), (10, 11, 12)]

方法2。构造一个新的列表,其中包含不满足给定条件的元素(元组)(这与删除满足给定条件的列表元素是一样的)。下面是它的代码:

newlist1 = [somelist[tup] for tup in range(len(somelist)) if(sum(somelist[tup])!=15)]

print newlist1
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

方法3。找到满足给定条件的索引,然后使用与这些索引对应的删除元素(元组)。下面是它的代码。

indices = [i for i in range(len(somelist)) if(sum(somelist[i])==15)]
newlist2 = [tup for j, tup in enumerate(somelist) if j not in indices]

print newlist2
>>>[(1, 2, 3), (7, 8, 9), (10, 11, 12)]

方法1和方法2比方法3快。方法2和方法3比方法1更有效。我更喜欢方法2。对于上面的例子,time(method1): time(method2): time(method3) = 1:1: 1.7

其他答案是正确的,从你正在迭代的列表中删除通常是一个坏主意。反向迭代避免了一些陷阱,但是要遵循这样做的代码要困难得多,所以通常您最好使用列表理解或过滤器。

然而,有一种情况下,从您正在迭代的序列中删除元素是安全的:如果您在迭代时只删除一项。这可以通过返回或中断来确保。例如:

for i, item in enumerate(lst):
    if item % 4 == 0:
        foo(item)
        del lst[i]
        break

当您对满足某些条件的列表中的第一个项执行一些具有副作用的操作,然后立即从列表中删除该项时,这通常比列表推导式更容易理解。

变通方案概述

:

use a linked list implementation/roll your own. A linked list is the proper data structure to support efficient item removal, and does not force you to make space/time tradeoffs. A CPython list is implemented with dynamic arrays as mentioned here, which is not a good data type to support removals. There doesn't seem to be a linked list in the standard library however: Is there a linked list predefined library in Python? https://github.com/ajakubek/python-llist start a new list() from scratch, and .append() back at the end as mentioned at: https://stackoverflow.com/a/1207460/895245 This time efficient, but less space efficient because it keeps an extra copy of the array around during iteration. use del with an index as mentioned at: https://stackoverflow.com/a/1207485/895245 This is more space efficient since it dispenses the array copy, but it is less time efficient, because removal from dynamic arrays requires shifting all following items back by one, which is O(N).

一般来说,如果你做得很快,不想添加一个自定义LinkedList类,你只需要在默认情况下使用更快的.append()选项,除非内存是一个大问题。

官方Python 2教程4.2。“声明”

https://docs.python.org/2/tutorial/controlflow.html#for-statements

这部分文档明确说明:

您需要复制迭代列表才能修改它 一种方法是使用切片符号[:]

If you need to modify the sequence you are iterating over while inside the loop (for example to duplicate selected items), it is recommended that you first make a copy. Iterating over a sequence does not implicitly make a copy. The slice notation makes this especially convenient: >>> words = ['cat', 'window', 'defenestrate'] >>> for w in words[:]: # Loop over a slice copy of the entire list. ... if len(w) > 6: ... words.insert(0, w) ... >>> words ['defenestrate', 'cat', 'window', 'defenestrate']

Python 2文档7.3。“for语句”

https://docs.python.org/2/reference/compound_stmts.html#for

这部分文档再次说明你必须复制一份,并给出了一个实际的删除示例:

Note: There is a subtlety when the sequence is being modified by the loop (this can only occur for mutable sequences, i.e. lists). An internal counter is used to keep track of which item is used next, and this is incremented on each iteration. When this counter has reached the length of the sequence the loop terminates. This means that if the suite deletes the current (or a previous) item from the sequence, the next item will be skipped (since it gets the index of the current item which has already been treated). Likewise, if the suite inserts an item in the sequence before the current item, the current item will be treated again the next time through the loop. This can lead to nasty bugs that can be avoided by making a temporary copy using a slice of the whole sequence, e.g., for x in a[:]:

    if x < 0: a.remove(x)

然而,我不同意这个实现,因为.remove()必须遍历整个列表才能找到值。

Python能做得更好吗?

似乎这个特定的Python API可以得到改进。例如,将其与:

Java ListIterator::删除哪些文档“此调用只能对next或previous调用一次” c++ std::vector::erase,返回被删除元素之后的一个有效的互操作器

这两种方法都清楚地表明,除了使用迭代器本身,您不能修改正在迭代的列表,并为您提供了在不复制列表的情况下修改列表的有效方法。

可能潜在的基本原理是,Python列表被假定为动态数组支持,因此任何类型的删除都将是低效的,而Java在ListIterator的ArrayList和LinkedList实现方面都有更好的接口层次结构。

在Python标准库中似乎也没有显式的链表类型:Python链表

for i in range(len(somelist) - 1, -1, -1):
    if some_condition(somelist, i):
        del somelist[i]

你需要向后走,否则就有点像锯掉你坐着的树枝:-)

Python 2用户:用xrange替换range以避免创建硬编码的列表