从匹配条件的可迭代对象中获取第一项

我想从匹配条件的列表中获得第一项。产生的方法不能处理整个列表,这一点很重要,因为列表可能相当大。例如,以下函数就足够了:

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

但是,我想不出一个好的内置/单行程序来让我这样做。如果没有必要,我不想复制这个函数。是否有一种内置的方法来获取匹配条件的第一项?

337026 次浏览

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

from itertools import ifilter


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

类似于使用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块。但这看起来有点模糊和滥用语法。

我会这样写

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

Oneliner:

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

如果你不确定根据条件,任何元素都是有效的,你应该用try/except将其括起来,因为[0]可以引发IndexError

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

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

Python 2.6+和Python 3:

如果你想在没有找到匹配的元素时引发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内置,这就是为什么我认为有必要自己提供一个答案——至少是“正确版本”;问题以这种方式记录;-)。

Python <= 2.5

如果迭代器立即结束,迭代器的.next()方法立即引发StopIteration——也就是说,对于你的用例,如果可迭代对象中没有项满足条件。如果你不在乎(即,你知道必须至少有一个令人满意的项),那么只需使用.next()(最好用于genexp,在Python 2.6或更好的版本中内置next行)。

如果你关心,像你第一次在Q中指出的那样,将东西包装在函数中似乎是最好的,而你提出的函数实现也很好,你可以选择使用itertoolsfor...: break循环,或genexp,或try/except StopIteration作为函数体,正如各种答案所建议的那样。这些替代方案都没有多少附加价值,所以我会选择你最初提出的极其简单的版本。

该死的例外!

我爱这个答案。然而,由于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

作为一个可重用、文档化和测试的函数

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建议这个函数的一个版本,如果可迭代对象为空或没有匹配条件的项,你可以有一个预定义的返回值:

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

在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

通过使用

(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假设上面讨论的表达式中确定的第一个值的值。

这个问题已经有了很好的答案。我只是说说我的意见,因为我来这里是想为我自己的问题找到一个解决方案,这和OP非常相似。

如果你想使用生成器找到匹配条件的第一项的INDEX,你可以简单地这样做:

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

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停止迭代。

使用“功能”样式:

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

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

默认值

正如在其他响应中提到的,如果你想避免在条件不满足时引发异常,你必须向函数next添加一个额外的参数。

“功能”风格:

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)

你也可以在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()

对于使用Python 3.8或更新版本的人,我建议使用“赋值表达式”;如赋值表达式中所述。

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

下面是三种方法的速度测试。Next()不是最快的方法。

from timeit import default_timer as timer


# Is set irreflexive?


def a():
return frozenset((x3, x3) for x3 in set([x1[x2] for x2 in range(2) for x1 in value]) if (x3, x3) in value) == frozenset()




def b():
return next((False for x1 in value if (x1[0], x1[0]) in value or (x1[1], x1[1]) in value), True)




def c():
for x1 in value:
if (x1[0], x1[0]) in value or (x1[1], x1[1]) in value:
return False
return True




times = 1000000
value = frozenset({(1, 3), (2, 1)})




start_time = timer()
for x in range(times):
a()
print("a(): Calculation ended after " + str(round((timer() - start_time) * 1000) / 1000.0) + " sec")


start_time = timer()
for x in range(times):
b()
print("b(): Calculation ended after " + str(round((timer() - start_time) * 1000) / 1000.0) + " sec")


start_time = timer()
for x in range(times):
c()
print("c(): Calculation ended after " + str(round((timer() - start_time) * 1000) / 1000.0) + " sec")

结果:

Calculation ended after 1.365 sec
Calculation ended after 0.685 sec
Calculation ended after 0.493 sec

我知道已经太迟了,但我的回答是:

def find_index(nums, fn):
return next(i for i, x in enumerate(nums) if fn(x))
print(find_index([1, 2, 3, 4], lambda n: n % 2 == 1))

如果你不想使用next(),你可以使用unpacking:

>>> a, *_ = filter(lambda e: e == 10, [7,8,9,10,11,12])
>>> a
10
>>> _
[]
>>> a, *_ = filter(lambda e: e == 1000, [7,8,9,10,11,12])
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: not enough values to unpack (expected at least 1, got 0)

注意,使用filter()相当于编写(item for item in iterable if condition) Python文档

如果你需要对边缘情况的支持,你可以这样写:

>>> a, *_ = [e for e in [7,8,9,10,11,12] if e == 1000] or [None]
>>> a
None
>>> _
[]

下面是带有基准的3个备选方案。

使用next()

一行程序:

values = list(range(1, 10000000))


value = next((x for x in values if x > 9999999), None)

使用函数

这是使用函数使用next()的替代方案,它大约快2%-5%:

values = list(range(1, 10000000))


def first(items):
for item in items:
if item > 9999999:  # Your condition
return item
return None  # Default value


value = first(values)

使用λ

这是一个在所有情况下都可以用来替换next()的函数。性能大约降低300%:

values = list(range(1, 10000000))


def first(items, condition, default = None):
for item in items:
if condition(item):
return item
return default


value = first(values, lambda x: x > 9999999, None)

基准

  • 功能:1 x
  • 下:1.02 - 1.05 x
  • λ:比;3 x

内存消耗相同。

这是基准