这个问题在这里已经有答案了:

当我用 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”条目?)。有谁有更优雅的解决方案,或者至少是更有效的解决方案?

与字典一起使用的一个怎么样?

有帮助吗?

解决方案

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

  1. 使用 filter:

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

  2. 使用列表理解:

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

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

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

其他提示

您还可以向后迭代列表:

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

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

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

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

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

使用 列表理解

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

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

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

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

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

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

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

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

两种解决方案, 筛选理解 需要建立一个新列表。我对 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

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

要回答有关使用字典的问题,您应该注意 Python 3.0 将包括 字典理解:

>>> {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'])

如果要就地过滤列表并且列表大小相当大,那么前面答案中提到的基于 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)]

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

  • 他们复制你的列表并返回新的,当原始列表非常大时,这将是低效的
  • 当选择项目的标准(在您的情况下,if name[-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 遍解决方案,而不是过滤器或列表推导式

在一套的情况下。

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

这是我的 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

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

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top