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

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

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


当前回答

变通方案概述

:

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链表

其他回答

建议列表推导的答案几乎是正确的,除了它们构建了一个全新的列表,然后给它一个与旧列表相同的名称,它们没有在适当的地方修改旧列表。这与Lennart建议的选择性删除不同——它更快,但如果您的列表是通过多个引用访问的,那么您只是重新设置了其中一个引用,而没有更改列表对象本身,这可能会导致微妙的、灾难性的错误。

幸运的是,它非常容易获得列表推导式的速度和所需的就地更改的语义——只是代码:

somelist[:] = [tup for tup in somelist if determine(tup)]

请注意与其他答案的细微区别:这个答案没有分配给一个裸名。它赋值给一个列表切片,恰好是整个列表,因此替换了同一Python列表对象中的列表内容,而不是像其他答案一样只是重新设置一个引用(从以前的列表对象到新的列表对象)。

最有效的方法是列表理解,很多人展示了他们的案例,当然,通过过滤器获得迭代器也是一个很好的方法。

过滤器接收一个函数和一个序列。Filter依次将传递的函数应用于每个元素,然后根据函数的返回值是True还是False来决定是否保留或丢弃该元素。

这里有一个例子(获取元组中的概率):

list(filter(lambda x:x%2==1, (1, 2, 4, 5, 6, 9, 10, 15)))  
# result: [1, 5, 9, 15]

警告:你也可以不处理迭代器。迭代器有时比序列更好。

您需要获取列表的副本并首先对其进行迭代,否则迭代将失败,可能会出现意想不到的结果。

例如(取决于列表的类型):

for tup in somelist[:]:
    etc....

一个例子:

>>> somelist = range(10)
>>> for x in somelist:
...     somelist.remove(x)
>>> somelist
[1, 3, 5, 7, 9]

>>> somelist = range(10)
>>> for x in somelist[:]:
...     somelist.remove(x)
>>> somelist
[]

如果当前列表项满足所需的条件,那么创建一个新列表可能是聪明的做法。

so:

for item in originalList:
   if (item != badValue):
        newList.append(item)

为了避免用新的列表名称重新编码整个项目:

originalList[:] = newList

注意,来自Python文档:

copy.copy (x) 返回x的浅拷贝。 copy.deepcopy (x) 返回x的深拷贝。

如果稍后将使用新列表,可以简单地将elem设置为None,然后在后面的循环中判断它,如下所示

for i in li:
    i = None

for elem in li:
    if elem is None:
        continue

这样,你就不需要复制列表,而且更容易理解。