Znajdź pierwszy element w sekwencji, który pasuje do predykatu


171

Chcę idiomatycznego sposobu na znalezienie pierwszego elementu na liście, który pasuje do predykatu.

Obecny kod jest dość brzydki:

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

Myślałem o zmianie tego na:

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

Ale musi być coś bardziej eleganckiego ... I byłoby miło, gdyby zwracało Nonewartość, zamiast zgłaszać wyjątek, jeśli nie zostanie znalezione dopasowanie.

Wiem, że mógłbym po prostu zdefiniować funkcję taką jak:

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

Ale rozpoczęcie wypełniania kodu takimi funkcjami użytkowymi jest zupełnie bez smaku (a ludzie prawdopodobnie nie zauważą, że już tam są, więc mają tendencję do powtarzania się w czasie), jeśli istnieją wbudowane narzędzia, które już zapewniają to samo.


3
Poza tym, że zostało zadane później niż „ funkcja znajdowania sekwencji w Pythonie ”, to pytanie ma znacznie lepszy tytuł .
Wilk

Odpowiedzi:


249

Aby znaleźć pierwszy element w sekwencji, seqktóry pasuje do a predicate:

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

Lub ( itertools.ifilterw Pythonie 2) :

next(filter(predicate, seq))

Podnosi się, StopIterationjeśli nie ma.


Aby wrócić, Nonejeśli nie ma takiego elementu:

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

Lub:

next(filter(predicate, seq), None)

27
Lub możesz podać drugi argument „domyślny”, nextktóry jest używany zamiast zgłaszania wyjątku.
Karl Knechtel

2
@fortran: next()jest dostępny od wersji Pythona 2.6. Możesz przeczytać stronę Co nowego, aby szybko zapoznać się z nowymi funkcjami.
jfs

1
Jestem nowicjuszem w Pythonie i czytam dokumentację, a ifilter używa metody „yield”. Zakładam, że oznacza to, że predykat jest oceniany leniwie w trakcie. tj. nie przeprowadzamy predykatu przez całą listę, ponieważ mam funkcję predykatu, która jest trochę droga i chcę tylko iterować do momentu, w którym znajdziemy element
Kannan Ekanath

2
@geekazoid: seq.find(&method(:predicate))lub jeszcze bardziej zwięzłe, na przykład metody:[1,1,4].find(&:even?)
jfs

16
ifilterzostała zmieniona na filterw Pythonie 3.
tsauerwein


8

Nie sądzę, żeby było coś złego w obu rozwiązaniach, które zaproponowałeś w swoim pytaniu.

We własnym kodzie zaimplementowałbym to jednak w następujący sposób:

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

Składnia with ()tworzy generator, który jest bardziej wydajny niż generowanie całej listy naraz [].


I nie tylko to - []możesz napotkać problemy, jeśli iterator nigdy się nie skończy lub jego elementy są trudne do utworzenia, im później to ...
glglgl

6
'generator' object has no attribute 'next'na Pythonie 3.
jfs,

@glglgl - Co do pierwszego punktu (nigdy się nie kończy), wątpię w to, ponieważ argumentem jest ciąg skończony [a dokładniej lista według pytania OP]. Co do drugiego: ponownie, ponieważ dostarczony argument jest sekwencją, obiekty powinny być już utworzone i zapisane do czasu wywołania tej funkcji ... czy czegoś mi brakuje?
Mac,

@JFSebastian - Dziękuję, nie byłem tego świadomy! :) Z ciekawości, jaka jest zasada projektowa tego wyboru?
Mac,

@mac - Aby zachować spójność z podwójnym podkreśleniem innych metod specjalnych. Zobacz python.org/dev/peps/pep-3114
Chewie,

1

Odpowiedź JF Sebastiana jest najbardziej elegancka, ale wymaga Pythona 2.6, jak wskazał Fortran.

Dla wersji Pythona <2.6, oto najlepsze, co mogę wymyślić:

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

Alternatywnie, jeśli potrzebowałeś listy później (lista obsługuje StopIteration) lub potrzebujesz czegoś więcej niż tylko pierwszego, ale nadal nie wszystkiego, możesz to zrobić za pomocą islice:

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

AKTUALIZACJA: Chociaż osobiście używam predefiniowanej funkcji o nazwie first (), która przechwytuje StopIteration i zwraca None, Oto możliwe ulepszenie w stosunku do powyższego przykładu: unikaj używania filtra / ifilter:

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

11
Yikes! jeśli do tego dojdzie, wykonałbym po prostu prostą pętlę „for” z ciągiem „if” w środku - znacznie łatwiejsze do odczytania
Nick Perkins
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.