Вопрос-Ответ

Get the first item from an iterable that matches a condition

Получение первого элемента из итерируемого объекта, соответствующего условию

Я хотел бы получить первый элемент из списка, соответствующего условию. Важно, чтобы результирующий метод не обрабатывал весь список, который может быть довольно большим. Например, следующая функция является адекватной:

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