İlk öğeyi bir koşula uyan bir yinelemeden alın


304

İlk öğeyi bir koşula uyan bir listeden almak istiyorum. Ortaya çıkan yöntemin listenin tamamını işlememesi önemlidir, bu da oldukça büyük olabilir. Örneğin, aşağıdaki işlev yeterlidir:

def first(the_iterable, condition = lambda x: True):
    for i in the_iterable:
        if condition(i):
            return i

Bu işlev şu şekilde kullanılabilir:

>>> first(range(10))
0
>>> first(range(10), lambda i: i > 3)
4

Ancak, bunu yapmama izin verecek iyi bir yerleşik / tek astar düşünemiyorum. Gerekmiyorsa bu işlevi özellikle kopyalamak istemiyorum. Bir koşula uyan ilk öğeyi almanın yerleşik bir yolu var mı?


Yanıtlar:


480

Python 2.6 veya daha yenisinde:

StopIterationEşleşen bir öğe bulunmazsa yükseltilmek istiyorsanız :

next(x for x in the_iterable if x > 3)

Bunun yerine default_value(örneğin None) iade edilmesini istiyorsanız :

next((x for x in the_iterable if x > 3), default_value)

Bu durumda jeneratör ifadesi etrafında fazladan bir çift parantez gerektiğini unutmayın - jeneratör ifadesi tek argüman olmadığında bunlara ihtiyaç vardır.

Yanıtların çoğunun nextyerleşik olarak görmezden geldiğini görüyorum ve bu yüzden gizemli bir nedenden dolayı Python sürümü sorunundan bahsetmeden% 100 2.5 ve daha eski sürümlere odaklandıklarını varsayıyorum (ancak daha sonra bu sözü görmüyorum) cevaplar yapmak söznext yerleşik Eğer gerekli bir cevap kendim sağlamak düşünülen, bu yüzden de - "doğru versiyonu" sorunu kaydında bu şekilde ;-) alır en azından.

2.5'te, .next()yineleyici yöntemi hemen yineleyici StopIterationbiterse - yani, kullanım durumunuz için, yinelenebilir bir öğe koşulu karşılamıyorsa hemen yükseltir . Eğer umursamıyorsanız (yani, en az bir tatmin edici öğe olması gerektiğini biliyorsunuz ) o zaman sadece kullanın .next()(bir genexp'de en iyisi, nextPython 2.6'da yerleşik için en iyisi ve daha iyisi).

Eğer varsa bunu öncelikle Q belirtilen olduğu gibi bir işlevde bakım, sarma şeyler iyi görünüyor ve siz teklif edilen fonksiyon uygulaması gayet iken, alternatif olarak kullanabilirsiniz itertools, bir for...: breakdöngü ya da bir genexp ya da bir try/except StopIterationişlevin organı olarak , çeşitli cevaplar önerdiği gibi. Bu alternatiflerin hiçbirinde katma değer yok, bu yüzden ilk önerdiğiniz basit basit versiyona giderdim.


6
Açıkladığınız gibi çalışmıyor. StopIterationHiçbir element bulunmadığında yükselir
Suor

Bu arama sonuçlarında ortaya çıktığı için, @ Suor'un 2011'deki yorumunu takip ettim ve ilk paragrafı biraz daha açıklığa kavuşturdum. Lütfen devam edin ve gerekiyorsa düzenlememi değiştirin.
Kos

4
Bu seçilen cevap olduğu için, burada ilk elemanı doğru seçmeye bir cevap paylaşmak zorunda hissediyorum . Kısacası: bir sonrakinin kullanımı teşvik edilmemelidir.
guyarad

1
@guyarad Bu cevapta önerilen çözüm sadece bir sonrakinden daha az "şifreli" dir? Bir sonraki (bu cevapta) karşısındaki tek argüman bir istisnayı ele almanız gerektiğidir; Gerçekten mi ?
Abraham TS

Benim görüşüm, yorumu yazdığım zamandan biraz farklı. Senin değinmek istediğin noktayı anlıyorum. Bununla başa çıkmak zorunda StopIterationkalmak gerçekten hoş değil. Daha iyi bir yöntem kullanın.
guyarad

30

Yeniden kullanılabilir, belgelenmiş ve test edilmiş bir fonksiyon olarak

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))

Varsayılan bağımsız değişkeni olan sürüm

