it-swarm.com.ru

Найти первый элемент в последовательности, которая соответствует предикату

Я хочу идиоматический способ найти первый элемент в списке, который соответствует предикату.

Текущий код довольно уродлив:

[x for x in seq if predicate(x)][0]

Я думал об изменении его на:

from itertools import dropwhile
dropwhile(lambda x: not predicate(x), seq).next()

Но должно быть что-то более элегантное ... И было бы хорошо, если бы оно возвращало значение None, а не вызывало исключение, если совпадение не найдено.

Я знаю, что мог бы просто определить функцию как:

def get_first(predicate, seq):
    for i in seq:
        if predicate(i): return i
    return None

Но начинать заполнение кода такими служебными функциями довольно безвкусно (и люди, вероятно, не заметят, что они уже есть, поэтому они имеют тенденцию повторяться со временем), если есть встроенные модули, которые уже предоставляют то же самое.

145
fortran

next(x for x in seq if predicate(x))

Он вызывает StopIteration, если его нет.

next(ifilter(predicate, seq), None)

возвращает None, если такого элемента нет.

200
jfs

Вы можете использовать выражение генератора со значением по умолчанию, а затем next it:

next((x for x in seq if predicate(x)), None)

Хотя для этой однострочной вы должны использовать Python> = 2.6.

Эта довольно популярная статья дополнительно обсуждает эту проблему: Cleanest Python функция поиска в списке? .

83
Chewie

Я не думаю, что что-то не так с любым решением, которое вы предложили в своем вопросе.

В моем собственном коде я бы реализовал это так:

(x for x in seq if predicate(x)).next()

Синтаксис с () создает генератор, который более эффективен, чем генерация всего списка одновременно с [].

5
mac

Ответ Дж. Ф. Себастьяна самый элегантный, но требует python 2.6, как указал Фортран.

Для Python версии <2.6 вот лучшее, что я могу придумать:

from itertools import repeat,ifilter,chain
chain(ifilter(predicate,seq),repeat(None)).next()

В качестве альтернативы, если вам нужен список позже (list обрабатывает StopIteration), или вам нужно больше, чем просто первый, но все же не все, вы можете сделать это с помощью islice:

from itertools import islice,ifilter
list(islice(ifilter(predicate,seq),1))

ОБНОВЛЕНИЕ: хотя я лично использую предопределенную функцию first (), которая перехватывает StopIteration и возвращает None, вот возможное улучшение по сравнению с приведенным выше примером: избегайте использования filter/ifilter:

from itertools import islice,chain
chain((x for x in seq if predicate(x)),repeat(None)).next()
1
parity3