从与条件匹配的iterable中获取第一个项目

时间:2010-03-02 07:11:30

标签: python iterator

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

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

但是,我不能想到一个好的内置/单线程让我这样做。如果我不需要,我不特别想复制这个功能。是否有内置方法可以使第一个项目符合条件?

15 个答案:

答案 0 :(得分:361)

在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构建的行-in在Python 2.6及更高版本中。

如果你关心,你在Q中首先指出的函数包装内容似乎是最好的,虽然你提出的函数实现很好,但你也可以使用{{1} },一个itertools循环,或一个genexp,或一个for...: break作为函数的主体,正如各种答案所暗示的那样。这些替代品中没有太多附加值,所以我会选择你最初提出的简单版本。

答案 1 :(得分:22)

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

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))

答案 2 :(得分:13)

与使用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阻止。但这似乎有点模糊和滥用语法。

答案 3 :(得分:12)

该死的例外!

我爱this answer。但是,由于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

答案 4 :(得分:7)

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

使用“理解”样式:

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

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

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

使用“functional”样式:

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

警告:这在Python 2中不起作用,甚至将range替换为xrange,因为filter创建了一个列表而不是迭代器(效率低下) ),next函数仅适用于迭代器。

默认值

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

“functional”样式:

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

“理解”风格:

使用此样式,您需要使用()包围理解表达式以避免使用SyntaxError: Generator expression must be parenthesized if not sole argument

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

答案 5 :(得分:6)

itertools模块包含迭代器的过滤函数。可以通过调用next()来获取筛选迭代器的第一个元素:

from itertools import ifilter

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

答案 6 :(得分:6)

我会写这个

next(x for x in xrange(10) if x > 3)

答案 7 :(得分:6)

对于旧版本的Python,下一个内置不存在:

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

答案 8 :(得分: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 假定上面讨论的表达式中标识的第一个值。

答案 9 :(得分:3)

对于使用 Python 3.8 或更高版本的任何人,我建议使用 PEP 572 -- Assignment Expressions 中所述的“赋值表达式”。

if any((match := i) > 3 for i in range(10)):
    print(match)

答案 10 :(得分:1)

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

如果您想使用生成器找到符合条件的第一个项目的INDEX,您可以这样做:

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

答案 11 :(得分:0)

由于您已请求内置单行,这将避免StopIteration异常的问题,但它要求您的iterable很小,因此您可以将其转换为列表,因为这是我所知道的唯一一个将吞下StopIteration并让你偷看价值的构造:

(lambda x:x[0] if x else None)(list(y for y in ITERABLE if CONDITION))

(如果没有元素匹配,您将获得None而不是StopIteration例外。)

答案 12 :(得分:0)

您还可以在Numpy中使用argwhere函数。例如:

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()

答案 13 :(得分:-2)

Oneliner:

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

如果您不确定任何元素是否符合条件,则应将其与try/except括起来,因为[0]可以引发IndexError

答案 14 :(得分:-2)

在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