@zorf, yinelenebilir boşsa veya koşulla eşleşen öğe yoksa önceden tanımlanmış bir dönüş değerine sahip olabileceğiniz bu işlevin bir sürümünü önerdi:

def first(iterable, default = None, 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.

    If the `default` argument is given and the iterable is empty,
    or if it has no items matching the condition, the `default` argument
    is returned if it matches the condition.

    The `default` argument being None is the same as it not being given.

    Raises `StopIteration` if no item satisfying the condition is found
    and default is not given or doesn't satisfy the condition.

    >>> first( (1,2,3), condition=lambda x: x % 2 == 0)
    2
    >>> first(range(3, 100))
    3
    >>> first( () )
    Traceback (most recent call last):
    ...
    StopIteration
    >>> first([], default=1)
    1
    >>> first([], default=1, condition=lambda x: x % 2 == 0)
    Traceback (most recent call last):
    ...
    StopIteration
    >>> first([1,3,5], default=1, condition=lambda x: x % 2 == 0)
    Traceback (most recent call last):
    ...
    StopIteration
    """

    try:
        return next(x for x in iterable if condition(x))
    except StopIteration:
        if default is not None and condition(default):
            return default
        else:
            raise

6
Bir yöntemle kaydırıyorsanız, en azından StopIteration'ı yakalayın ve EmptySequence hatasını yükseltin. Hiçbir unsur olmadığında çok daha güzel olurdu.
guyarad

@guyarad Bu bir çeşit ValueError mı?
Caridorc

2
@guyarad python'da StopIterationkanonik "element dışı" istisnadır. Atılmasında bir sorun görmüyorum. Muhtemelen "Varsayılan" işlev için varsayılan bir parametre olarak iletilebilir hiçbiri kullanırdım.
Baldrickk

1
Baldrickk Bunun bir yineleme yöntemi olmadığını hissediyorum. Bunu bir yineleyici yarışmasında çağırmazsınız. Ama bu konuda çok güçlü hissetmiyorum :)
guyarad

1
İsteğe bağlı bir varsayılan bağımsız değişken olmalıdır ve bu bağımsız değişken sağlanmazsa, ancak dizideki hiçbir öğe koşulu karşılamadığında bir istisna oluşturur.
Zorf

28

Lanet İstisnalar!

Bu cevabı seviyorum . Ancak, öğe olmadığında next()bir StopIterationistisna oluşturduğundan, bir istisnayı önlemek için aşağıdaki snippet'i kullanırdım:

a = []
item = next((x for x in a), None)

Örneğin,

a = []
item = next(x for x in a)

Bir StopIterationistisna doğuracak;

Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
StopIteration

13

Kullanmaya benzer şekilde ifilter, bir jeneratör ifadesi kullanabilirsiniz:

>>> (x for x in xrange(10) if x > 5).next()
6

Her iki durumda StopIterationda, herhangi bir elementin durumunuzu karşılamaması durumunda, muhtemelen yakalamak istersiniz .

Teknik olarak, sanırım böyle bir şey yapabilirsiniz:

>>> foo = None
>>> for foo in (x for x in xrange(10) if x > 5): break
... 
>>> foo
6

Bir try/exceptblok yapmak zorunda kalmazdı . Ama bu sözdizimi için biraz belirsiz ve küfürlü görünüyor.


+1: Ne karanlık ne de küfürlü değil. Her şey göz önüne alındığında, sonuncusu oldukça temiz görünüyor.
S.Lott

6
Sonuncusu hiç de temiz değil - ödevi netleştirmeden ve operasyonun ezilmesinin mantıklı olmaması halinde ortaya çıkacak istisna dışında for foo in genex: breakbunu yapmanın bir yolu foo = next(genex). Bir istisna yakalamak yerine bir hata kodu ile sonuçlanmak Python'da genellikle kötü bir şeydir.
Mike Graham

13

Python 3'te en etkili yol aşağıdakilerden biridir (benzer bir örnek kullanarak):

İle "anlama" tarzı:

next(i for i in range(100000000) if i == 1000)

UYARI : İfade Python 2 ile de çalışır, ancak örnekte rangePython 2 gibi bir liste yerine Python 3'te yinelenebilir bir nesne döndüren kullanılır (Python 2'de yinelenebilir bir yapı oluşturmak istiyorsanızxrange bunun yerine ).

İfadenin next([i for ...]), öğeleri filtrelemeden önce tüm öğelerle bir liste oluşturmasına neden olacak ve yinelemeyi bir kez durdurmak yerine tüm seçeneklerin işlenmesine neden olacak bir anlama ifadesinde bir liste oluşturmaktan kaçının.i == 1000 .

İle "fonksiyonel" tarzı:

next(filter(lambda i: i == 1000, range(100000000)))

UYARI : Bu bile yerine Python 2 değil çalışır rangeile xrangeo nedeniyle filteryerine (verimsiz) bir yineleyici bir listesini oluşturmak venext işlev sadece adım adım elde çalışır.

Varsayılan değer

Diğer yanıtlarda belirtildiği gibi next, koşul yerine getirilmediğinde bir kural dışı durumdan kaçınmak istiyorsanız işleve bir ekstra parametre eklemeniz gerekir .

"fonksiyonel" stil:

next(filter(lambda i: i == 1000, range(100000000)), False)

"anlama" tarzı:

Bu stille, ()aşağıdakilerden kaçınmak için anlama ifadesini çevrelemeniz gerekir SyntaxError: Generator expression must be parenthesized if not sole argument:

next((i for i in range(100000000) if i == 1000), False)


6

itertoolsModül yineleyicileri için bir filtre işlevi içerir. Filtrelenmiş yineleyicinin ilk elemanı, çağrılarak elde edilebilir next():

from itertools import ifilter

print ifilter((lambda i: i > 3), range(10)).next()

2
Jeneratör ifadeleri daha basittir.
Eric O Lebigot

1
( i) filterve ( i) mapuygulanan işlevlerin zaten mevcut olduğu durumlar için mantıklı olabilir, ancak böyle bir durumda sadece bir jeneratör ifadesi kullanmak çok daha mantıklıdır.
Mike Graham

Bu en iyi cevap. Liste kavrayışlarından kaçının xahlee.info/comp/list_comprehension.html
mit

6

Bir sonraki yerleşikin mevcut olmadığı eski Python sürümleri için:

(x for x in range(10) if x > 3).next()

5

Kullanarak

(index for index, value in enumerate(the_iterable) if condition(value))

tek kontrol edebilirsiniz koşulu ait değeri ilk öğenin the_iterable ve bunun elde endeksi tüm öğeleri değerlendirmek gerek kalmadan the_iterable .

Kullanılacak ifadenin tamamı şöyledir:

first_index = next(index for index, value in enumerate(the_iterable) if condition(value))

Burada first_index yukarıda tartışılan ifadede tanımlanan ilk değerin değerini varsayar.


4

Bu sorunun zaten harika yanıtları var. Sadece iki sentimi ekliyorum çünkü buraya OP'ye çok benzeyen kendi sorunuma bir çözüm bulmaya çalıştım.

Oluşturucular kullanarak bir ölçütle eşleşen ilk öğenin INDEX'ini bulmak istiyorsanız, şunları yapabilirsiniz:

next(index for index, value in enumerate(iterable) if condition)


0

argwhereİşlevi Numpy içinde de kullanabilirsiniz . Örneğin:

i) "helloworld" deki ilk "l" yi bulun:

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) İlk rasgele sayıyı bulun> 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) Son rasgele sayıyı bulmak> 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()

-1

Python 3'te:

a = (None, False, 0, 1)
assert next(filter(None, a)) == 1

Python 2.6'da:

a = (None, False, 0, 1)
assert next(iter(filter(None, a))) == 1

EDIT: Ben açık olduğunu düşündüm, ama görünüşe göre değil: yerine Nonebir işlevi (veya a lambda) durumu kontrol ile geçebilirsiniz :

a = [2,3,4,5,6,7,8]
assert next(filter(lambda x: x%2, a)) == 3

-3

Oneliner:

thefirst = [i for i in range(10) if i > 3][0]

Herhangi bir öğenin ölçütlere göre geçerli olacağından emin değilseniz try/except, [0]bunu artıracağınızdan , bunu bir IndexError.


TypeError: 'jeneratör' nesnesinin aboneliği kaldırılamaz
Josh Lee

Benim kötü, liste anlama bir jeneratör değil sabit olmalı ... teşekkürler! :)
Mizipzor

2
Tüm tekrarlanabilir değerleri değerlendirmek için hiçbir neden yoktur (bu mümkün olmayabilir). Sağlanan diğer çözümlerden birini kullanmak daha sağlam ve verimlidir.
Mike Graham
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.