如何快速删除列表中不符合约束条件的所有元素?

6

我有一个字符串列表。我有一个函数,给定一个字符串返回0或1。如何删除所有函数返回0的字符串?

5个回答

7

4
请注意,这将创建一个新的列表;它不会改变现有列表。 - intuited
4
"如果不是 fn(x)" 更符合惯用语,并且适用于 Python 认为真/假的任何值(您的函数返回0或1,但没有理由硬编码这种假设)。 - Beni Cherniavsky-Paskin

3
我建议使用生成器表达式而不是列表推导式,以避免可能出现的大型中间列表。
result = (x for x in l if f(x))
# print it, or something
print list(result)

就像列表推导式一样,这个操作不会直接修改你的原始列表。


1

编辑: 最佳答案请见底部。

如果您需要更改现有列表,例如因为您在其他位置有对它的引用,那么您将需要实际删除列表中的值。

我不知道Python中是否存在这样的函数,但类似这样的代码可以工作(未经测试):

def cull_list(lst, pred):
    """Removes all values from ``lst`` which for which ``pred(v)`` is false."""
    def remove_all(v):
        """Remove all instances of ``v`` from ``lst``"""
        try:
             while True:
                 lst.remove(v)
        except ValueError:
             pass

    values = set(lst)

    for v in values:
        if not pred(v):
            remove_all(v)

也许更高效的替代方案,可能对一些人来说看起来太像C代码了:

def efficient_cull_list(lst, pred):
    end = len(lst)
    i = 0
    while i < end:
        if not pred(lst[i]):
            del lst[i]
            end -= 1
        else:
            i += 1

编辑...: 正如Aaron在评论中指出的那样,可以使用类似的方法更清晰地完成此操作

def reversed_cull_list(lst, pred):
    for i in range(len(lst) - 1, -1, -1):
        if not pred(lst[i]):
            del lst[i]

...编辑

这些例程的诀窍在于,使用像enumerate这样的函数,正如其他回答者建议的那样,不会考虑到列表元素已被删除的事实。我所知道的唯一方法是手动跟踪索引而不是允许Python进行迭代。这里肯定会有速度上的妥协,因此最好还是做类似以下的事情:

lst[:] = (v for v in lst if pred(v))

其实,现在我想起来了,这是在列表上执行“原地”过滤的最明智的方法。生成器的值在填充lst元素之前被迭代,因此不存在索引冲突问题。如果您想使此更加明确,只需执行以下操作:

lst[:] = [v for v in lst if pred(v)]

我认为在这种情况下,从效率上讲不会有太大的区别。

如果我正确理解它们的工作原理,这两种方法中的任何一种都将使列表多出一个副本,因此,如果你正在处理一些“巨大的土地”,那么上面提到的任何一种真正的原地解决方案都会更好。


“enumerate” 确实会适当地调整索引。在尝试之前,我并没有意识到这一点。编写一个简单的循环,遍历从1到20的列表,并删除所有是3的偶数倍数的项。每次进行删除时,请打印列表和索引。您将删除索引2、4、5、6、10和12。 - D.Shawley
你可以通过倒序循环来简化循环。这样,当你删除一个元素时,就不必去减小(或者甚至根本不需要)end的值。同时,while中也没有分支。 - aaronasterling
@D.Shawley:要么你糊涂了,要么是我糊涂了。我编写了一些doctests来测试这个问题的各种解决方案;也许我在那方面搞错了什么? - intuited
@Aaron:啊,非常好的观点。我添加了一个反向顺序索引版本。 - intuited
@D.Shawley:重复你的实验,使用一个谓词来指定每个不是3的偶数倍的项目。 - John Machin
是的。需要使用 reversed。生成器解决方案可能是最好的,但它不会就地修改列表。 - D.Shawley

0

用生成器表达式:

alist[:] = (item for item in alist if afunction(item))

功能性:

alist[:] = filter(afunction, alist)

或者:

import itertools
alist[:] = itertools.ifilter(afunction, alist)

完全等价。

您还可以使用列表推导式:

alist = [item for item in alist if afunction(item)]

现场修改:

import collections

indexes_to_delete= collections.deque(
    idx
    for idx, item in enumerate(alist)
    if afunction(item))
while indexes_to_delete:
    del alist[indexes_to_delete.pop()]

0
>>> s = [1, 2, 3, 4, 5, 6]
>>> def f(x):             
...     if x<=2: return 0
...     else: return 1   
>>> for n,x in enumerate(s):
...     if f(x) == 0: s[n]=None
>>> s=filter(None,s)
>>> s
[3, 4, 5, 6]

这个不起作用:对于列表 [1, 2, 3, 4, 5, 6]f = lambda v: v <= 2,我得到了 [1, 2, 4, 6] - intuited

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接