基于谓词从列表中删除一个元素

7

我希望从列表中删除一个包含'X''N'的元素。我需要对大型基因组进行操作。以下是一个例子:

输入:

codon=['AAT','XAC','ANT','TTA']

预期输出:
codon=['AAT','TTA']  

2
当你在Python参考页面中查找“list”中的“remove”时,你找到了什么?有什么发现吗?请查看Python文档,然后用具体内容更新你的问题。 - S.Lott
提示:从这里开始搜索:http://docs.python.org/library/stdtypes.html#sequence-types-str-unicode-list-tuple-buffer-xrange - S.Lott
我已编辑了问题标题,似乎有些人只看到了“从列表中删除元素”,而没有往下读。 - Ben James
10个回答

7

基础目的

>>> [x for x in ['AAT','XAC','ANT','TTA'] if "X" not in x and "N" not in x]
['AAT', 'TTA']

如果你有大量的数据,我建议你使用字典或集合

如果除了X和N还有许多其他字符,你可以这样做

>>> [x for x in ['AAT','XAC','ANT','TTA'] if not any(ch for ch in list(x) if ch in ["X","N","Y","Z","K","J"])]
['AAT', 'TTA']

注意: list(x) 可以简写为 x["X","N","Y","Z","K","J"] 可以简写为 "XNYZKJ",并参考 gnibbler 的答案,他做得最好。


你比我快一步了。我也有同样的答案。 - James Brooks
一定有更好的写条件的方法。例如像 len(set("x n".split()) | set(x)) == 0 这样简单易懂,如果你需要移除更多的东西,这种方法可能会慢一些。 - James Brooks
@James,是的,我也这么认为,请您也发表您的意见,他可以参考。谢谢。 - YOU
@James,我在我的答案中添加了使用集合的版本。 - John La Rooy

4
另一种不是最快但我认为读起来很流畅的方式
>>> [x for x in ['AAT','XAC','ANT','TTA'] if not any(y in x for y in "XN")]
['AAT', 'TTA']

>>> [x for x in ['AAT','XAC','ANT','TTA'] if not set("XN")&set(x)]
['AAT', 'TTA']

这种方法在长密码子中会更快(假设存在一些重复)。
codon = ['AAT','XAC','ANT','TTA']
def pred(s,memo={}):
    if s not in memo:
        memo[s]=not any(y in s for y in "XN")
    return memo[s]

print filter(pred,codon)

这里是James Brooks建议的方法,您需要测试一下哪种方法对于您的数据速度更快。
codon = ['AAT','XAC','ANT','TTA']
def pred(s,memo={}):
    if s not in memo:
        memo[s]= not set("XN")&set(s)
    return memo[s]

print filter(pred,codon)

对于这个示例密码子,使用集合的版本速度大约慢了10%。

你总是最短的, :-) +1 - YOU
不错!pred 的代码可以利用内置的 dict.setdefault 方法(正如我的答案所示);我猜想 setdefault 版本会更快。 - Eric O. Lebigot
@EOL,当然值得一试,但请记住,setdefault的第二个参数每次都会被评估。 - John La Rooy
这只是一个小问题,但你可以通过将测试更改为if not s is memo并将memo[s] =移动到其块中来删除冗余的return语句。 - Ben Blank
@Ben,谢谢,我已经改了,实际上似乎确实稍微快了一点。 - John La Rooy

3
也可以使用过滤器来实现此功能。
    lst = filter(lambda x: 'X' not in x and 'N' not in x, list)

2
任何重复整个列表的原因吗?怎么样:
>>> def pred(item, haystack="XN"):
...     return any(needle in item for needle in haystack)
...
>>> lst = ['AAT', 'XAC', 'ANT', 'TTA']
>>> idx = 0
>>> while idx < len(lst):
...     if pred(lst[idx]):
...         del lst[idx]
...     else:
...         idx = idx + 1
...
>>> lst
['AAT', 'TTA']

我知道列表推导式现在很流行,但如果列表很长,我们不想没有任何理由地重复它,对吧?你可以将其带到下一个步骤并创建一个好用的实用函数:

>>> def remove_if(coll, predicate):
...     idx = len(coll) - 1
...     while idx >= 0:
...         if predicate(coll[idx]):
...             del coll[idx]
...         idx = idx - 1
...     return coll
...
>>> lst = ['AAT', 'XAC', 'ANT', 'TTA']
>>> remove_if(lst, pred)
['AAT', 'TTA']
>>> lst
['AAT', 'TTA']

这是C开发人员思考问题的方式。使用Python,特别是列表,您已经牺牲了很多效率,那么为什么要关心原地修改列表呢? - Ben James
实际上,一个C++开发者……我认为就地修改是OP所要求的。即使数据集很大,我通常也关心效率,因为大量内存复制通常意味着几个小时而不是几分钟,或者如果你正在处理大型数据集时完全耗尽内存。更不用说列表理解和一堆notand在一起隐藏了你真正在做什么。与其从列表中删除几个项目,不如看到没有项目的列表副本。我想我更喜欢简单和清晰。 - D.Shawley
1
第二个例子中应该是 idx = idx - 1。使用列表推导式或 filter() 的原因是循环在 C 中,所以通常更快。 - John La Rooy
谢谢gnibbler。我从来没有想过推导式和内置函数会在本地代码中循环。有趣的想法。 - D.Shawley
实际上,这种方法的时间复杂度是O(n^2),而获取一个新的过滤列表并用新列表替换旧列表的内容则为O(n) - ead
请查看我的回答,其中提供了一个不使用列表推导的O(n)版本。 - undefined

