6

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

17

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

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

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

5 ответ(ов)

0

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

def first(iterable, condition=lambda x: True):
    """
    Возвращает первый элемент из `iterable`, который
    удовлетворяет заданному `condition`.

    Если условие не задано, возвращает первый элемент 
    из iterable.

    Вызывает `StopIteration`, если не найдено ни одного элемента, 
    удовлетворяющего условию.

    >>> 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 предложил версию этой функции, в которой можно задать предопределенное значение, если iterable пуст или не содержит элементов, удовлетворяющих условию:

def first(iterable, default=None, condition=lambda x: True):
    """
    Возвращает первый элемент из `iterable`, который
    удовлетворяет заданному `condition`.

    Если условие не задано, возвращает первый элемент 
    из iterable.

    Если задан аргумент `default` и iterable пуст,
    или если в нем нет элементов, удовлетворяющих условию, 
    то возвращается аргумент `default`, если он соответствует условию.

    Аргумент `default`, равный None, эквивалентен его отсутствию.

    Вызывает `StopIteration`, если не найдено ни одного элемента, 
    удовлетворяющего условию, и default не задан или не соответствует 
    условию.

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

В этой версии функции добавлена возможность указать значение по умолчанию при отсутствии подходящих элементов в итерабельном объекте. Если iterable пуст и аргумент default не удовлетворяет условию, будет вызвано исключение StopIteration. В противном случае будет возвращено значение default, если оно соответствует переданному условию.

0

Наиболее эффективные способы в 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)
0

Чтобы перевести данный код на русский, можно сформулировать ответ следующим образом:

Если вы хотите получить следующее число из диапазона от 0 до 9, которое больше 3, вы можете использовать следующую конструкцию:

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

Обратите внимание, что в Python 3 замените xrange на range, так как xrange был удалён в третьей версии Python. Этот код вернёт первое число из диапазона, соответствующее вашему условию. В данном случае, результатом будет число 4.

0

Похоже, вы хотите использовать генераторное выражение, аналогично 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. Тем не менее, он выглядит довольно странно и может восприниматься как злоупотребление синтаксисом.

0

В более ранних версиях Python, в которых нет встроенной функции next(), можно использовать функцию next() на генераторах, как показано ниже.

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

Вот пример, который можно использовать:

gen = (x for x in range(10) if x > 3)
first_element = gen.__next__()  # Здесь мы вызываем метод __next__() на генераторе
print(first_element)

Если вы работаете с Python 2.x, можно просто использовать gen.next():

gen = (x for x in range(10) if x > 3)
first_element = gen.next()  # Здесь используется метод next() для генератора
print(first_element)

Обратите внимание, что использование метода next() в Python 2.7 возвращает следующий элемент генератора, или вызывает исключение StopIteration, если элементов больше нет. Учтите, перед выполнением кода лучше обработать это исключение, чтобы избежать ошибок в случае отсутствия подходящих элементов.

Чтобы ответить на вопрос, пожалуйста, войдите или зарегистрируйтесь