Знайдіть перший елемент у послідовності, яка відповідає присудку


171

Я хочу ідіоматичний спосіб знайти перший елемент у списку, який відповідає присудку.

Поточний код досить некрасивий:

[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

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


3
Окрім того, що його задають пізніше, ніж " функція пошуку послідовності пітона ", це питання має набагато кращу назву .
Вовк

Відповіді:


249

Щоб знайти перший елемент у послідовності, seqяка відповідає predicate:

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

Або ( itertools.ifilterна Python 2) :

next(filter(predicate, seq))

Він піднімається, StopIterationякщо його немає.


Щоб повернутися, Noneякщо такого елемента немає:

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

Або:

next(filter(predicate, seq), None)

27
Або ви можете надати другий аргумент «за замовчуванням», nextякий використовується замість підвищення винятку.
Карл Кнечтел

2
@fortran: next()доступний з Python 2.6. Ви можете прочитати сторінку Що нового, щоб швидко ознайомитися з новими можливостями.
jfs

1
Я новачок в пітоні і читаю документи, а ifilter використовує метод "урожайності". Я припускаю, що це означає, що присудок оцінюється ліниво, коли ми йдемо. тобто ми не запускаємо присудок через весь список, оскільки у мене є функція предиката, яка коштує трохи дорого, і я хочу лише повторити до моменту, коли ми знайдемо предмет
Каннан Еканат

2
@geekazoid: seq.find(&method(:predicate))або навіть більш стислий приклад, наприклад:[1,1,4].find(&:even?)
jfs

16
ifilterбуло перейменовано на filterPython 3.
tsauerwein

92

Ви можете використовувати вираз генератора зі значенням за замовчуванням, а потім nextйого:

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

Хоча для цього одного вкладиша вам потрібно використовувати Python> = 2.6.

Ця досить популярна стаття далі обговорює це питання: Найясніша функція пошуку в списку Python? .


8

Я не думаю, що немає нічого поганого в жодному із запропонованих вами запитань.

У власному коді я би реалізував це так:

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

Синтаксис з ()створює генератор, який є більш ефективним, ніж генерувати весь список відразу [].


І не тільки це - з []вами можуть виникнути проблеми, якщо ітератор ніколи не закінчується або його елементи важко створити, чим пізніше він вийде ...
glglgl

6
'generator' object has no attribute 'next'на Python 3.
jfs

@glglgl - Щодо першого пункту (ніколи не закінчується), я сумніваюся в цьому, оскільки аргумент - це кінцева послідовність [точніше, перелік відповідно до питання ОП]. Щодо другого: знову ж таки, оскільки поданий аргумент є послідовністю, об'єкти повинні були бути вже створені та збережені до моменту виклику цієї функції .... чи я щось пропускаю?
мак

@JFSebastian - Дякую, я про це не знав! :) З цікавості, який принцип дизайну стоїть за цим вибором?
мак

@mac - Для узгодженості з подвійним підкресленням інших спеціальних методів. Дивіться python.org/dev/peps/pep-3114
Chewie

1

Відповідь Дж. Ф. Себастьяна є найелегантнішою, але вимагає пітона 2.6, як зазначав фортран.

Для версії Python <2.6, ось найкраще, що я можу придумати:

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

Крім того, якщо вам знадобився список пізніше (список обробляє 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()

11
Yikes! якщо це зводиться до цього, я б просто робив простий цикл "for" з "if" всередині нього - набагато простіше читати
Нік Перкінс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.