首页 文章

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

提问于
浏览
761

我正在迭代Python中的元组列表,并且如果它们符合某些条件,我会尝试删除它们 .

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

我应该用什么代替 code_to_remove_tup ?我无法弄清楚如何以这种方式删除项目 .

23 回答

  • 7

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

  • 2

    我需要做类似的事情,在我的情况下,问题是内存 - 我需要合并列表中的多个数据集对象,在做了一些事情后,作为一个新对象,并需要摆脱我合并的每个条目避免重复所有这些并炸毁内存 . 在我的情况下,字典中的对象而不是列表工作正常:

    
    ```java
    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
    
  • 9

    对于任何有可能真正大的东西,我使用以下内容 .

    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)
    

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

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

    你需要倒退,否则就像锯掉你正坐在的树枝上一样:-)

    Python 2用户:将 range 替换为 xrange 以避免创建硬编码列表

  • 0

    如果当前列表项符合所需条件,也可以智能地创建新列表 .

    所以:

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

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

    originalList[:] = newList
    

    请注意,来自Python文档:

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

  • 1

    在某些情况下,您所做的不仅仅是简单地过滤列表中的一个项目,您希望在迭代时更改迭代 .

    这是一个预先复制列表不正确的例子,反向迭代是不可能的,列表理解也不是一个选项 .

    """ 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
    
  • 45

    The official Python 2 tutorial 4.2. "for Statements" says

    如果您需要修改在循环内迭代的序列(例如复制所选项目),建议您先复制 . 迭代序列不会隐式地复制 . 切片表示法使这一点特别方便:>>> for w in words [:]:#循环遍历整个列表的切片副本 .
    ......如果len(w)> 6:
    ... words.insert(0,w)
    ...

    单词
    ['defenestrate','cat','window','defenestrate']

    这是建议在:https://stackoverflow.com/a/1207427/895245

    The Python 2 documentation 7.3. "The for statement" gives the same advice

    注意:当循环修改序列时有一个微妙之处(这只能发生在可变序列,即列表中) . 内部计数器用于跟踪下一个使用的项目,并在每次迭代时递增 . 当该计数器达到序列的长度时,循环终止 . 这意味着如果套件从序列中删除当前(或前一个)项,则将跳过下一个项(因为它获取已经处理的当前项的索引) . 同样,如果套件在当前项目之前的序列中插入项目,则下次循环时将再次处理当前项目 . 这可能导致令人讨厌的错误,可以通过使用整个序列的片段进行临时复制来避免,例如,对于[:]中的x:
    如果x <0:a.remove(x)

    Could Python do this better?

    似乎可以改进这个特定的Python API . 例如,将它与它的Java对应物ListIterator进行比较,这清楚地表明除了迭代器本身之外你不能修改被迭代的列表,并且在不复制列表的情况下为您提供了有效的方法 . 来吧,Python!

  • 632

    我可以想到三种方法来解决你的问题 . 作为一个例子,我将创建一个随机的元组列表 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的元组 .

    我选择的是一个随机选择的例子 . Feel free to change list of tuples 和我选择的 condition .

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

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

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

    Method 1 and method 2 are faster than method 3 . 方法2和方法3比方法1更有效 . 我是 prefer method2 . 对于上述示例, time(method1) : time(method2) : time(method3) = 1 : 1 : 1.7

  • 211

    建议列表推导的答案几乎是正确的 - 除了它们构建一个全新的列表然后给它与旧列表相同的名称,它们不会修改旧的列表 . 这与你通过选择性删除所做的不同,就像在@ Lennart的建议中一样 - 它更快,但如果你的列表是通过多个引用访问的,那么你只是重新安装其中一个引用而不是改变列表对象本身可能导致微妙的,灾难性的错误 .

    幸运的是,获得列表推导的速度和就地更改所需的语义非常容易 - 只需代码:

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

    注意与其他答案的细微差别:这个没有分配到一个名字 - 它分配给恰好是整个列表的列表切片,从而替换列表内容 within the same Python list object ,而不是仅仅重新安装一个引用(来自之前的列表对象)到新的列表对象)像其他答案 .

  • 35

    最有效的方法是列表理解,很多人展示他们的情况,当然,这也是一个通过 filter 获得 iterator 的好方法 .

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

    有一个例子(获取元组中的几率):

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

    Caution: You can also not handle iterators. Iterators are sometimes better than sequences.

  • 0

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

    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)
    
  • 4

    您可能希望使用 filter() 作为内置功能 .

    欲了解更多详情check here

  • 509

    如果你想在迭代期间做任何其他事情,那么获得索引(这可以保证你能够引用它,例如,如果你有一个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 是您're going to later delete don' t变化的指数 .

  • 0

    您需要获取列表的副本并首先迭代它,否则迭代将失败,结果可能是意外结果 .

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

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

    这个答案最初是为了回答一个被标记为重复的问题而写的:Removing coordinates from list on 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'来验证它,这与仅仅是“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副本 . 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'对象 . 出于实际目的,您可以将其视为创建其参数的反向副本 . 这是我推荐的解决方案 .

  • 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'
    

    如果可能的话,最好使用另一种方法,在迭代迭代时不需要修改迭代,但对于某些算法,它可能会不那么直截了当 . 因此,如果您确定您确实需要原始问题中描述的代码模式,那么这是可能的 .

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


    完整答案:

    编辑:此答案中的最后一个代码示例给出了 why 的用例,您有时可能希望修改列表而不是使用列表推导 . 答案的第一部分作为 how 的教程,可以在适当的位置修改数组 .

    解决方案来自senderle的this回答(相关问题) . 这解释了在迭代已修改的列表时如何更新数组索引 . 下面的解决方案旨在正确跟踪数组索引,即使列表已被修改 .

    here https://github.com/alanbacon/FluidIterator 下载 fluidIter.py ,它只是一个文件,因此无需安装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 . 也可以使用切片修改列表(未实现 sortreverse 方法) .

    唯一的条件是您必须只修改列表,如果在任何时候将 fluidLl 重新分配给不同的列表对象,则代码将无效 . 原始的 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
    

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

    正如我所说的那样:这是一个复杂的解决方案,会损害代码的可读性并使调试更加困难 . 因此,应该首先考虑其他解决方案,例如David Raznick的_407836中提到的列表理解 . 话虽这么说,我已经找到了这个类对我有用的时间,并且比跟踪需要删除的元素的索引更容易使用 .


    编辑:正如评论中所提到的,这个答案实际上并不存在这种方法提供解决方案的问题 . 我会尝试在这里解决这个问题:

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

    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]
    
  • 9

    其他答案是正确的,从列表中删除你最好使用列表理解或 filter 通常是一个坏主意 .

    但是,有一种情况是从您正在迭代的序列中删除元素是安全的:如果您重复迭代 . 这可以使用 returnbreak 来确保 . 例如:

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

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

  • 4

    您需要立即创建列表的副本,以便在迭代和删除该列表中符合特定条件的元组时将其作为参考 .

    然后,它取决于您想要输出的列表类型,无论是删除的元组列表还是a未删除的元组列表 .

    正如大卫指出的那样,我建议列表理解来保留你不想删除的元素 .

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

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

    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
    
  • 92

    这里的大多数答案都希望您创建列表的副本 . 我有一个用例,其中列表很长(110K项),并且更加明智地继续减少列表 .

    首先你需要 replace foreach loop with while loop

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

    i 的值在if块中未更改,因为一旦删除旧项目,您将希望获得新项目FROM THE SAME INDEX的值 .

  • 3

    这样一个例子的最佳方法是list comprehension

    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)
    
  • 34

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

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

    要么

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

    我需要用一个巨大的列表来执行此操作,并且复制列表似乎很昂贵,特别是因为在我的情况下,删除的数量与剩余的项目相比很少 . 我采用了这种低级方法 .

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

    我不知道的是,将几个删除与复制大型列表相比有多高效 . 如果您有任何见解,请评论 .

相关问题