【问题标题】:Find first element in a sequence that matches a predicate查找与谓词匹配的序列中的第一个元素
【发布时间】:2012-01-21 23:38:33
【问题描述】:

我想要一种惯用的方法来查找列表中与谓词匹配的第一个元素。

目前的代码相当难看:

[x for x in seq if predicate(x)][0]

我考虑过改成:

from itertools import dropwhile
dropwhile(lambda x: not predicate(x), seq).next()

但必须有更优雅的东西......如果它返回一个 None 值而不是在找不到匹配项时引发异常,那就太好了。

我知道我可以定义如下函数:

def get_first(predicate, seq):
    for i in seq:
        if predicate(i): return i
    return None

但是,如果有内置函数已经提供了相同的功能,那么开始用这样的实用函数填充代码是非常无味的(人们可能不会注意到它们已经存在,因此它们往往会随着时间的推移而重复) .

【问题讨论】:

标签: python predicate


【解决方案1】:

要查找序列seq 中与predicate 匹配的第一个元素:

next(x for x in seq if predicate(x))

或者简单地说:

Python 2

next(itertools.ifilter(predicate, seq))

Python 3

next(filter(predicate, seq))

如果谓词与任何元素都不匹配,这些将引发StopIteration 异常。


如果没有这样的元素,则返回None

next((x for x in seq if predicate(x)), None)

或者:

next(filter(predicate, seq), None)

【讨论】:

  • 或者您可以为next 提供第二个“默认”参数,而不是引发异常。
  • @fortran: next() 自 Python 2.6 起可用您可以阅读 What's New page 以快速熟悉新功能。
  • 我是 python 新手,阅读文档,ifilter 使用“yield”方法。我认为这意味着谓词在我们进行时会被懒惰地评估。即,我们不会在整个列表中运行谓词,因为我有一个有点昂贵的谓词函数,我只想迭代直到我们找到一个项目
  • @geekazoid: seq.find(&method(:predicate)) 或者更简洁的实例方法,例如:[1,1,4].find(&:even?)
  • ifilter 在 Python 3 中重命名为 filter
【解决方案2】:

您可以使用带有默认值的生成器表达式,然后 next 它:

next((x for x in seq if predicate(x)), None)

虽然对于这个单行,您需要使用 Python >= 2.6。

这篇颇受欢迎的文章进一步讨论了这个问题:Cleanest Python find-in-list function?

【讨论】:

    【解决方案3】:

    我认为您在问题中提出的任何一种解决方案都没有任何问题。

    在我自己的代码中,我会这样实现它:

    (x for x in seq if predicate(x)).next()
    

    () 的语法创建了一个生成器,这比使用[] 一次生成所有列表更有效。

    【讨论】:

    • 不仅如此——使用[],如果迭代器永远不会结束或者它的元素很难创建,你可能会遇到问题,它越晚......
    • 'generator' object has no attribute 'next' 在 Python 3 上。
    • @glglgl - 至于第一点(永无止境),我对此表示怀疑,因为参数是有限序列[更准确地说是根据 OP' 问题的列表]。至于第二个:再次,由于提供的参数是一个序列,因此在调用此函数时应该已经创建并存储了对象....还是我遗漏了什么?
    • @J.F.Sebastian - 谢谢,我不知道! :) 出于好奇,这个选择背后的设计原则是什么?
    • @mac - 为了与其他特殊方法的双下划线保持一致。见python.org/dev/peps/pep-3114
    【解决方案4】:

    J.F. Sebastian 的回答是最优雅的,但正如 fortran 所指出的那样需要 p​​ython 2.6。

    对于

    from itertools import repeat,ifilter,chain
    chain(ifilter(predicate,seq),repeat(None)).next()
    

    或者,如果您稍后需要一个列表(列表处理 StopIteration),或者您需要的不仅仅是第一个但仍然不是全部,您可以使用 islice 来完成:

    from itertools import islice,ifilter
    list(islice(ifilter(predicate,seq),1))
    

    更新: 虽然我个人使用了一个名为 first() 的预定义函数,它捕获一个 StopIteration 并返回 None,但对于上面的示例,这里有一个可能的改进:避免使用 filter / ifilter:

    from itertools import islice,chain
    chain((x for x in seq if predicate(x)),repeat(None)).next()
    

    【讨论】:

    • 哎呀!如果归根结底,我会做一个简单的“for”循环,里面有一个“if”——更容易阅读
    猜你喜欢
    • 2016-03-06
    • 2012-10-07
    • 2014-07-04
    • 1970-01-01
    • 2014-06-22
    • 2018-01-24
    相关资源
    最近更新 更多