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

iecba09b  于 2021-08-20  发布在  Java
关注(0)|答案(10)|浏览(321)

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

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

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

我应该用什么来代替 code_to_remove_tup ? 我不知道如何以这种方式删除该项目。

js4nwp54

js4nwp541#

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

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

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

9rbhqvlz

9rbhqvlz2#

如果当前列表项满足所需的条件,也可以创建一个新列表。
因此:

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

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

originalList[:] = newList

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

hsvhsicv

hsvhsicv3#

这个答案最初是为了回答一个被标记为重复的问题:从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的全新副本更干净的解决方案。反转()函数:

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

不幸的是,我不能充分理解

zpgglvta

zpgglvta4#

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

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)
drnojrws

drnojrws5#

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

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

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

rbl8hiat

rbl8hiat6#

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

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

toe950277#

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

你需要向后走,否则就有点像锯掉你坐的树枝:-)
python 2用户:替换 range 通过 xrange 避免创建硬编码列表的步骤

3htmauhk

3htmauhk8#

变通办法概述
要么:
使用链表实现/滚动您自己的。
链表是支持高效项目删除的适当数据结构,不会强制您进行空间/时间权衡。
卡皮顿 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。”关于“声明”的声明
https://docs.python.org/2/reference/compound_stmts.html#for
文档的这一部分再次指出,您必须制作一份副本,并给出了一个实际的删除示例:
注意:当循环修改序列时有一个微妙之处(这只能发生在可变序列,即列表中)。内部计数器用于跟踪下一个使用哪个项,并且在每次迭代中递增。当该计数器达到序列长度时,循环终止。这意味着,如果套件从序列中删除当前(或上一个)项,则将跳过下一个项(因为它获取已处理的当前项的索引)。同样,如果套件在当前项之前的序列中插入了一个项,那么下次通过循环将再次处理当前项。这可能导致严重的错误,可以通过使用整个序列的一个片段制作临时副本来避免,例如。,

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

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

mpbci0fu

mpbci0fu9#

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

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)
yquaqz18

yquaqz1810#

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

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

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

相关问题