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


934

我正在遍历Python中的元组列表,并尝试在满足特定条件的情况下将其删除。

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

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


此页面上的大多数答案并不能真正解释为什么在遍历列表时删除元素会产生奇怪的结果,但是此问题中被接受的答案确实可以,并且对于第一次遇到此问题的初学者而言可能是更好的选择。
ggorlen

Answers:


827

您可以使用列表推导来创建一个仅包含您不想删除的元素的新列表:

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

或者,通过分配给slice 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)

为了清楚起见,以及对于那些发现使用[:]黑变或模糊表示法的人,这里有一个更明确的选择。从理论上讲,它在空间和时间上的表现应该与上面的单层表现相同。

temp = []
while somelist:
    x = somelist.pop()
    if not determine(x):
        temp.append(x)
while temp:
    somelist.append(templist.pop())

它也可以在其他语言中工作,而这些语言可能不具有Python列表的替换项功能,并且只需进行很少的修改即可。例如,并非所有语言都False像Python一样将空列表转换为。您可以替换while somelist:更明确的内容,例如while len(somelist) > 0:


4
如果您只知道将要删除的部分内容,是否可以加快速度,即仅删除那些内容并将其他内容留在原处而不是重写它们?
highBandWidth 2011年

20
如果我的清单很大而又付不起副本怎么办?
jpcgt 2014年

15
@jpcgt您应该使用somelist[:] = (x for x in somelist if determine(x))它来创建生成器,该生成器可能不会创建任何不必要的副本。
Rostislav Kondratenko

8
@RostislavKondratenko:内部list_ass_slice()实现somelist[:]=调用的函数PySequence_Fast()。这个函数总是返回一个列表,即@Alex Martelli的解决方案已经使用列表而不是生成器了,它最有可能效率更高
jfs 2015年

6
您是否愿意解释将列表理解分配给列表和列表克隆之间的区别?somelist两种方法都不会改变原始列表吗?
Bowen Liu

589

暗示列表理解的答案几乎是正确的-除了它们会建立一个全新的列表,然后为其命名与旧列表相同外,它们不会修改旧列表。这与@Lennart的建议中的选择性删除操作不同-速度更快,但是如果通过多个引用访问列表,则说明您只是在重新放置其中一个引用而不更改列表对象本身可能会导致微妙的灾难性错误。

幸运的是,获得列表理解的速度和就地变更所需的语义非常容易,只需编写代码即可:

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

请注意与其他答案的细微差别:这不是分配给裸名-而是分配给恰好是整个列表的列表切片,从而替换同一Python列表对象中的列表内容 ,而不仅仅是重新放置一个引用(从先前的列表对象到新的列表对象),就像其他答案一样。


1
如何用字典执行相同的切片作业?在Python 2.6中?
PaulMcG 2011年

11
@Paul:由于字典是无序的,因此切片对于字典没有意义。如果要用dict a的内容替换dict 的内容b,请使用a.clear(); a.update(b)
Sven Marnach

1
为什么通过替换变量所指的内容来“重新设置”其中一个引用会导致bug?看来这只会是多线程应用程序中的潜在问题,而不是单线程。
德里克·达默

59
@Derek x = ['foo','bar','baz']; y = x; x = [item for item in x if determine(item)];这将重新分配x给列表理解的结果,但y仍引用原始列表['foo','bar','baz']。如果您期望xy引用相同的列表,则可能是您引入了错误。您可以通过分配给整个列表的一部分来避免这种情况,如Alex所示,我在这里显示:x = ["foo","bar","baz"]; y = x; x[:] = [item for item in x if determine(item)];。该列表已被修改。确保对列表的所有引用(包括此处xy此处)均引用新列表。
史蒂文·T·斯奈德,

实际上,使用filter函数也会创建一个新列表,不会修改元素...仅olist[:] = [i for i in olist if not dislike(i)]
John Strood

302

您需要获取列表的副本并首先对其进行迭代,否则迭代将失败,并可能导致意外结果。

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

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

13
@Zen因为第二个迭代了列表的副本。因此,当您修改原始列表时,无需修改迭代的副本。
Lennart Regebro 2014年

