从符合条件的可迭代项中获取第一项


303

我想从符合条件的列表中获得第一项。重要的是,生成的方法不能处理整个列表,这可能会很大。例如,以下功能是足够的:

def first(the_iterable, condition = lambda x: True):
    for i in the_iterable:
        if condition(i):
            return i

可以使用以下功能:

>>> first(range(10))
0
>>> first(range(10), lambda i: i > 3)
4

但是,我想不出一个好的内置式/单层式来让我这样做。如果不需要,我特别不想复制此功能。是否有内置的方法来获取与条件匹配的第一项?


Answers:


475

在Python 2.6或更高版本中:

如果StopIteration在找不到匹配元素的情况下希望被引发:

next(x for x in the_iterable if x > 3)

如果您希望返回default_value(例如None),请执行以下操作:

next((x for x in the_iterable if x > 3), default_value)

请注意,在这种情况下,您需要在生成器表达式周围加一对括号-只要生成器表达式不是唯一的参数,就需要使用括号。

我看到大多数答案都坚决地忽略了next内置函数,因此我认为出于某种神秘的原因,它们100%专注于2.5版及更早的版本-并未提及Python版本问题(但后来我没有看到该提及答案确实提到了next内置答案,这就是为什么我认为有必要自己提供答案的原因-至少以这种方式记录“正确版本”问题;-)。

在2.5中,如果迭代器立即完成.next(),则迭代器的方法立即提高StopIteration-即,对于您的用例,如果可迭代项中没有项满足条件。如果您不在乎(即,您知道必须至少有一个令人满意的项目),则只需使用.next()(在genexp上最好next,Python 2.6内置版本中的行及更高版本)。

如果您确实愿意的话,按照您在Q中首先指出的方法将内容包装在函数中似乎是最好的,尽管您建议的函数实现很好,但是您也可以使用itertoolsfor...: break循环或genexp,或者将a try/except StopIteration作为函数的主体,如各种答案所示。这些替代方案都没有太多附加值,因此我会选择您最初提出的简单的版本。


6
如您所描述的那样不起作用。StopIteration如果找不到任何元素,它将
触发

由于这是在搜索结果中出现的,因此我一直关注@Suor在2011年的评论,并对第一段进行了重新措辞,以使内容更加清楚。如果需要,请继续修改我的编辑。
科斯(Kos)

4
由于这是选择的答案,因此我不得不在此处共享选择正确的第一个元素的答案。简而言之:不应该鼓励使用next。
guyarad '16

1
@guyarad在该答案中提出的解决方案与仅使用next相比,其“神秘性”如何?反对next(在该答案中)的唯一论点是必须处理异常。真的吗?
亚伯拉罕TS

我的看法与我写评论的时间略有不同。我明白你的意思了。话虽这么说,要处理StopIteration真的不是很漂亮。最好使用一种方法。
Guyarad '18年

29

作为可重用,记录和测试的功能

def first(iterable, condition = lambda x: True):
    """
    Returns the first item in the `iterable` that
    satisfies the `condition`.

    If the condition is not given, returns the first item of
    the iterable.

    Raises `StopIteration` if no item satysfing the condition is found.

    >>> first( (1,2,3), condition=lambda x: x % 2 == 0)
    2
    >>> first(range(3, 100))
    3
    >>> first( () )
    Traceback (most recent call last):
    ...
    StopIteration
    """

    return next(x for x in iterable if condition(x))

具有默认参数的版本

@zorf建议使用此函数的版本,如果iterable为空或没有符合条件的项目,则可以具有预定义的返回值:

def first(iterable, default = None, condition = lambda x: True):
    """
    Returns the first item in the `iterable` that
    satisfies the `condition`.

    If the condition is not given, returns the first item of
    the iterable.

    If the `default` argument is given and the iterable is empty,
    or if it has no items matching the condition, the `default` argument
    is returned if it matches the condition.

    The `default` argument being None is the same as it not being given.

    Raises `StopIteration` if no item satisfying the condition is found
    and default is not given or doesn't satisfy the condition.

    >>> first( (1,2,3), condition=lambda x: x % 2 == 0)
    2
    >>> first(range(3, 100))
    3
    >>> first( () )
    Traceback (most recent call last):
    ...
    StopIteration
    >>> first([], default=1)
    1
    >>> first([], default=1, condition=lambda x: x % 2 == 0)
    Traceback (most recent call last):
    ...
    StopIteration
    >>> first([1,3,5], default=1, condition=lambda x: x % 2 == 0)
    Traceback (most recent call last):
    ...
    StopIteration
    """

    try:
        return next(x for x in iterable if condition(x))
    except StopIteration:
        if default is not None and condition(default):
            return default
        else:
            raise

6
如果使用方法包装它,至少要捕获StopIteration并引发EmptySequence错误。没有元素时会更漂亮。
Guyarad '18年

@guyarad这是一种ValueError吗?
Caridorc '18年

2
@guyarad StopIteration是python 中的规范“元素外”异常。我认为没有问题。我可能会使用默认值“ None”,它可以作为默认参数传递给函数。
鲍德里克

1
Baldrickk我觉得这不是迭代方法。您不会在迭代器的竞赛中称呼此人。但是我对此并不感到太强烈:)
Guyarad '18

