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

时间:2008-08-20 17:41:24

标签: python optimization set series

当我在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 个答案:

答案 0 :(得分:55)

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

  1. 使用filter

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

  2. 使用列表推导:

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

  3. 请注意,这两种情况都将谓词函数的计算值保持为True,因此您必须反转逻辑(即您说“保留没有姓氏史密斯的人”而不是“删除姓氏史密斯的人。)。

    编辑有趣......两个人分别发布了我在发帖时提出的两个答案。

答案 1 :(得分:37)

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

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

这样做的好处是它不会创建新列表(如filter或列表推导)并使用迭代器而不是列表副本(如[:])。

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

答案 2 :(得分:28)

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

>>> 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 *光束搜索,并使用它来从搜索光束中删除搜索点。)

答案 3 :(得分:10)

使用a list comprehension

list = [x for x in list if x[-5:] != "smith"]

答案 4 :(得分:4)

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

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

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

答案 5 :(得分:3)

过滤器对此非常棒。简单的例子:

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

编辑:Corey的列表理解也非常棒。

答案 6 :(得分:2)

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

答案 7 :(得分:2)

两种解决方案过滤器理解都需要构建新列表。我不太了解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

无论如何,对于短名单,我坚持使用之前提出的两种解决方案中的任何一种。

答案 8 :(得分:2)

要回答有关使用词典的问题,您应该注意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'])

答案 9 :(得分:2)

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

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的解决方案优于我的解决方案。它更pythonic,工作更快。所以,这是一个新的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)]

答案 10 :(得分:1)

过滤器和列表推导适用于您的示例,但它们存在一些问题:

  • 他们复制你的清单并返回新的清单,当原始清单真的很大时效率低下
  • 当选择项目的标准(在您的情况下,如果名称[-5:] =='史密斯')更复杂或有多个条件时,它们可能非常麻烦。

对于非常大的列表,您的原始解决方案实际上更有效,即使我们同意它更加丑陋。但是如果你担心你可以有多个'John 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

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

答案 11 :(得分:1)

在一组中。

toRemove = set([])  
for item in mySet:  
    if item is unwelcome:  
        toRemove.add(item)  
mySets = mySet - toRemove 

答案 12 :(得分:1)

这是我的filter_inplace实现,可用于从列表中就地过滤项目,我在找到此页面之前独立地想出了这个。它与PabloG发布的算​​法相同,只是更通用,因此您可以使用它来过滤列表,如果设置了反转,它也可以根据comparisonFunc从列表中删除{{1} };如果你愿意,可以使用一种反向过滤器。

True

答案 13 :(得分:-2)

嗯,这显然是您正在使用的数据结构的问题。例如,使用哈希表。有些实现支持每个键有多个条目,因此可以关闭最新元素,或删除所有元素。

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

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