3
与list(somelist)相比,做somelist [:]有什么更好的选择?
Mariusz Jamro

3
list(somelist)将可迭代对象转换为列表。somelist[:]制作支持切片的对象的副本。因此,他们不一定要做相同的事情。在这种情况下,我想复制该somelist对象,因此我使用[:]
Lennart Regebro,2015年

33
请注意所有阅读此书的人,这对于列表来说非常慢。remove()每次迭代都必须遍历整个列表,所以这将是永远的。
2015年

7
处理仅十几个项目的列表时,大O时间并不重要。对于将来的程序员而言,通常清晰,简单的理解远比性能更有价值。
史蒂夫

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

您需要向后走,否则就像将您坐在的树枝锯掉一样:-)

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


13
在最新版本的Python中,您可以使用reversed()内置
函数

16
reversed()不会创建新列表,而是在提供的序列上创建反向迭代器。像enumerate()一样,您必须将其包装在list()中才能实际获得列表。你可能会思考排序(),它创建一个新的列表中的每个时间(它有,所以它可以对它进行排序)。
ncoghlan 2015年

1
@Mauris因为enumerate返回迭代器并reversed需要一个序列。reversed(list(enumerate(somelist)))如果您不介意在内存中创建额外的列表,我想您可以这样做。
drevicko 2015年

2
对于数组,这是O(N * M),如果从大列表中删除许多项,这将非常慢。所以不推荐。
山姆·沃特金斯2015年

2
@SamWatkins是的,此答案适用于从非常大的数组中删除几个元素的情况。减少内存使用量,但m速度可能会慢一些。
纳文

52

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

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

这部分文档清楚地表明:

  • 您需要复制迭代列表以对其进行修改
  • 一种方法是使用切片符号 [:]

如果需要修改循环中要迭代的序列(例如,复制选定的项目),建议您首先进行复制。遍历序列不会隐式地创建副本。切片符号使这特别方便:

>>> 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()必须迭代整个列表以找到值。

最佳解决方法

要么:

通常.append(),除非内存是一个大问题,否则默认情况下,您只想使用默认选项。

Python可以做得更好吗?

似乎可以改进此特定的Python API。例如,将其与:

两者都清楚地表明,除了使用迭代器本身之外,您无法修改要迭代的列表,并为您提供了无需复制列表即可进行修改的有效方法。

可能的基本原理是,假定Python列表是由动态数组支持的,因此,任何类型的删除都将在时间上效率低下,而Java具有更好的接口层次结构,同时包含ArrayListLinkedList实现ListIterator

Python stdlib中似乎也没有明确的链接列表类型:Python链接列表


48

此类示例的最佳方法是列表理解

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)

37

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

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

要么

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

1.列表理解和生成器表达式是从Haskell(一种纯函数语言)中借用的;它们的功能与完全一样filter,并且具有Pythonic的功能。2.如果需要lambda使用mapfilter,则列表comp或genexpr 始终是更好的选择。map并且filter当transform / predicate函数是用C内置的Python且可迭代对象的大小不是很小时,它可能会稍快一些,但是当您需要lambdalistcomp / genexpr可以避免的时候,它们总是比较慢。
ShadowRanger

13

我需要使用大量列表来完成此操作,并且复制列表似乎很昂贵,尤其是因为在我的情况下,与保留的项目相比,删除的数量很少。我采用了这种低级方法。

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

我不知道相对于复制大型列表而言,几次删除的效率如何。如果您有任何见解,请发表评论。


就我而言,我需要将那些“不需要的”元素移到另一个列表中。您对此解决方案有何新评论?我还认为,最好使用一些删除而不是重复的列表。
gustavovelascoh

如果性能是一个问题(尽管与@Alexey相同),这是正确的答案。就是说,list应该首先考虑选择作为数据结构,因为从列表中间删除数据需要花费列表长度的线性时间。如果您真的不需要随机访问第k个连续项目,可以考虑一下OrderedDict
最大

@GVelascoh为什么不创建newlist = [],然后newlist.append(array[i])才创建del array[i]
最大

