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

Posted

技术标签:

【中文标题】在 Python 中从序列中删除项目的优雅方法? [复制]【英文标题】:Elegant way to remove items from sequence in Python? [duplicate] 【发布时间】:2010-09-06 07:32:20 【问题描述】:

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

使用字典的那个怎么样?

【问题讨论】:

您的代码确实删除了多个 Smiths,还是您对其进行了编辑? 【参考方案1】:

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

    使用filter

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

    使用列表推导:

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

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

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

【讨论】:

not name.endswith("Smith") 看起来好多了:-) 当然,如果你喜欢可读性什么的。 谁能给我解释一下[-5:]。如果你想检查整个列表会发生什么? @Sevenearths:“[-5:]”取名称的最后五个字符,因为我们想知道名称是否以“Smith”结尾。正如 Jochen 建议的那样,表达式“name[:-5]!='Smith'”可能写成“not name.endswith('Smith')”更易读。 别忘了提到使用name.endswith("Smith")而不是[-5:]带来的性能提升【参考方案2】:

您还可以向后迭代列表:

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

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

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

【讨论】:

这是一个非常创新的 Pythonic 解决方案。我喜欢它! 如果列表中有重复项(与谓词匹配),这是否有效? @Jon-Eric:是的,它有效。如果有重复,则删除第一个,列表缩小,reversed() 第二次产生相同的name。它是 O(n**2) 算法,不像 the accepted answer 使用 O(n) 算法。【参考方案3】:

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

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

【讨论】:

真正有趣的性能发现。您能否分享更多关于您的分析环境和评估方法的信息? 我敢打赌,您可以通过使用not name.endswith('Smith') 而不是每次迭代都创建一个切片来使其更快。无论哪种方式,如果不是您的回答,这是我可能永远找不到的有价值的信息,谢谢。 names[:] 建议对于使用 os.walk 过滤要遍历的目录名特别有用【参考方案4】:

使用a list comprehension

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

【讨论】:

似乎真的不适用于整数。 temprevengelist = "0-12354-6876" temprevengelist = temprevengelist.split('-') list = [x for x in temprevengelist if x[-5:] != 6876] @FahimAkhter:那是因为您将整数与字符串进行比较:在 Python 中,6876(整数)和 "6876"(字符串)是两个不同的值,并且不相等.尝试将x[-5:] != 6876 替换为x[-5:] != "6876"int(x[-5:]) != 6876【参考方案5】:

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

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

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

【讨论】:

【参考方案6】:

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

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

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

【讨论】:

【参考方案7】:
names = filter(lambda x: x[-5:] != "Smith", names);

【讨论】:

【参考方案8】:

filtercomprehension 这两种解决方案都需要构建一个新列表。我对 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

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

【讨论】:

我认为 names.remove(name) 可能是一个 O(n) 操作,这将使它成为一个 O(n^2) 算法。 我会亲自将我的 while 表达式写为 item 使用 del names[item] 或 names.pop(item) 可能比 names.remove(name) 更有效。这不太可能是 O(n),尽管我不知道它是如何工作的实际内部结构。【参考方案9】:

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

【讨论】:

你不需要将() 放在生成器表达式周围,除非它们不是唯一的参数,并且[] 使生成器表达式具体化一个列表,这使得dict([(k,v) for k,v in d.items()]) 慢得多比dict(((k,v) for k,v in d.items()))【参考方案10】:

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

【讨论】:

删除尾随项目:del original_list[new_list_size:]【参考方案11】:

过滤器和列表推导适用于您的示例,但它们有几个问题:

他们会复制您的列表并返回新列表,当原始列表非常大时,这将是低效的 当挑选物品的标准(在您的情况下,如果是名称[-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 解决方案而不是过滤器或列表推导

【讨论】:

如果您有多个“Smith”条目,这将无法正常工作,因为由于删除了较早的实例,要删除的其他实例已被转移。出于类似的原因,如果将第二个“Smith”条目添加到列表末尾,此算法会引发异常。 @Miqueella:你说得对,我的原始帖子因多个史密斯而失败,我修复了它以相反的顺序删除。谢谢。【参考方案12】:

在集合的情况下。

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

【讨论】:

【参考方案13】:

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

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

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

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

【讨论】:

对于某些问题,切换到不同的数据结构并不是一个真正的选择——特别是,如果您在创建元素集之前不知道过滤条件。例如,如果您正在执行某种搜索,并且想要修剪您的搜索空间,您通常不会提前知道修剪的适当截止条件。

以上是关于在 Python 中从序列中删除项目的优雅方法? [复制]的主要内容,如果未能解决你的问题,请参考以下文章

在 C# 中从序列化为 JSON 的 mongodb 文档中删除 ObjectId

在 cursoradapter 中从 ListView 中删除项目

Python 如何优雅的删除列表中的重复元素

如何在 python 或 php 中从 mp3 的 ID3 中删除版权标签?

python代码检测链表中的环并删除环

Python中7个不一样的代码写法