Bir yüklemle eşleşen bir dizideki ilk öğeyi bulma


171

Listedeki bir yüklemle eşleşen ilk öğeyi bulmak için deyimsel bir yol istiyorum.

Mevcut kod oldukça çirkin:

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

Bunu şu şekilde değiştirmeyi düşündüm:

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

Ama daha zarif bir şey olmalı ... Ve Noneherhangi bir eşleşme bulunamazsa istisna oluşturmak yerine bir değer döndürürse iyi olur .

Ben sadece gibi bir işlevi tanımlayabilirsiniz biliyorum:

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

Ancak kodu zaten bu gibi yardımcı işlevlerle doldurmaya başlamak oldukça tatsızdır (ve insanlar muhtemelen zaten orada olduklarını fark etmeyeceklerdir, bu yüzden zaten aynı şeyi sağlayan yerleşikler varsa).


3
" Python dizi bulma fonksiyonu " dan sonra sorulmasının yanı sıra , bu sorunun çok daha iyi bir başlığı vardır .
Wolf

Yanıtlar:


250

Bir sırayla seqeşleşen ilk öğeyi bulmak için predicate:

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

Veya ( itertools.ifilterPython 2'de) :

next(filter(predicate, seq))

O yükseltir StopIterationhiçbiri varsa.


NoneBöyle bir öğe yoksa geri dönmek için :

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

Veya:

next(filter(predicate, seq), None)

28
Veya nextistisna oluşturmak yerine kullanılan ikinci bir "varsayılan" argüman sağlayabilirsiniz.
Karl Knechtel

2
@fortran: next()Python 2.6'dan beri kullanılabilir . Yeni özelliklere hızlı bir şekilde alışmak için Yenilikler sayfasını okuyabilirsiniz .
jfs

1
Ben bir python newbie ve belgeleri okumak ve ifilter "verim" yöntemini kullanır. Bunun, yüklemin gittikçe tembel olarak değerlendirildiği anlamına geldiğini düşünüyorum. Yani, yüklemi tüm liste boyunca çalıştırmıyoruz, çünkü biraz pahalı bir yüklem fonksiyonum var ve sadece bir öğe bulduğumuz noktaya kadar tekrarlamak istiyorum
Kannan Ekanath

2
@geekazoid: seq.find(&method(:predicate))örneğin yöntemler için daha da özlü örneğin:[1,1,4].find(&:even?)
jfs

16
ifilterfilterPython 3 olarak yeniden adlandırıldı .
tsauerwein

92

Varsayılan değeri olan bir üreteç ifadesi kullanabilirsiniz ve daha sonra next:

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

Bu tek astar için Python> = 2.6 kullanmanız gerekir.

Bu oldukça popüler makalede ayrıca bu sorun ele alınmaktadır: En temiz Python bulma listesi işlevi? .


8

Sorunuzda önerdiğiniz her iki çözümde de yanlış bir şey olduğunu düşünmüyorum.

Kendi kodumda olsa ben böyle uygulamak istiyorum:

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

İle sözdizimi, ()tüm listeyi aynı anda oluşturmaktan daha verimli bir jeneratör oluşturur [].


Ve sadece bu değil - []yineleyici asla
bitmezse

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

@glglgl - İlk noktaya gelince (asla bitmez), argüman sonlu bir sekans olduğundan [OP 'sorusuna göre daha kesin olarak bir liste]. İkincisi ise: yine, verilen argüman bir dizi olduğundan, nesneler bu fonksiyon çağrıldığında .... önceden oluşturulmuş ve saklanmış olmalıdır .... yoksa bir şey mi kaçırıyorum?
mac

@JFSebastian - Teşekkür ederim, bunun farkında değildim! :) Meraktan, bu seçimin arkasındaki tasarım prensibi nedir?
mac

@mac - Diğer özel yöntemlerin çift alt çizgisiyle tutarlılık için. Bkz. Python.org/dev/peps/pep-3114
Chewie

1

JF Sebastian'ın cevabı en zarif ama fortran'ın işaret ettiği gibi python 2.6 gerektiriyor.

Python sürüm <2.6 için, gelip en iyisi:

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

Alternatif olarak, daha sonra bir listeye ihtiyacınız varsa (liste StopIteration'ı işliyorsa) veya yalnızca ilk değil, yine de hepsinden daha fazlasına ihtiyacınız varsa, bunu islice ile yapabilirsiniz:

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

GÜNCELLEME: Ben şahsen bir StopIteration yakalar ve Hiçbiri döndüren first () adlı önceden tanımlanmış bir işlev kullanmama rağmen, Yukarıdaki örnek üzerinde olası bir gelişme İşte: filter / ifilter kullanmaktan kaçının:

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

11
Olmadı! eğer bu aşağıya gelirse, basit bir "for" döngüsünü içinde bir "if" ile yapardım - okumak çok daha kolay
Nick Perkins
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.