2
请注意,这可能是时间效率低的:如果list()是链表,则随机访问会很昂贵,如果list()是数组,则删除会很昂贵,因为它们需要将所有后续元素向前移动。体面的迭代器可以使链表实现更好。但是,这可以节省空间。
Ciro Santilli冠状病毒审查六四事件法轮功

10

如果当前列表项符合期望的条件,则仅创建一个新列表也可能很聪明。

所以:

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

并且避免必须使用新的列表名称重新编码整个项目:

originalList[:] = newList

注意,来自Python文档:

copy.copy(x)返回x的浅表副本。

copy.deepcopy(x)返回x的深层副本。


3
这不会添加几年前接受的答案中没有的新信息。
Mark Amery

2
这很简单,这是查看问题的另一种方法@MarkAmery。对于那些不喜欢压缩编码语法的人来说,它的简洁性更低。
ntk4

9

此答案最初是为回答一个问题而编写的,此问题已被标记为重复: 从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”来验证这一点,而“ is”不同于“等于”(==)。

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

不幸的是,我无法充分描述reversed()的工作方式。当列表传递给它时,它返回一个“ listreverseiterator”对象。出于实际目的,您可以将其视为创建其参数的反向副本。这是我推荐的解决方案。


4

如果要在迭代过程中执行其他任何操作,最好同时获得索引(这可以保证您能够引用它,例如,如果您有字典列表)和实际的列表项内容。

inlist = [{'field1':10, 'field2':20}, {'field1':30, 'field2':15}]    
for idx, i in enumerate(inlist):
    do some stuff with i['field1']
    if somecondition:
        xlist.append(idx)
for i in reversed(xlist): del inlist[i]

enumerate使您可以立即访问项目和索引。reversed这样一来,您以后要删除的索引就不会改变。


为什么在有字典列表的情况下获取索引比在其他任何类型的列表的情况下更相关?据我所知,这没有任何意义。
Mark Amery


4

这里的大多数答案都希望您创建列表的副本。我有一个用例,其中的列表很长(110K个项),而继续缩小列表会更明智。

首先,您需要将while循环替换为foreach循环

i = 0
while i < len(somelist):
    if determine(somelist[i]):
         del somelist[i]
    else:
        i += 1

i在if块中,不会更改的值,因为一旦删除了旧项,您将希望从相同的索引中获取新项的值。


3

您可以尝试反向进行循环,因此对于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元素)。


循环reversed(list(enumerate(some_list)))比自己计算索引更简单。
Mark Amery

@MarkAmery认为您不能以这种方式更改列表。
Queequeg '16

3

一种可能的解决方案,如果您不仅要删除某些内容,而且还希望在单个循环中对所有元素执行某些操作,则很有用:

alist = ['good', 'bad', 'good', 'bad', 'good']
i = 0
for x in alist[:]:
    if x == 'bad':
        alist.pop(i)
        i -= 1
    # do something cool with x or just print x
    print(x)
    i += 1

您实际上应该只使用理解力。它们更容易理解。
Beefster

如果我要删除bad事物,对其进行处理并且还对某个good事物进行处理,该怎么办?
阿列克谢

1
实际上,我意识到这里有些聪明之处,是您使用开放的切片(alist[:])复制了列表,并且由于您可能在做一些花哨的事情,因此它实际上有一个用例。好的修订是好的。接受我的投票。
Beefster '18

2

我需要做类似的事情,在我的情况下,问题是内存-我需要将列表中的多个数据集对象合并,然后再对它们做一些事情,作为一个新对象,并且需要摆脱要合并的每个条目避免重复所有操作并浪费内存。就我而言,将对象放在字典中而不是列表中可以很好地工作:

```

k = range(5)
v = ['a','b','c','d','e']
d = {key:val for key,val in zip(k, v)}

print d
for i in range(5):
    print d[i]
    d.pop(i)
print d

```


2

TLDR:

我写了一个库,使您可以执行此操作:

