如何在迭代时从列表中删除项?

ni65a41a  于 2021-07-13  发布在  Java
关注(0)|答案(10)|浏览(296)

**这个问题的答案是社区的努力。编辑现有答案以改进此帖子。它目前不接受新的答案或互动。

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

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

我应该用什么来代替 code_to_remove_tup ? 我想不出怎么用这种方式取下这个东西。

n9vozmp4

n9vozmp41#

对于那些喜欢函数式编程的人:

somelist[:] = filter(lambda tup: not determine(tup), somelist)

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

oknwwptz2#

我需要用一个巨大的列表来完成这项工作,而且复制列表似乎很昂贵,特别是在我的例子中,删除的数量与保留的项目相比是很少的。我采取了这种低级的方法。

array = [lots of stuff]
arraySize = len(array)
i = 0
while i < arraySize:
    if someTest(array[i]):
        del array[i]
        arraySize -= 1
    else:
        i += 1

我不知道的是,与复制一个大列表相比,几次删除的效率有多高。请评论,如果你有任何见解。

7ivaypg9

7ivaypg93#

如果当前列表项满足所需的条件,那么只创建一个新列表可能是明智的。
所以:

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

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

originalList[:] = newList

注意,来自python文档:
copy.copy(x)返回x的浅拷贝。
copy.deepcopy(x)返回x的深度副本。

tuwxkamq

tuwxkamq4#

这个答案最初是为了回答一个问题而写的,这个问题后来被标记为重复:从python上的列表中删除坐标
代码中有两个问题:
1) 当使用remove()时,您尝试删除整数,而需要删除元组。
2) for循环将跳过列表中的项目。
让我们看看执行代码时会发生什么:

>>> L1 = [(1,2), (5,6), (-1,-2), (1,-2)]
>>> for (a,b) in L1:
...   if a < 0 or b < 0:
...     L1.remove(a,b)
... 
Traceback (most recent call last):
  File "<stdin>", line 3, in <module>
TypeError: remove() takes exactly one argument (2 given)

第一个问题是将“a”和“b”都传递给remove(),但是remove()只接受一个参数。那么我们如何才能让remove()正确地处理您的列表呢?我们需要弄清楚你名单上的每个元素是什么。在本例中,每个都是一个元组。要查看这个,让我们访问列表中的一个元素(索引从0开始):

>>> L1[1]
(5, 6)
>>> type(L1[1])
<type 'tuple'>

啊哈!l1的每个元素实际上是一个元组。所以这就是我们需要传递给remove()的内容。python中的元组非常简单,只需将值括在圆括号中即可a、 “b”不是元组,但“(a,b)”是元组。因此,我们修改您的代码并再次运行它:


# The remove line now includes an extra "()" to make a tuple out of "a,b"

L1.remove((a,b))

这段代码运行时没有任何错误,但让我们看看它输出的列表:

L1 is now: [(1, 2), (5, 6), (1, -2)]

为什么(1,-2)还在你的名单上?结果证明,在使用循环对列表进行迭代时修改列表是一个非常糟糕的主意,如果不特别小心的话。(1,-2)保留在列表中的原因是列表中每个项的位置在for循环的迭代之间发生了变化。让我们看看如果我们给上面的代码提供一个更长的列表会发生什么:

L1 = [(1,2),(5,6),(-1,-2),(1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]

### Outputs:

L1 is now: [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

从该结果可以推断,每次条件语句的计算结果为true并且删除列表项时,循环的下一次迭代都将跳过对列表中下一项的计算,因为它的值现在位于不同的索引中。
最直观的解决方案是复制列表,然后遍历原始列表并只修改副本。你可以这样做:

L2 = L1
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))

# Now, remove the original copy of L1 and replace with L2

print L2 is L1
del L1
L1 = L2; del L2
print ("L1 is now: ", L1)

但是,输出将与之前相同:

'L1 is now: ', [(1, 2), (5, 6), (1, -2), (3, 4), (5, 7), (2, 1), (5, -1), (0, 6)]

这是因为当我们创建l2时,python实际上并没有创建新对象。相反,它只是将l2引用到与l1相同的对象。我们可以用“is”来验证这一点,它不同于仅仅“equals”(==)。

>>> L2=L1
>>> L1 is L2
True

我们可以使用copy.copy()制作一个真正的副本。然后一切正常:

import copy
L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
L2 = copy.copy(L1)
for (a,b) in L1:
    if a < 0 or b < 0 :
        L2.remove((a,b))

# Now, remove the original copy of L1 and replace with L2

del L1
L1 = L2; del L2
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

最后,还有一个比制作l1的全新副本更干净的解决方案。reversed()函数:

L1 = [(1,2), (5,6),(-1,-2), (1,-2),(3,4),(5,7),(-4,4),(2,1),(-3,-3),(5,-1),(0,6)]
for (a,b) in reversed(L1):
    if a < 0 or b < 0 :
        L1.remove((a,b))
print ("L1 is now: ", L1)
>>> L1 is now: [(1, 2), (5, 6), (3, 4), (5, 7), (2, 1), (0, 6)]

不幸的是,我不能胜任

