57

当我在 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

这是低效的,相当丑陋的并且可能有错误(它如何处理多个“约翰史密斯”条目?)。有没有人有更优雅的解决方案,或者至少是更有效的解决方案?

一个与字典一起工作的怎么样?

4

14 回答 14

56

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

  1. 使用filter

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

  2. 使用列表推导:

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

请注意,这两种情况都保留谓词函数评估为的值True,因此您必须颠倒逻辑(即您说“保留没有姓氏的人史密斯”而不是“删除有姓氏的人史密斯”)。

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

于 2008-08-20T17:50:47.003 回答
37

您还可以向后迭代列表:

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

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

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

于 2008-10-08T01:24:09.233 回答
29

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

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

于 2011-01-09T14:41:40.307 回答
10

使用列表推导

list = [x for x in list if x[-5:] != "smith"]
于 2008-08-20T17:49:29.783 回答
4

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

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

与原始代码的唯一区别是在 for 循环中使用了names[:]代替。names这样,代码会遍历列表的(浅)副本,并且删除按预期工作。由于列表复制很浅,因此相当快。

于 2008-10-05T11:48:45.387 回答
3

过滤器会很棒。简单的例子:

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

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

于 2008-08-20T17:49:10.427 回答
2
names = filter(lambda x: x[-5:] != "Smith", names);
于 2008-08-20T17:48:56.730 回答
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

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

于 2008-08-20T18:20:33.970 回答
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'])
于 2008-10-07T14:33:49.420 回答
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)]
于 2012-04-02T16:20:45.203 回答
1

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

  • 他们复制您的列表并返回新列表,当原始列表非常大时,这将是低效的
  • 当挑选物品的标准(在您的情况下,如果姓名[-5:] =='smith')更复杂,或者有几个条件,它们可以真正繁琐。

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

于 2008-10-02T18:44:42.613 回答
1

在一套的情况下。

toRemove = set([])  
for item in mySet:  
    if item is unwelcome:  
        toRemove.add(item)  
mySets = mySet - toRemove 
于 2009-12-07T04:01:43.500 回答
1

这是我的filter_inplace实现,可用于就地过滤列表中的项目,在找到此页面之前,我自己独立想出了这个。它与 PabloG 发布的算法相同,只是变得更通用,因此您可以使用它来过滤列表,它还可以根据comparisonFuncif reversed 设置从列表中删除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
于 2013-03-15T14:12:56.133 回答
-2

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

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

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

于 2008-08-20T17:46:43.630 回答