Получить первый элемент из итерируемого, который соответствует условию - PullRequest
238 голосов
/ 02 марта 2010

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

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

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

Ответы [ 14 ]

370 голосов
/ 02 марта 2010

В 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-версии (но тогда я не вижу упоминания в ответах, что do упоминает встроенный next, поэтому я подумал, что необходимо дать ответ сам - по крайней мере, "правильный" версия "проблема попадает на запись таким образом; -).

В 2.5 метод итераторов .next() немедленно вызывает StopIteration, если итератор немедленно завершает работу, т. Е. Для вашего случая использования, если ни один элемент в итерации не удовлетворяет условию. Если вам все равно (то есть вы знаете, что должен быть хотя бы одним удовлетворительным элементом), тогда просто используйте .next() (лучше всего для genexp, строка для next встроенного в Python 2.6 и лучше).

Если вы делаете заботу, лучше всего обернуть вещи в функцию, как вы сначала указали в Q, и, хотя реализация функции, которую вы предложили, просто прекрасна, вы можете альтернативно использовать itertools, a for...: break loop, или genexp, или try/except StopIteration в качестве тела функции, как предлагали различные ответы. Ни в одной из этих альтернатив нет особой выгоды, поэтому я бы выбрал совершенно простую версию, которую вы впервые предложили.

23 голосов
/ 19 февраля 2016

Как многоразовая, документированная и протестированная функция

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))
13 голосов
/ 20 января 2016

Черт, исключения!

Я люблю этот ответ . Однако, поскольку 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
13 голосов
/ 02 марта 2010

Аналогично использованию 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. Но это кажется неясным и оскорбительным для синтаксиса.

7 голосов
/ 08 января 2018

Наиболее эффективный способ в 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)
6 голосов
/ 02 марта 2010

Для более старых версий Python, где нет следующего встроенного:

(x for x in range(10) if x > 3).next()
6 голосов
/ 02 марта 2010

Я бы написал это

next(x for x in xrange(10) if x > 3)
6 голосов
/ 02 марта 2010

Модуль itertools содержит функцию фильтра для итераторов. Первый элемент отфильтрованного итератора можно получить, вызвав для него next():

from itertools import ifilter

print ifilter((lambda i: i > 3), range(10)).next()
5 голосов
/ 21 ноября 2016

Используя

(index for index, value in enumerate(the_iterable) if condition(value))

можно проверить условие значения первого элемента в the_iterable и получить его index без необходимости оцените все элементы в the_iterable .

Полное выражение для использования:

first_index = next(index for index, value in enumerate(the_iterable) if condition(value))

Здесь first_index предполагает значение первого значения, указанного в приведенном выше выражении.

1 голос
/ 10 мая 2017

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

Если вы хотите найти УКАЗАТЕЛЬ первого элемента, соответствующего критерию, с помощью генераторов, вы можете просто сделать:

next(index for index, value in enumerate(iterable) if condition)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...