我在Python中迭代一个元组列表,并试图删除它们,如果它们满足某些条件。
for tup in somelist:
if determine(tup):
code_to_remove_tup
我应该用什么来代替code_to_remove_tup?我不知道怎么用这种方式把东西拿掉。
我在Python中迭代一个元组列表,并试图删除它们,如果它们满足某些条件。
for tup in somelist:
if determine(tup):
code_to_remove_tup
我应该用什么来代替code_to_remove_tup?我不知道怎么用这种方式把东西拿掉。
当前回答
如果您想在迭代期间做其他事情,那么最好同时获得索引(这保证您能够引用它,例如,如果您有一个字典列表)和实际的列表项内容。
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使您可以同时访问项和索引。反向是为了以后你要删除的索引不会改变。
其他回答
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'
如果可能的话,最好使用另一种方法,在迭代时不需要修改你的iterable,但对于某些算法来说,它可能没有那么直接。所以如果你确定你真的想要原始问题中描述的代码模式,这是可能的。
应该适用于所有可变序列,而不仅仅是列表。
完整的回答:
编辑:这个回答中的最后一个代码示例给出了一个用例,说明为什么有时希望就地修改列表,而不是使用列表理解式。答案的第一部分是关于如何适当修改数组的教程。
解决方案从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。还可以使用切片修改列表(不实现排序和反向方法)。
唯一的条件是您必须只修改列表,如果在任何时候fluidL或l被重新分配给不同的列表对象,代码将无法工作。原始的fluidL对象仍将被for循环使用,但将超出我们修改的范围。
i.e.
fluidL[2] = 'a' # is OK
fluidL = [0, 1, 'a', 3, 4, 5, 6, 7, 8] # is not OK
如果要访问列表的当前下标值,则不能使用enumerate,因为这只计算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
更多的例子/测试可以在fluidIter.py底部的if __name__ is "__main__":部分中找到。它们值得一看,因为它们解释了在不同情况下会发生什么。例如:使用切片替换列表中的大段。或者在嵌套的for循环中使用(并修改)相同的可迭代对象。
正如我在开始时所说的:这是一个复杂的解决方案,将损害代码的可读性,并使调试更加困难。因此,应该首先考虑其他解决方案,如David Raznick回答中提到的列表理解。话虽如此,我发现这个类对我来说很有用,而且比跟踪需要删除的元素的索引更容易使用。
编辑:正如评论中提到的,这个答案并没有真正提出这个方法提供解决方案的问题。我将在这里尝试解决这个问题:
列表推导式提供了一种生成新列表的方法,但这些方法倾向于孤立地查看每个元素,而不是将列表的当前状态作为一个整体。
i.e.
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]
您需要获取列表的副本并首先对其进行迭代,否则迭代将失败,可能会出现意想不到的结果。
例如(取决于列表的类型):
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的深拷贝。
对于任何有潜力做大的东西,我使用以下方法。
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)
这应该比其他任何东西都快得多。
您可以使用列表推导式创建一个新列表,其中只包含您不想删除的元素:
somelist = [x for x in somelist if not determine(x)]
或者,通过分配slice somlist[:],你可以改变现有的列表,只包含你想要的项:
somelist[:] = [x for x in somelist if not determine(x)]
如果对某个列表的其他引用需要反映更改,则此方法可能很有用。
你也可以使用itertools来代替理解函数。在Python 2中:
from itertools import ifilterfalse
somelist[:] = ifilterfalse(determine, somelist)
或者在python3中:
from itertools import filterfalse
somelist[:] = filterfalse(determine, somelist)