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

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

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


当前回答

这个答案最初是为了回答一个问题而写的,这个问题后来被标记为重复: 在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)

第一个问题是同时向remove()传递'a'和'b',但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副本更干净的解决方案。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'对象。出于实际目的,您可以将其视为创建其参数的反向副本。这是我推荐的解决方案。

其他回答

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

更多详情请点击这里

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

所以第四次迭代完成指针移动到第五次…

这就是为什么你的循环没有覆盖65,因为它已经移动到前一个索引。

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

ite = lis # Don’t do it will reference instead copy

所以使用list[::]复制列表。

现在你会付出,

[5, 7, 13, 29]

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

所以你可以试试列表理解。

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

你可以反过来尝试for- loops,这样对于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元素)。

如果稍后将使用新列表,可以简单地将elem设置为None,然后在后面的循环中判断它,如下所示

for i in li:
    i = None

for elem in li:
    if elem is None:
        continue

这样,你就不需要复制列表,而且更容易理解。

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

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。找到满足给定条件的索引,然后使用与这些索引对应的删除元素(元组)。下面是它的代码。

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更有效。我更喜欢方法2。对于上面的例子,time(method1): time(method2): time(method3) = 1:1: 1.7