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

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

我应该用什么来代替code_to_remove_tup?我不知道怎么用这种方式把东西拿掉。


当前回答

您可能希望使用内置的filter()。

更多详情请点击这里

其他回答

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 i, item in enumerate(lst):
    if item % 4 == 0:
        foo(item)
        del lst[i]
        break

当您对满足某些条件的列表中的第一个项执行一些具有副作用的操作,然后立即从列表中删除该项时,这通常比列表推导式更容易理解。

如果当前列表项满足所需的条件,那么创建一个新列表可能是聪明的做法。

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)

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

我需要做一些类似的事情,在我的情况下,问题是内存——我需要在一个列表中合并多个数据集对象,在对它们做了一些事情之后,作为一个新对象,并且需要摆脱我正在合并的每个条目,以避免重复所有它们并增加内存。在我的情况下,对象在一个字典而不是一个列表工作得很好:

```

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

```