条件に一致するイテラブルから最初のアイテムを取得する


303

条件に一致するリストから最初のアイテムを取得したいと思います。結果のメソッドがリスト全体を処理しないことが重要です。これは非常に大きくなる可能性があります。たとえば、次の関数で十分です。

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

この関数は次のように使用できます。

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

ただし、これを実現するための優れた組み込み/ワンライナーを考えることはできません。特に必要がなければ、この関数をコピーしたくありません。条件に一致する最初のアイテムを取得する組み込みの方法はありますか?


回答:


476

Python 2.6以降の場合:

StopIteration一致する要素が見つからない場合に発生させたい場合:

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

代わりにdefault_value(例えばNone)を返したい場合:

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

この場合、ジェネレータ式を囲む括弧のペアがさらに必要であることに注意してください。ジェネレータ式が唯一の引数ではない場合は常に括弧が必要です。

私はほとんどの回答がnextビルトインを断固として無視していることを理解しているため、いくつかの不思議な理由のために、それらはバージョン2.5以前に100%焦点を当てていると思います-Pythonバージョンの問題については言及していません(しかし、答えない言及next、少なくとも「正しいバージョン」の問題は、レコードにこの方法を取得します;-) -私は答えを自分で提供することが必要であると考え理由である、ビルトインを。

2.5では、.next()イテレータのメソッドがStopIterationすぐに終了するのは、イテレータがすぐに終了する場合です。つまり、ユースケースでは、イテラブルのどの項目も条件を満たさない場合です。気にしない場合(つまり、少なくとも1つの満足できる項目が必要であることがわかっている場合)を使用します.next()(genexpで最適next、Python 2.6以降の組み込みの行)。

あなたがいる場合行うケアを、あなたが最初にあなたのQで示されたように、関数内のラッピングのものは最高のようだ、とあなたが提案した機能の実装がうまくている間、あなたは、代わりに使用することができitertoolsfor...: breakループ、またはgenexp、またはtry/except StopIteration関数のボディとして、さまざまな答えが示唆したように。これらの選択肢には付加価値があまりないため、最初に提案した非常にシンプルなバージョンを使用します。


6
あなたが説明するように動作しません。StopIteration要素が見つからないときに発生します
Suor

これは検索結果に表示されるので、2011年の@Suorのコメントをフォローし、最初の段落を少し言い換えてわかりやすくしました。必要に応じて、編集を修正してください。
Kos

4
これが選択された回答であるため、最初の要素を正しく選択するための回答をここで共有する必要があります。つまり、nextの使用は推奨されません。
Guyarad 16

1
@guyaradその答えで提案されているソリューションは、nextを使用するよりも「不可解」ではありませんか?(その答えの)nextに対する唯一の議論は、例外を処理する必要があるということです。本当に ?
アブラハムTS

私の意見は、コメントを書いたときとは少し異なります。あなたの言ってる事がわかります。それは言われている、処理することStopIterationは本当にきれいではありません。メソッドを使用することをお勧めします。
guyarad

29

再利用可能な、文書化され、テストされた機能として

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

デフォルトの引数を持つバージョン

@zorfは、反復可能オブジェクトが空であるか、条件に一致するアイテムがない場合に、事前定義された戻り値を持つことができるこの関数のバージョンを提案しました。

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
メソッドでラップしている場合は、少なくともStopIterationをキャッチして、EmptySequenceエラーを発生させます。要素がない場合はかなりきれいになります。
Guyarad

@guyaradそれは一種のValueErrorですか?
Caridorc

2
@guyarad StopIterationは、Python の標準的な「要素外」例外です。投げられることに問題はありません。おそらく、関数のデフォルトパラメータとして渡すことができるデフォルトの「なし」を使用します。
Baldrickk、2018年

1
Baldrickkこれは反復法ではないと思います。イテレータのコンテストでこれを呼び出すことはありません。しかし、私はそれについてあまり強く感じていません:)
guyarad '25年

1
オプションのデフォルト引数が必要です。その引数を指定しない場合は、シーケンス内のどの要素も条件を満たさない場合にのみ例外が発生します。
Zorf

28

くそ例外!

私はこの答えが大好きです。ただし、アイテムがない場合next()StopIteration例外が発生するため、次のスニペットを使用して例外を回避します。

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

