Получение первого элемента из итерируемого объекта, соответствующего условию
Я хотел бы получить первый элемент из списка, соответствующего условию. Важно, чтобы результирующий метод не обрабатывал весь список, который может быть довольно большим. Например, следующая функция является адекватной:
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+ и 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()
Метод iterators немедленно запускает работу, StopIteration
если итератор немедленно завершается - т. Е. Для вашего варианта использования, если ни один элемент в итерируемом объекте не удовлетворяет условию. Если вам все равно (т. Е. вы знаете, что должен быть хотя бы один удовлетворяющий элемент), тогда просто используйте .next()
(лучше всего в genexp, строка для next
встроенного в Python 2.6 и выше).
Если вам не все равно, лучше всего обернуть все в функцию, как вы сначала указали в своем Q, и хотя предложенная вами реализация функции просто великолепна, вы могли бы в качестве альтернативы использовать itertools
, for...: break
цикл, или genexp, или try/except StopIteration
в качестве тела функции, как предлагалось в различных ответах. Ни в одной из этих альтернатив нет особой добавленной стоимости, поэтому я бы выбрал предельно простую версию, которую вы предложили первой.
Ответ 2
Чертовы исключения!
Мне нравится ответ Алекса Мартелли. Однако, поскольку 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
Ответ 3
Как повторно используемая, документированная и протестированная функция
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
Ответ 4
Наиболее эффективным способом в 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, даже при замене range
на xrange
из-за того, что 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)