from fluidIter import FluidIterable
fSomeList = FluidIterable(someList)  
for tup in fSomeList:
    if determine(tup):
        # remove 'tup' without "breaking" the iteration
        fSomeList.remove(tup)
        # tup has also been removed from 'someList'
        # as well as 'fSomeList'

如果可能的话,最好使用另一种方法,该方法不需要在迭代时修改可迭代对象,但是对于某些算法,可能不是那么简单。因此,如果您确定确实希望原始问题中描述的代码模式,则可以。

应该适用于所有可变序列,而不仅仅是列表。


完整答案:

编辑:此答案中的最后一个代码示例给出了一个用例,说明了为什么有时您可能想在适当位置修改列表而不是使用列表理解。答案的第一部分用作如何在适当位置修改数组的教程。

从senderle的答案(针对相关问题)可以得出解决方案。这说明了在遍历已修改的列表时如何更新数组索引。即使解决方案列表被修改,下面的解决方案旨在正确跟踪数组索引。

fluidIter.py这里 下载https://github.com/alanbacon/FluidIterator,它只是一个文件,因此无需安装git。没有安装程序,因此您需要确保该文件位于您自己的python路径中。该代码是为python 3编写的,未经python 2的测试。

from fluidIter import FluidIterable
l = [0,1,2,3,4,5,6,7,8]  
fluidL = FluidIterable(l)                       
for i in fluidL:
    print('initial state of list on this iteration: ' + str(fluidL)) 
    print('current iteration value: ' + str(i))
    print('popped value: ' + str(fluidL.pop(2)))
    print(' ')

print('Final List Value: ' + str(l))

这将产生以下输出:

initial state of list on this iteration: [0, 1, 2, 3, 4, 5, 6, 7, 8]
current iteration value: 0
popped value: 2

initial state of list on this iteration: [0, 1, 3, 4, 5, 6, 7, 8]
current iteration value: 1
popped value: 3

initial state of list on this iteration: [0, 1, 4, 5, 6, 7, 8]
current iteration value: 4
popped value: 4

initial state of list on this iteration: [0, 1, 5, 6, 7, 8]
current iteration value: 5
popped value: 5

initial state of list on this iteration: [0, 1, 6, 7, 8]
current iteration value: 6
popped value: 6

initial state of list on this iteration: [0, 1, 7, 8]
current iteration value: 7
popped value: 7

initial state of list on this iteration: [0, 1, 8]
current iteration value: 8
popped value: 8

Final List Value: [0, 1]

上面我们pop在流体列表对象上使用了该方法。其他常见的迭代方法也实现,诸如del fluidL[i].remove.insert.append.extend。还可以使用切片修改列表(sort并且reverse未实现方法)。

唯一的条件是,您必须仅在适当位置修改列表,如果在任何时候fluidL或将l其重新分配给其他列表对象,代码将无法工作。原始fluidL对象仍将由for循环使用,但超出范围供我们修改。

fluidL[2] = 'a'   # is OK
fluidL = [0, 1, 'a', 3, 4, 5, 6, 7, 8]  # is not OK

如果要访问列表的当前索引值,则不能使用枚举,因为它仅计算for循环已运行了多少次。相反,我们将直接使用迭代器对象。

fluidArr = FluidIterable([0,1,2,3])
# get iterator first so can query the current index
fluidArrIter = fluidArr.__iter__()
for i, v in enumerate(fluidArrIter):
    print('enum: ', i)
    print('current val: ', v)
    print('current ind: ', fluidArrIter.currentIndex)
    print(fluidArr)
    fluidArr.insert(0,'a')
    print(' ')

print('Final List Value: ' + str(fluidArr))

这将输出以下内容:

enum:  0
current val:  0
current ind:  0
[0, 1, 2, 3]

enum:  1
current val:  1
current ind:  2
['a', 0, 1, 2, 3]

enum:  2
current val:  2
current ind:  4
['a', 'a', 0, 1, 2, 3]

enum:  3
current val:  3
current ind:  6
['a', 'a', 'a', 0, 1, 2, 3]

Final List Value: ['a', 'a', 'a', 'a', 0, 1, 2, 3]

