题
我正在迭代 Python 中的元组列表,并尝试在它们满足某些条件时将其删除。
for tup in somelist:
if determine(tup):
code_to_remove_tup
我应该用什么来代替 code_to_remove_tup
?我不知道如何以这种方式删除该项目。
解决方案
您可以使用列表推导来创建仅包含您不想删除的元素的新列表:
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)
其他提示
建议列表推导的答案几乎是正确的 - 除了它们构建一个全新的列表然后给它与旧列表相同的名称,它们不会修改旧的列表。这与您通过选择性删除所做的不同,如 @ Lennart的建议 - 它更快,但如果你的list是通过多个引用访问的,你只是重新引用其中一个引用而不改变列表对象本身会导致细微的,灾难性的错误。
幸运的是,获得列表推导的速度和就地更改所需的语义非常容易 - 只需代码:
somelist[:] = [tup for tup in somelist if determine(tup)]
请注意与其他答案的细微差别:这个不分配到一个名字 - 它分配给恰好是整个列表的列表切片,从而替换列表内容 在同一个Python列表对象中,而不是像其他答案一样重新安排一个引用(从前一个列表对象到新列表对象)。
您需要获取列表的副本并首先对其进行迭代,否则迭代将失败,结果可能会出现意外结果。
例如(取决于列表的类型):
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
[]
for i in range(len(somelist) - 1, -1, -1):
if some_condition(somelist, i):
del somelist[i]
你需要倒退,否则就像锯掉你所坐的树枝一样: - )
Python 2用户:用 xrange
替换 range
以避免创建硬编码列表
这样一个例子的最佳方法是列表理解
somelist = [tup for tup in somelist if determine(tup)]
如果您正在做一些比调用确定
函数更复杂的事情,我更喜欢构建一个新列表,并在我去的时候简单地追加它。例如
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)
官方 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()
回到最后: https://stackoverflow.com/a/1207460/895245这样时间效率高,但空间效率低,因为它在迭代期间保留了数组的副本。
使用
del
有一个索引: https://stackoverflow.com/a/1207485/895245由于它分配了数组副本,因此空间效率更高,但时间效率较低,因为 CPython 列出了 用动态数组实现.
这意味着删除项目需要将所有后续项目移回 1,即 O(N)。
一般来说,你只是想跑得更快 .append()
默认选项,除非内存是一个大问题。
Python 能做得更好吗?
看来这个特定的 Python API 可以改进。例如,将其与 Java 对应项进行比较 列表迭代器, ,这清楚地表明,除了迭代器本身之外,您无法修改正在迭代的列表,并且为您提供了在不复制列表的情况下执行此操作的有效方法。
也许根本原因是Python列表被假定为动态数组支持,因此任何类型的删除无论如何都是时间效率低下的,而Java有一个更好的接口层次结构 ArrayList
和 LinkedList
的实施 ListIterator
.
Python stdlib 中似乎也没有显式的链表类型: Python 链表
对于那些喜欢函数式编程的人:
somelist[:] = filter(lambda tup: not determine(tup), somelist)
或
from itertools import ifilterfalse
somelist[:] = list(ifilterfalse(determine, somelist))
如果当前列表项符合所需条件,也可以创建新列表。
这样:
for item in originalList:
if (item != badValue):
newList.append(item)
并避免使用新列表名称重新编码整个项目:
originalList[:] = newList
请注意,来自Python文档:
copy.copy(x)的 返回x的浅表副本。
copy.deepcopy(x)的 返回x的深层副本。
我需要用一个巨大的列表来执行此操作,并且复制列表似乎很昂贵,特别是因为在我的情况下,删除的数量与剩余的项目相比很少。我采用了这种低级别的方法。
array = [lots of stuff]
arraySize = len(array)
i = 0
while i < arraySize:
if someTest(array[i]):
del array[i]
arraySize -= 1
else:
i += 1
我不知道的是,将几个删除与复制大型列表相比有多高效。如果您有任何见解,请发表评论。
这个答案最初是针对一个被标记为重复的问题而写的: 从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副本。 reverse()函数:
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)]
不幸的是,我无法充分描述reverse()的工作原理。当列表传递给它时,它返回一个'listreverseiterator'对象。出于实际目的,您可以将其视为创建其参数的反向副本。这是我推荐的解决方案。
如果你想在迭代过程中做任何其他事情,那么获得索引(这可以保证你能够引用它,例如,如果你有一个dicts列表)和实际的列表项内容可能会很好。
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
是为了让您稍后删除的索引不会改变。
您可能希望使用 filter()
作为内置函数。
有关详细信息,请点击此处
您可以尝试反向循环,因此对于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元素)。
一种可能的解决方案,如果您不仅要删除一些内容,还要在单个循环中对所有元素执行某些操作,这非常有用:
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
这里的大多数答案都要求您创建列表的副本。我有一个用例,其中列表很长(110K项),而且改变列表更加明智。
首先,你需要用while循环替换foreach循环,
i = 0
while i < len(somelist):
if determine(somelist[i]):
del somelist[i]
else:
i += 1
i
的值在if块中没有改变,因为一旦删除旧项目,你将想要获得新项目FROM THE SAME INDEX的值。
我需要做类似的事情,在我的情况下,问题是内存 - 我需要合并列表中的多个数据集对象,在做了一些东西后,作为一个新对象,并需要摆脱每个条目我正在合并,以避免重复所有这些并炸毁记忆。在我的情况下,字典中的对象而不是列表工作正常:
```
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
```
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'
如果可能的话,最好使用另一种方法,在迭代迭代时不需要修改迭代,但对于某些算法,它可能不是那么直接。因此,如果您确定您确实需要原始问题中描述的代码模式,那么这是可能的。
应该对所有可变序列起作用,而不仅仅是列表。
完整答案:
编辑:此答案中的最后一个代码示例为 为什么 提供了一个用例,您有时可能希望修改列表而不是使用列表推导。答案的第一部分是 如何 的教程,可以在适当的位置修改数组。
解决方案来自发送方的此答案(针对相关问题)。这解释了在迭代已修改的列表时如何更新数组索引。下面的解决方案旨在正确跟踪数组索引,即使列表已被修改。
从此处 https:/下载
,它只是一个文件,所以不需要安装git。没有安装程序,因此您需要确保该文件位于您自己的python路径中。代码是为python 3编写的,在python 2上未经测试。 fluidIter.py
/github.com/alanbacon/FluidIterator
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
如果_ code__是 fluidIter.py
底部的__ main __&quot;:部分,则可以在中找到更多示例/测试。这些值得关注,因为它们解释了在各种情况下会发生什么。例如:使用切片替换列表的大部分。或者在嵌套for循环中使用(并修改)相同的iterable。
正如我所说的那样:这是一个复杂的解决方案,会损害代码的可读性并使调试更加困难。因此,应首先考虑其他解决方案,例如David Raznick的答案中提到的列表推导。话虽这么说,我发现这个类对我有用的时间比使用跟踪需要删除的元素的索引更容易使用。
编辑:正如评论中所提到的,这个答案实际上并不存在这种方法提供解决方案的问题。我将尝试在此解决这个问题:
列表推导提供了一种生成新列表的方法,但这些方法倾向于孤立地查看每个元素而不是t
其他答案是正确的,从您正在迭代的列表中删除通常是一个坏主意。反向迭代避免了陷阱,但是遵循执行该操作的代码要困难得多,因此通常最好使用列表解析或过滤器
。
但是,有一种情况是可以安全地从正在迭代的序列中删除元素:如果在迭代时只删除一个项目。这可以使用 return
或 break
来确保。例如:
for i, item in enumerate(lst):
if item % 4 == 0:
foo(item)
del lst[i]
break
当您对符合某些条件的列表中的第一个项目执行某些副作用,然后立即从列表中删除该项目时,这通常比列表理解更容易理解。
最有效的方法是列表理解,很多人都展示了它们的情况,当然,它也是通过 filter
获得 iterator
的好方法。
Filter
接收函数和序列。Filter
依次将传递的函数应用于每个元素,然后根据函数返回值是True
还是False <来决定是保留还是丢弃该元素/代码>
有一个例子(获取元组中的赔率):
list(filter(lambda x:x%2==1, (1, 2, 4, 5, 6, 9, 10, 15)))
# result: [1, 5, 9, 15]
警告:您也无法处理迭代器。迭代器有时比序列更好。
我可以想出三种解决问题的方法。作为一个例子,我将创建一个随机的元组列表 somelist = [(1,2,3),(4,5,6),(3,6,6),(7,8,9), (15,0,0),(10,11,12)]
。我选择的条件是元组的元素之和= 15
。在最终列表中,我们将只有那些总和不等于15的元组。
我选择的是一个随机选择的例子。 随时更改 元组列表以及我选择的条件。
方法1.&gt; 使用您建议的框架(其中一个填写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.&gt; 构造一个新列表,其中包含不满足给定条件的元素(元组)(这与删除满足给定条件的列表元素相同) 。以下是代码:
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.&gt; 查找满足给定条件的索引,然后使用与这些索引对应的删除元素(元组)。以下是该代码。
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
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等
对于任何有可能真正大的东西,我使用以下内容。
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)
这应该比其他任何东西快得多。
在某些情况下,您所做的不仅仅是简单地过滤列表中的一个项目,您希望在迭代时更改迭代。
这是一个预先复制列表不正确的例子,反向迭代是不可能的,列表理解也不是一个选项。
""" 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
如果稍后使用新列表,您只需将elem设置为None,然后在后面的循环中判断它,就像这样
for i in li:
i = None
for elem in li:
if elem is None:
continue
通过这种方式,您不需要复制列表,它更容易理解。
取消一个数字列表,你想要删除所有可以被3整除的no,
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)
您想立即创建列表副本,以便在迭代并删除该列表中符合特定条件的元组时将其作为参考。
然后,它取决于输出所需的列表类型,无论是已删除元组的列表还是未删除的元组列表。
正如大卫指出的那样,我建议使用列表理解来保留你不想删除的元素。
somelist = [x for x in somelist if not determine(x)]