Получить первый элемент из итерабельного, который соответствует условию
Я хотел бы получить первый элемент из списка, соответствующего условию. Важно, чтобы полученный метод не обрабатывал весь список, который может быть довольно большим. Например, достаточна следующая функция:
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
Однако я не могу придумать хороший встроенный/один-лайнер, чтобы позволить мне сделать это. Я не особо хочу копировать эту функцию, если мне это не нужно. Есть ли встроенный способ получить первый элемент, соответствующий условию?
Ответы
Ответ 1
В Python 2.6 или выше:
Если вы хотите, чтобы StopIteration
был поднят, если соответствующий элемент не найден:
next(x for x in the_iterable if x > 3)
Если вы хотите вместо default_value
(например, None
) <: t → :
next( (x for x in the_iterable if x>3), default_value)
Обратите внимание, что в этом случае вам нужна дополнительная пара круглых скобок вокруг выражения генератора - они нужны всегда, когда выражение генератора не является единственным аргументом.
Я вижу, что большинство ответов решительно игнорируют next
, и поэтому я предполагаю, что по какой-то загадочной причине они сосредоточены на 100% в версиях 2.5 и старше - без упоминания проблемы с версией Python (но потом я не вижу упоминания в ответах, которые упоминают встроенный next
, поэтому я счел нужным дать ответ сам - по крайней мере, проблема с "правильной версией" записывается таким образом; -).
В 2.5, метод итераторов .next()
сразу же поднимает StopIteration
, если итератор сразу заканчивает - т.е. для вашего варианта использования, если ни один элемент в итерабельности не удовлетворяет условию. Если вам неинтересно (т.е. Вы знаете, что должен быть хотя бы одним удовлетворительным элементом), то просто используйте .next()
(лучше всего на linux, строка для next
встроенного в Python 2.6 и выше).
Если вы позаботитесь, обертывание вещей в функции, как вы впервые указали в вашем Q, кажется лучшим, и, хотя реализация функции, которую вы предложили, просто прекрасна, вы можете использовать цикл itertools
, a for...: break
или ген xp или try/except StopIteration
как тело функции, как предлагали различные ответы. Там нет большой добавленной стоимости в любой из этих альтернатив, поэтому я бы пошел на совершенно простую версию, которую вы впервые предложили.
Ответ 2
В качестве многоразовой, документированной и проверенной функции
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))
Ответ 3
Аналогично использованию 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
. Но это кажется неясным и оскорбительным для синтаксиса.
Ответ 4
Проклятые исключения!
Я люблю этот ответ. Однако, поскольку 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
Ответ 5
Модуль itertools
содержит функцию фильтра для итераторов. Первый элемент отфильтрованного итератора может быть получен путем вызова next()
на нем:
from itertools import ifilter
print ifilter((lambda i: i > 3), range(10)).next()
Ответ 6
Я бы написал это
next(x for x in xrange(10) if x > 3)
Ответ 7
Для более старых версий Python, где следующий встроенный модуль не существует:
(x for x in range(10) if x > 3).next()
Ответ 8
Используя
(index for index, value in enumerate(the_iterable) if condition(value))
можно проверить условие значения первого элемента в файле_имя и получить его индекс без необходимости оценивать все элементы в переменной_имя.
Полное выражение для использования
first_index = next(index for index, value in enumerate(the_iterable) if condition(value))
Здесь first_index принимает значение первого значения, указанного в выражении, описанном выше.
Ответ 9
Наиболее эффективный способ в 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...])
выражении понимания next([i for...])
, что приведет к созданию списка со всеми элементами перед фильтрацией элементов и вместо этого будет обрабатывать все параметры. остановить итерацию, как только i == 1000
.
С "функциональным" стилем:
next(filter(lambda i: i == 1000, range(100000000)))
ВНИМАНИЕ: Это не работает в Python 2, даже если заменить range
на xrange
, так как filter
создает список вместо итератора (неэффективно), а next
функция работает только с итераторами.
Значение по умолчанию
Как упоминалось в других ответах, вы должны добавить дополнительный параметр в функцию next
если хотите избежать исключения, возникающего, когда условие не выполняется.
"функциональный" стиль:
next(filter(lambda i: i == 1000, range(100000000)), False)
стиль "понимания":
С этим стилем вам нужно SyntaxError: Generator expression must be parenthesized if not sole argument
понимания в ()
чтобы избежать SyntaxError: Generator expression must be parenthesized if not sole argument
:
next((i for i in range(100000000) if i == 1000), False)
Ответ 10
Поскольку вы запросили встроенный однострочный шрифт, это позволит избежать проблемы с исключением StopIteration
, хотя для этого требуется, чтобы ваш итеративный файл был небольшим, поэтому вы можете перечислить его в список, поскольку это единственный я знаю, что проглотит StopIteration и позволит вам заглянуть в значения:
(lambda x:x[0] if x else None)(list(y for y in ITERABLE if CONDITION))
(Если ни один элемент не соответствует, вы получите None
вместо исключения StopIteration
.)
Ответ 11
У этого вопроса уже есть отличные ответы. Я только добавляю свои два цента, потому что я приземлился здесь, пытаясь найти решение моей собственной проблемы, которая очень похожа на OP.
Если вы хотите найти ИНДЕКС первого элемента, соответствующего критериям с использованием генераторов, вы можете просто сделать:
next(index for index, value in enumerate(iterable) if condition)
Ответ 12
Вы можете также использовать argwhere
функцию в Numpy. Например:
i) Найдите первое "l" в "helloworld":
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
Oneliner:
thefirst = [i for i in range(10) if i > 3][0]
Если вы не уверены, что какой-либо элемент будет действителен в соответствии с критериями, вы должны заключить это с помощью try/except
, так как [0]
может поднять IndexError
.
Ответ 14
В 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