代码之家  ›  专栏  ›  技术社区  ›  postfuturist

在Python中从序列中删除项目的优雅方法?[副本]

  •  57
  • postfuturist  · 技术社区  · 17 年前

    当我用Python编写代码时,我经常需要根据一些标准从列表或其他序列类型中删除项目。我还没有找到一个优雅高效的解决方案,因为从当前迭代的列表中删除项目是不好的。例如,你不能这样做:

    for name in names:
        if name[-5:] == 'Smith':
            names.remove(name)
    

    我通常会做这样的事情:

    toremove = []
    for name in names:
        if name[-5:] == 'Smith':
            toremove.append(name)
    for name in toremove:
        names.remove(name)
    del toremove
    

    这是低效的,相当丑陋,可能存在漏洞(它如何处理多个“John Smith”条目?)。有人有更优雅的解决方案,或者至少是更有效的解决方案吗?

    用字典的怎么样?

    14 回复  |  直到 16 年前
        1
  •  55
  •   Ray    13 年前

    完成过滤的两种简单方法是:

    1. 使用 filter :

      names = filter(lambda name: name[-5:] != "Smith", names)

    2. 使用列表解析:

      names = [name for name in names if name[-5:] != "Smith"]

    请注意,这两种情况都保留谓词函数计算结果为的值 True ,所以你必须颠倒逻辑(即你说“保留那些没有姓史密斯的人”,而不是“删除那些姓史密斯的”)。

    编辑 有趣。..在我发布我的答案时,有两个人分别发布了我建议的两个答案。

        2
  •  37
  •   Xavier Martinez-Hidalgo    16 年前

    你也可以在列表上向后迭代:

    for name in reversed(names):
        if name[-5:] == 'Smith':
            names.remove(name)
    

    这样做的优点是它不会创建新的列表(如 filter 或列表理解),并使用迭代器而不是列表副本(如 [:] ).

    请注意,尽管在向后迭代时删除元素是安全的,但插入它们有点棘手。

        3
  •  28
  •   Edward Loper    14 年前

    显而易见的答案是约翰和其他几个人给出的,即:

    >>> names = [name for name in names if name[-5:] != "Smith"]       # <-- slower
    

    但这样做的缺点是,它创建了一个新的列表对象,而不是重用原始对象。我做了一些分析和实验,我想出的最有效的方法是:

    >>> names[:] = (name for name in names if name[-5:] != "Smith")    # <-- faster
    

    分配给“names[:]”基本上意味着“用以下值替换名称列表的内容”。它与仅仅分配名称不同,因为它不会创建新的列表对象。赋值的右侧是生成器表达式(注意使用括号而不是方括号)。这将导致Python在列表中迭代。

    一些快速分析表明,这比列表理解方法快约30%,比过滤方法快约40%。

    警告 :虽然这个解决方案比显而易见的解决方案更快,但它更模糊,并且依赖于更先进的Python技术。如果你真的使用它,我建议你附上一条评论。它可能只有在你真正关心这个特定操作的性能的情况下才值得使用(不管怎样,它都很快)。(在我使用这个的情况下,我正在进行A*波束搜索,并使用它从搜索波束中删除搜索点。)

        4
  •  10
  •   Nils Pipenbrinck    17 年前

    使用 a list comprehension

    list = [x for x in list if x[-5:] != "smith"]
    
        5
  •  4
  •   elifiner    16 年前

    有时过滤(使用过滤器或列表理解)不起作用。当其他对象持有对您正在修改的列表的引用,并且您需要就地修改列表时,就会发生这种情况。

    for name in names[:]:
        if name[-5:] == 'Smith':
            names.remove(name)
    

    与原始代码的唯一区别是使用了 names[:] 而不是 names 在for循环中。这样,代码会迭代列表的(浅)副本,删除操作会按预期进行。由于列表复制很浅,所以速度相当快。

        6
  •  3
  •   mk.    17 年前

    过滤器在这方面会很棒。简单示例:

    names = ['mike', 'dave', 'jim']
    filter(lambda x: x != 'mike', names)
    ['dave', 'jim']
    

    编辑: 科里的列表理解能力也很棒。

        7
  •  2
  •   pottedmeat    17 年前
    names = filter(lambda x: x[-5:] != "Smith", names);
    
        8
  •  2
  •   PabloG    17 年前

    这两种解决方案, 滤器 理解力 需要建立一个新的列表。我对Python的内部没有足够的了解,但我 认为 更传统(但不那么优雅)的方法可能更有效:

    names = ['Jones', 'Vai', 'Smith', 'Perez']
    
    item = 0
    while item <> len(names):
        name = names [item]
        if name=='Smith':
            names.remove(name)
        else:
            item += 1
    
    print names
    

    不管怎样,对于短名单,我坚持使用前面提出的两种解决方案中的任何一种。

        9
  •  2
  •   Jason Baker    16 年前

    要回答你关于使用字典的问题,你应该注意Python 3.0将包括 dict comprehensions :

    >>> {i : chr(65+i) for i in range(4)}
    

    同时,你可以这样做一个准字典理解:

    >>> dict([(i, chr(65+i)) for i in range(4)])
    

    或者作为一个更直接的答案:

    dict([(key, name) for key, name in some_dictionary.iteritems if name[-5:] != 'Smith'])
    
        10
  •  2
  •   Community CDub    8 年前

    如果列表应该被过滤到位,并且列表大小相当大,那么前面答案中提到的基于list.remove()的算法可能不合适,因为它们的计算复杂度为O(n^2)。在这种情况下,您可以使用以下非Python函数:

    def filter_inplace(func, original_list):
      """ Filters the original_list in-place.
    
      Removes elements from the original_list for which func() returns False.
    
      Algrithm's computational complexity is O(N), where N is the size
      of the original_list.
      """
    
      # Compact the list in-place.
      new_list_size = 0
      for item in original_list:
        if func(item):
          original_list[new_list_size] = item
          new_list_size += 1
    
      # Remove trailing items from the list.
      tail_size = len(original_list) - new_list_size
      while tail_size:
        original_list.pop()
        tail_size -= 1
    
    
    a = [1, 2, 3, 4, 5, 6, 7]
    
    # Remove even numbers from a in-place.
    filter_inplace(lambda x: x & 1, a)
    
    # Prints [1, 3, 5, 7]
    print a
    

    编辑: 实际上,解决方案在 https://stackoverflow.com/a/4639748/274937 优于我的溶液。它更像蟒蛇,工作速度更快。下面是一个新的filter_inplace()实现:

    def filter_inplace(func, original_list):
      """ Filters the original_list inplace.
    
      Removes elements from the original_list for which function returns False.
    
      Algrithm's computational complexity is O(N), where N is the size
      of the original_list.
      """
      original_list[:] = [item for item in original_list if func(item)]
    
        11
  •  1
  •   Ricardo Reyes    16 年前

    过滤器和列表解析对于您的示例来说是可以的,但它们有几个问题:

    • 他们复制你的列表并返回新的列表,当原始列表真的很大时,这将是低效的
    • 当选择项目的标准(在你的例子中,如果name[-5:]==“Smith”)更复杂,或者有几个条件时,它们可能会非常麻烦。

    你最初的解决方案实际上对非常大的列表更有效,即使我们可以同意它更丑陋。但如果你担心你会有多个“约翰·史密斯”,可以通过根据位置而不是价值删除来修复:

    names = ['Jones', 'Vai', 'Smith', 'Perez', 'Smith']
    
    toremove = []
    for pos, name in enumerate(names):
        if name[-5:] == 'Smith':
            toremove.append(pos)
    for pos in sorted(toremove, reverse=True):
        del(names[pos])
    
    print names
    

    我们不能在不考虑列表大小的情况下选择解决方案,但对于大列表,我更喜欢你的两步解决方案,而不是过滤器或列表理解

        12
  •  1
  •   CashMonkey    15 年前

    在一组的情况下。

    toRemove = set([])  
    for item in mySet:  
        if item is unwelcome:  
            toRemove.add(item)  
    mySets = mySet - toRemove 
    
        13
  •  1
  •   Cory Gross    12 年前

    这是我的 filter_inplace 可以用来从列表中过滤项目的实现,在找到这个页面之前,我独立地想出了这个。它与PabloG发布的算法相同,只是更通用,所以你可以用它来过滤列表,它还可以根据 comparisonFunc 如果设置了反转 True ;如果你愿意的话,可以说是一种反向过滤器。

    def filter_inplace(conditionFunc, list, reversed=False):
        index = 0
        while index < len(list):
            item = list[index]
    
            shouldRemove = not conditionFunc(item)
            if reversed: shouldRemove = not shouldRemove
    
            if shouldRemove:
                list.remove(item)
            else:
                index += 1
    
        14
  •  -2
  •   nlucaroni    17 年前

    好吧,这显然是您使用的数据结构的问题。例如,使用标签。一些实现支持每个键有多个条目,因此可以弹出最新元素,也可以删除所有元素。

    但这是,你要找到的解决方案是,通过不同的数据结构而不是算法来实现优雅。也许你可以做得更好,如果它是排序的,或者别的什么,但列表上的迭代是你在这里的唯一方法。

    编辑: 人们确实意识到他要求的是“效率”。..所有这些建议的方法都只是迭代列表,这与他建议的相同。