2
  1. filter(lambda x: 'N' not in x or 'X' not in x, your_list)
  2. your_list = [x for x in your_list if 'N' not in x or 'X' not in x]
这两行代码都是用于过滤列表中的元素。第一行使用了Python内置函数filter()和lambda表达式,该表达式表示如果元素中不包含字母N或字母X,则保留该元素。第二行则使用了列表推导式,与第一行效果相同。

这里的条件是错误的。它应该是'N'不在x中且'X'不在x中,或者是not ('N' in x or 'X' in x) - Lee D

1

我非常喜欢gnibbler的记忆化方法。在大数据集上,使用记忆化的任一方法应该是完全相同的快速,因为记忆字典应该很快就会被填满,实际测试应该很少进行。考虑到这一点,我们应该能够进一步提高大数据集的性能。(这对于非常小的数据集来说有一些成本,但谁关心那些呢?)以下代码只需要在存在时查找一次记忆字典中的项目,而不是两次(一次确定成员身份,另一次提取值)。

codon = ['AAT', 'XAC', 'ANT', 'TTA']
def pred(s,memo={}):
    try:
        return memo[s]
    except KeyError:
        memo[s] = not any(y in s for y in "XN")
    return memo[s]

filtered = filter(pred, codon)

如我所说,如果基因组很大(或者至少不是极小),这应该会更快。

如果您不想复制列表,而只是遍历过滤后的列表,请执行以下操作:

for item in (item for item in codon if pred):
    do_something(item)

你使用的 try...except 结构与内置的 dict.setdefault 函数非常相似(请参见我的答案中的示例)。 - Eric O. Lebigot
实际上有一个微妙的区别:使用 setdefault 时,“默认”表达式无论如何都会被评估!这个表达式包含了我们试图避免的缓慢谓词,并使记忆化无用。而 try/except 版本只在出现 KeyError 时才评估谓词。 - musicinmybrain

1
如果你正在处理非常大的列表,你希望使用尽可能少地遍历整个列表的方法。
你最好的选择可能是创建一个过滤函数,并使用itertools.ifilter,例如:
new_seq = itertools.ifilter(lambda x: 'X' in x or 'N' in x, seq)

这实际上推迟了对列表中每个元素的测试,直到您实际迭代它。请注意,您可以像原始序列一样过滤已过滤的序列:

new_seq1 = itertools.ifilter(some_other_predicate, new_seq)

编辑:

另外,一些测试表明,在集合中记忆找到的条目很可能提供足够的改进,值得这样做,并且使用正则表达式可能不是正确的方法:

seq = ['AAT','XAC','ANT','TTA']
>>> p = re.compile('[X|N]')
>>> timeit.timeit('[x for x in seq if not p.search(x)]', 'from __main__ import p, seq')
3.4722548536196314
>>> timeit.timeit('[x for x in seq if "X" not in x and "N" not in x]', 'from __main__ import seq')
1.0560532134670666
>>> s = set(('XAC', 'ANT'))
>>> timeit.timeit('[x for x in seq if x not in s]', 'from __main__ import s, seq')
0.87923730529996647

然而,new_seq是一个生成器,如果你只想循环它,那就没问题,但有时你需要一个列表。 - John La Rooy
如果你正在处理非常大的列表,任何使用列表而不是可迭代对象的地方都可能成为问题。你可以轻松地将生成器转换为列表——list(new_seq)就能做到这一点——但它会访问列表中的每个元素来实现转换。如果你正在处理数百万个密码子的列表,你真的只想这样做一次。 - Robert Rossney

0
如果您想修改实际列表而不是创建一个新的,这里有一组简单的函数可以使用:
from typing import TypeVar, Callable, List
T = TypeVar("T")

def list_remove_first(lst: List[T], accept: Callable[[T], bool]) -> None:
    for i, v in enumerate(lst):
        if accept(v):
            del lst[i]
            return


def list_remove_all(lst: List[T], accept: Callable[[T], bool]) -> None:
    for i in reversed(range(len(lst))):
        if accept(lst[i]):
            del lst[i]
    

0

如S.Mark所请求,这是我的版本。它可能会慢一些,但可以更轻松地更改所删除的内容。

def filter_genome(genome, killlist = set("X N".split()):
    return [codon for codon in genome if 0 == len(set(codon) | killlist)]

这个答案中的集合运算符应该是&,而不是| - Lee D

0

使用正则表达式在同一字符串中搜索某个字符比多次搜索快得多(渐进地):实际上,使用正则表达式时,序列最多只需读取一次(而不是像gnibbler的原始答案中未找到字母时需要读取两次)。通过gnibbler的记忆化,正则表达式方法的读取如下:

import re
remove = re.compile('[XN]').search

codon = ['AAT','XAC','ANT','TTA']
def pred(s,memo={}):
    if s not in memo:
        memo[s]= not remove(s)
    return memo[s]

print filter(pred,codon)

这应该比使用“in s”或“set”检查(即,对于足够长的字符串s,上面的代码应该更快)更快(渐近地)。

我最初认为gnibbler的答案可以用dict.setdefault()更快、更紧凑地编写:

codon = ['AAT','XAC','ANT','TTA']
def pred(s,memo={}):
    return memo.setdefault(s, not any(y in s for y in "XN"))

print filter(pred,codon)

然而,正如gnibbler所指出的那样,setdefault中的值始终会被评估(尽管原则上,它只能在未找到字典键时评估)。


请记住,setdefault函数的第二个参数始终会被计算,从而使记忆化失效。 - John La Rooy

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