1
应该有一个可选的默认参数,并且如果不提供该参数,则只有当序列中没有元素满足条件时才引发异常。
佐夫

28

该死的例外!

我喜欢这个答案。但是,由于在没有项目时next()引发StopIteration异常,因此我将使用以下代码段来避免异常:

a = []
item = next((x for x in a), None)

例如,

a = []
item = next(x for x in a)

将引发StopIteration异常;

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

13

与using相似ifilter,您可以使用生成器表达式:

>>> (x for x in xrange(10) if x > 5).next()
6

无论哪种情况,StopIteration如果没有元素满足您的条件,您可能都想抓住。

从技术上讲,我想您可以执行以下操作:

>>> foo = None
>>> for foo in (x for x in xrange(10) if x > 5): break
... 
>>> foo
6

这样可以避免产生try/except障碍。但这似乎对语法有些晦涩难懂。


+1:不要模糊,也不要辱骂。考虑到所有因素,最后一个看起来很干净。
S.Lott

6
最后一个根本不是干净的- for foo in genex: break只是一种foo = next(genex)没有明确分配工作的方式,只有在没有合理意义的操作被挤压的情况下才会出现这种情况。在Python中以失败代码结束而不是捕获异常通常是一件坏事。
Mike Graham

13

Python 3中最有效的方法是以下方法之一(使用类似的示例):

具有“领悟”风格:

next(i for i in range(100000000) if i == 1000)

警告:该表达式也适用于Python 2,但是在该示例中使用的range是在Python 3中返回一个可迭代对象,而不是像Python 2这样的列表(如果要在Python 2中构造一个可迭代对象,请使用xrange)。

请注意,该表达式避免在comprehension表达式中构造一个列表next([i for ...]),这会导致在过滤元素之前创建一个包含所有元素的列表,并且会导致处理整个选项,而不是停止迭代一次i == 1000

具有“实用”风格:

next(filter(lambda i: i == 1000, range(100000000)))

警告:这不会工作在Python 2,甚至取代rangexrange由于是filter创建一个列表,而不是一个迭代器(低效率),以及next功能只与迭代器的工作原理。

默认值

如其他响应中所述,next如果要避免在不满足条件时引发异常,则必须在函数中添加一个额外参数。

“实用”风格:

next(filter(lambda i: i == 1000, range(100000000)), False)

“领悟”风格:

使用这种样式时,您需要将comprehension表达式包含()在其中,以避免出现SyntaxError: Generator expression must be parenthesized if not sole argument

next((i for i in range(100000000) if i == 1000), False)


6

itertools模块包含用于迭代器的过滤器功能。可以通过调用next()它来获取过滤后的迭代器的第一个元素:

from itertools import ifilter

print ifilter((lambda i: i > 3), range(10)).next()

2
生成器表达式更简单。
Eric O Lebigot

1
ifilter和(imap对于已经存在要应用的函数的情况是有意义的,但是在这种情况下,仅使用生成器表达式会更有意义。
Mike Graham

这是最好的答案。避免列表内涵xahlee.info/comp/list_comprehension.html
麻省理工学院

6

对于较旧版本的Python,其中不存在下一个内置组件:

(x for x in range(10) if x > 3).next()

5

通过使用

(index for index, value in enumerate(the_iterable) if condition(value))

可以检查the_iterable中第一项的条件,并获得其索引,而无需评估the_iterable中的所有项

使用的完整表达式是

first_index = next(index for index, value in enumerate(the_iterable) if condition(value))

在这里,first_index假定在上述表达式中标识的第一个值的值。


4

这个问题已经有了很好的答案。我只加两分钱,因为我登陆这里试图找到解决自己问题的方法,这与OP非常相似。

如果要使用生成器查找与条件匹配的第一项的INDEX,只需执行以下操作:

next(index for index, value in enumerate(iterable) if condition)


0

您也可以argwhere在Numpy中使用该功能。例如:

i)在“ helloworld”中找到第一个“ l”:

import numpy as np
l = list("helloworld") # Create list
i = np.argwhere(np.array(l)=="l") # i = array([[2],[3],[8]])
index_of_first = i.min()

ii)查找第一个随机数> 0.1

import numpy as np
r = np.random.rand(50) # Create random numbers
i = np.argwhere(r>0.1)
index_of_first = i.min()

iii)找到最后一个随机数> 0.1

import numpy as np
r = np.random.rand(50) # Create random numbers
i = np.argwhere(r>0.1)
index_of_last = i.max()

-1

在Python 3中:

a = (None, False, 0, 1)
assert next(filter(None, a)) == 1

在Python 2.6中:

a = (None, False, 0, 1)
assert next(iter(filter(None, a))) == 1

编辑:我认为这很明显,但显然不是:而是None可以通过lambda检查条件来传递函数(或):

a = [2,3,4,5,6,7,8]
assert next(filter(lambda x: x%2, a)) == 3

-3

Oneliner:

thefirst = [i for i in range(10) if i > 3][0]

如果您不确定任何元素根据条件有效,则应将其括起来,try/except因为这[0]会引发IndexError


TypeError:“发电机”对象无法下标
Josh Lee 2010年

我的不好,应该是列表理解而不是生成器,已修复...谢谢!:)
Mizipzor

2
没有理由评估整个可迭代项(这可能是不可能的)。使用提供的其他解决方案之一更强大,更有效。
Mike Graham
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.