例えば、

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

引き上げるStopIteration例外を。

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

13

の使用ifilterと同様に、ジェネレータ式を使用できます。

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

どちらの場合StopIterationも、条件を満たしている要素がない場合は、おそらくキャッチする必要があります。

技術的には、次のようなことができると思います。

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

try/exceptブロックを作る必要がなくなります。しかし、それは構文に対してあいまいで乱用のようです。


+1:曖昧でも、虐待でもない。すべてを考慮すると、最後のものはかなりきれいに見えます。
S.Lott

6
最後の1つはまったくクリーンでfor foo in genex: breakはありません。foo = next(genex)割り当てを明確にせずに行う方法であり、例外として、操作が押しつぶされても意味がない場合に発生します。例外をキャッチするのではなく、失敗コードで終わることは、通常、Pythonでは悪いことです。
マイクグラハム

13

Python 3で最も効率的な方法は、次のいずれかです(同様の例を使用)。

「理解」のスタイル:

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

警告:式はPython 2でも機能しますが、この例ではrange、Python 2のようなリストではなく、Python 3で反復可能なオブジェクトを返します(Python 2で反復可能なオブジェクトを作成する場合は、xrange代わりにしてください)。

式は、内包式next([i for ...])でリストを作成することを避けます。これにより、要素をフィルタリングする前にすべての要素を含むリストが作成され、反復を1回停止するのではなく、オプション全体が処理されますi == 1000

「機能」のスタイル:

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

警告:これはPython 2では機能せず、イテレーターの代わりにリストを作成するdueで置き換えrangeても(非効率的)、関数はイテレーターでのみ機能します。xrangefilternext

デフォルト値

他の応答で述べたようnextに、条件が満たされないときに発生する例外を回避する場合は、関数にパラメーターを追加する必要があります。

「機能的」スタイル:

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

「理解」スタイル:

このスタイルでは()SyntaxError: Generator expression must be parenthesized if not sole argument次を回避するために内包表記を囲む必要があります。

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


6

このitertoolsモジュールには、イテレーター用のフィルター関数が含まれています。フィルターされたイテレーターの最初の要素は、next()それを呼び出すことで取得できます。

from itertools import ifilter

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

2
ジェネレータ式はより単純です。
エリックOレビゴット

1
ifilterと(imapは、適用されている関数が既に存在する場合には意味がありますが、このような状況では、ジェネレーター式を使用するだけの方がはるかに意味があります。
マイクグラハム、

これが最良の答えです。避けリストの内包表記はxahlee.info/comp/list_comprehension.html
MIT

6

次の組み込みが存在しない古いバージョンのPythonの場合:

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

5

を使用して

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

the_iterableの最初の項目の状態を確認し、the_iterableのすべての項目を評価する必要なく、そのインデックスを取得できます。

使用する完全な式は

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

ここで、first_indexは、上記の式で識別された最初の値を想定しています。


4

この質問にはすでに素晴らしい答えがあります。OPに非常によく似ている、自分の問題の解決策を見つけようとしてここに着陸したので、2セントを追加するだけです。

ジェネレータを使用して、基準に一致する最初の項目のINDEXを検索する場合は、次のようにするだけです。

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


0

argwhereNumpy の関数を使用することもできます。例えば:

i) "helloworld"の最初の "l"を見つけます:

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)最初の乱数> 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)最後の乱数> 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の場合:

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

Python 2.6の場合:

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

編集:それは明白だと思いましたが、明らかにそうではありません:代わりに、条件のチェックをNone含む関数(またはlambda)を渡すことができます:

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

-3

一発ギャグ:

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

基準に従って要素が有効であるかどうかわからない場合はtry/except、を発生さ[0]せる可能性があるため、これをで囲む必要がありIndexErrorます。


TypeError例外:「ジェネレータ」オブジェクトはunsubscriptableある
ジョシュ・リー

私の悪い点は、ジェネレータではなくリスト内包であるはずです、修正されました...ありがとう!:)
Mizipzor 2010年

2
イテラブル全体を評価する理由はありません(これは不可能かもしれません)。提供されている他のソリューションのいずれかを使用する方がより堅牢で効率的です。
マイクグラハム、
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.