FluidIterable类只是提供了原始列表对象的包装。可以将原始对象作为流体对象的属性进行访问,如下所示:

originalList = fluidArr.fixedIterable

可以if __name__ is "__main__":在底部的部分找到更多示例/测试fluidIter.py。这些值得一看,因为它们解释了在各种情况下会发生什么。例如:使用切片替换列表的大部分。或在嵌套for循环中使用(并修改)相同的可迭代对象。

就像我刚开始所说的那样:这是一个复杂的解决方案,将损害代码的可读性,并使调试更加困难。因此,应该首先考虑其他解决方案,例如David Raznick的答案中提到的列表理解。话虽如此,但我发现此类对我有用并且比跟踪需要删除的元素的索引更容易使用的时代。


编辑:如评论中所述,此答案并未真正提出此方法可提供解决方案的问题。我将在这里尝试解决:

列表理解提供了一种生成新列表的方法,但是这些方法倾向于孤立地查看每个元素,而不是整个列表的当前状态。

newList = [i for i in oldList if testFunc(i)]

但是,如果的结果testFunc取决于已添加的元素newList怎么办?还是仍然oldList可以添加其中的元素?也许仍然可以使用列表理解的方法,但是它会开始失去它的优雅感,对我来说,修改列表很容易。

下面的代码是遭受上述问题的一种算法示例。该算法将减少列表,以使任何元素都不是其他任何元素的倍数。

randInts = [70, 20, 61, 80, 54, 18, 7, 18, 55, 9]
fRandInts = FluidIterable(randInts)
fRandIntsIter = fRandInts.__iter__()
# for each value in the list (outer loop)
# test against every other value in the list (inner loop)
for i in fRandIntsIter:
    print(' ')
    print('outer val: ', i)
    innerIntsIter = fRandInts.__iter__()
    for j in innerIntsIter:
        innerIndex = innerIntsIter.currentIndex
        # skip the element that the outloop is currently on
        # because we don't want to test a value against itself
        if not innerIndex == fRandIntsIter.currentIndex:
            # if the test element, j, is a multiple 
            # of the reference element, i, then remove 'j'
            if j%i == 0:
                print('remove val: ', j)
                # remove element in place, without breaking the
                # iteration of either loop
                del fRandInts[innerIndex]
            # end if multiple, then remove
        # end if not the same value as outer loop
    # end inner loop
# end outerloop

print('')
print('final list: ', randInts)

输出和最终的简化列表如下所示

outer val:  70

outer val:  20
remove val:  80

outer val:  61

outer val:  54

outer val:  18
remove val:  54
remove val:  18

outer val:  7
remove val:  70

outer val:  55

outer val:  9
remove val:  18

final list:  [20, 61, 7, 55, 9]

很难说这是否设计过度,因为目前尚不清楚要解决什么问题。使用这种方法删除元素会实现哪些目标,some_list[:] = [x for x in some_list if not some_condition(x)]而没有实现?如果没有答案,为什么有人会相信,下载并使用带有错别字和注释掉的代码的600行库是比单行代码更好的解决方案?-1。
Mark Amery

@MarkAmery。这种情况的主要用例是,不仅要根据项目本身来决定是否应删除(或添加或移动)某项,还应根据列表中另一个项目的状态或列表的状态来确定是否要删除(或添加或移动)项目。整个。例如,列表推导无法编写类似some_list[:] = [x for x in some_list if not some_condition(y)]where y与list元素不同的列表x。也不可能写some_list[:] = [x for x in some_list if not some_condition(intermediateStateOf_some_list)]
共鸣

2

最有效的方法是列表理解,很多人都表现出他们的情况,当然,这也是一个很好的方式得到一个iterator通过filter

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

有一个例子(在元组中获得赔率):

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

警告:您也不能处理迭代器。迭代器有时比序列更好。


2

for循环将通过索引进行迭代。

认为你有一个清单,

[5, 7, 13, 29, 65, 91]

您使用了名为的列表变量lis。并且您使用它删除。

你的变量

lis = [5, 7, 13, 29, 35, 65, 91]
       0  1   2   3   4   5   6

