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

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

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