ctehm74n

ctehm74n5#

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

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

或者,将 somelist[:] ,可以更改现有列表,使其仅包含所需的项:

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

如果有其他参考文献,这种方法可能是有用的 somelist 这需要反映变化。
你可以使用 itertools . 在python 2中:

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

或者在python 3中:

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

2g32fytz6#

建议列表理解的答案几乎是正确的——只是它们构建了一个全新的列表,然后给它起了与旧列表相同的名字,因为它们没有修改旧列表。这与@lennart建议的选择性删除不同——它更快,但是如果您的列表是通过多个引用访问的,那么您只是重新放置其中一个引用而不改变list对象本身的事实可能会导致微妙的、灾难性的错误。
幸运的是,获得列表理解的速度和就地更改所需的语义非常容易——只需代码:

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

请注意与其他答案的细微差别:这个答案并不是指定给一个空名称,而是指定给一个恰好是整个列表的列表片,从而替换同一个python列表对象中的列表内容,而不是像其他答案一样重新放置一个引用(从以前的列表对象到新的列表对象)。

ymzxtsji

ymzxtsji7#

您需要获取列表的一个副本并首先对其进行迭代,否则迭代将失败,并产生意外的结果。
例如(取决于列表的类型):

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
[]
oyxsuwqo

oyxsuwqo8#

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

你需要向后走,否则就有点像锯掉你坐在上面的树枝:-)
python 2用户:替换 rangexrange 避免创建硬编码列表

pbgvytdp

pbgvytdp9#

解决方法概述
或者:
使用链表实现/自己滚动。
链表是支持有效项目移除的适当数据结构,不会强制您进行空间/时间权衡。
一个cpython list 是用动态数组实现的,如本文所述,动态数组不是一种支持删除的好数据类型。
但是,标准库中似乎没有链表:
python中有链表预定义库吗?
https://github.com/ajakubek/python-llist
开始新的生活 list() 从头开始,然后 .append() 回到结尾,如所述:https://stackoverflow.com/a/1207460/895245
这种方法时间效率高,但空间效率低,因为它在迭代过程中保留了数组的额外副本。
使用 del 索引如下:https://stackoverflow.com/a/1207485/895245
因为它分配了数组副本,所以空间效率更高,但是时间效率更低,因为从动态数组中删除需要将下面的所有项移回一个,即o(n)。
一般来说,如果你做得又快又脏,不想添加自定义 LinkedList 同学们,你们只想跑得更快 .append() 选项,除非内存是一个大问题。
官方python 2教程4.2。”“用于报表”
https://docs.python.org/2/tutorial/controlflow.html#for-陈述
这部分文件明确指出:
您需要复制一个迭代列表来修改它
一种方法是使用切片表示法 [:] 如果在循环中需要修改正在迭代的序列(例如复制选定项),建议您首先进行复制。对序列进行迭代不会隐式生成副本。切片表示法特别方便:

>>> 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
这部分文档再次说明您必须制作一个副本,并给出了一个实际的删除示例:
注意:当循环修改序列时有一个微妙之处(这只能发生在可变序列,即列表中)。内部计数器用于跟踪下一个使用哪个项,并且在每次迭代中递增。当此计数器达到序列长度时,循环终止。这意味着,如果套件从序列中删除当前(或上一个)项,则将跳过下一个项(因为它获取已处理的当前项的索引)。同样,如果套件在当前项之前的序列中插入一个项,那么下次通过循环时将再次处理当前项。这可能会导致讨厌的错误,可以通过使用整个序列的片段制作临时副本来避免,例如。,

for x in a[:]:
if x < 0: a.remove(x)

但是,我不同意这种实现,因为 .remove() 必须迭代整个列表才能找到值。
python能做得更好吗?
似乎这个特定的pythonapi可以改进。例如,将其与:
java listiterator::remove which documents“每次对下一个或上一个调用只能进行一次此调用”
c++ std::vector::erase 它将一个有效的interator返回到移除后的元素
这两种方法都清楚地表明,除非使用迭代器本身,否则不能修改正在迭代的列表,并且提供了在不复制列表的情况下进行修改的有效方法。
也许其基本原理是,python列表被假定为动态数组支持的,因此任何类型的删除都将是时间效率低下的,而java对这两者都有更好的接口层次结构 ArrayList 以及 LinkedList 的实现 ListIterator .
python stdlib中似乎也没有显式的链表类型:python链表

h5qlskok

h5qlskok10#

对于这样一个例子,最好的方法是列表理解

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

如果你在做比打电话更复杂的事情 determine 函数,我更喜欢构造一个新的列表,并在执行时简单地添加到它。例如

newlist = []
for tup in somelist:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)
somelist = newlist

使用复制列表 remove 可能会使您的代码看起来更干净,如下面的一个答案所述。对于非常大的列表,您绝对不应该这样做,因为这涉及到首先复制整个列表,然后执行
O(n) remove 操作,使其成为 O(n^2) 算法。

for tup in somelist[:]:
    # lots of code here, possibly setting things up for calling determine
    if determine(tup):
        newlist.append(tup)

相关问题