在第5次迭代中

您的数字35不是素数,因此您将其从列表中删除。

lis.remove(y)

然后下一个值(65)移至上一个索引。

lis = [5, 7, 13, 29, 65, 91]
       0  1   2   3   4   5

所以第4次迭代完成的指针移到了第5位。

那就是为什么您的循环自从移入上一个索引以来不覆盖65。

因此,您不应将列表引用到另一个仍引用原始变量而不是副本的变量中。

ite = lis #dont do it will reference instead copy

所以使用 list[::]

现在你会给,

[5, 7, 13, 29]

问题是您在迭代过程中从列表中删除了一个值,然后列表索引将崩溃。

因此您可以尝试理解。

它支持所有可迭代的对象,例如list,tuple,dict,string等


这有助于我理解为什么我的代码失败了。
Wahid Sadik

2

如果要在迭代时从列表中删除元素,请使用while循环,以便可以在每次删除后更改当前索引和结束索引。

例:

i = 0
length = len(list1)

while i < length:
    if condition:
        list1.remove(list1[i])
        i -= 1
        length -= 1

    i += 1

1

其他答案是正确的,因为从要迭代的列表中删除通常不是一个好主意。反向迭代避免了陷阱,但是遵循这样做的代码要困难得多,因此通常最好使用列表推导或filter

但是,在一种情况下,可以安全地从要迭代的序列中删除元素:如果仅在迭代时删除一个项目。可以使用a return或a 来确保break。例如:

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

当您对符合条件的列表中的第一个项目执行副作用操作,然后立即从列表中删除该项目时,这通常比列表理解更容易理解。


1

我可以想到三种解决问题的方法。例如,我将创建一个随机的元组列表somelist = [(1,2,3), (4,5,6), (3,6,6), (7,8,9), (15,0,0), (10,11,12)]。我选择的条件是sum of elements of a tuple = 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.>构造一个新列表,其中包含不满足给定条件的元素(元组)(这与删除满足给定条件的list的元素相同)。以下是该代码:

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.>查找满足给定条件的索引,然后使用与这些索引相对应的remove元素(元组)。以下是该代码。

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更有效。我更喜欢method2。对于上述示例,time(method1) : time(method2) : time(method3) = 1 : 1 : 1.7


0

对于具有很大潜力的任何事物,我使用以下内容。

import numpy as np

orig_list = np.array([1, 2, 3, 4, 5, 100, 8, 13])

remove_me = [100, 1]

cleaned = np.delete(orig_list, remove_me)
print(cleaned)

那应该比其他任何东西都快得多。


根据我的测量,NumPy对于包含20个以上元素的列表开始变得更快,而对于包含1000个以上元素的大列表,NumPy的过滤速度提高了12倍以上。
乔治,

0

在某些情况下,您要做的不仅仅是一次过滤一个列表,还希望迭代时更改迭代。

这是一个示例,其中事先复制列表是不正确的,不可能进行反向迭代,并且列表理解也不是一种选择。

""" Sieve of Eratosthenes """

def generate_primes(n):
    """ Generates all primes less than n. """
    primes = list(range(2,n))
    idx = 0
    while idx < len(primes):
        p = primes[idx]
        for multiple in range(p+p, n, p):
            try:
                primes.remove(multiple)
            except ValueError:
                pass #EAFP
        idx += 1
        yield p

0

如果以后要使用新列表,只需将elem设置为None,然后在以后的循环中进行判断,就像这样

for i in li:
    i = None

for elem in li:
    if elem is None:
        continue

这样,您无需复制列表,而且更容易理解。


-1

提出一个数字列表,您想删除所有可被3整除的数字,

list_number =[i for i in range(100)]

使用list comprehension,这将创建一个新列表并创建新的内存空间

new_list =[i for i in list_number if i%3!=0]

使用lambda filter函数,这将创建结果新列表并占用内存空间

new_list = list(filter(lambda x:x%3!=0, list_number))

无需占用新列表和修改现有列表的存储空间

for index, value in enumerate(list_number):
    if list_number[index]%3==0:
        list_number.remove(value)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.