web-dev-qa-db-ja.com

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

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

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

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

237
Chris Phillips

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 ビルトインを絶対に無視するので、何らかの謎の理由でPythonバージョンの問題に言及せずにバージョン2.5以前に100%焦点を当てていると思います(しかし、 donextビルトインに言及しているという回答にその言及がないので、自分で答えを提供する必要があると思ったのです-少なくとも「正しい」バージョン」の問題はこの方法で記録されます;-)。

2.5では、イテレータの .next() メソッドは、イテレータがすぐに終了する場合、つまり、ユースケースの場合、反復可能なアイテムが条件を満たさない場合、すぐにStopIterationを発生させます。気にしない場合(つまり、must少なくとも1つの満足できるアイテムでなければならない)、.next()(genexpで最適、 nextビルトインPython 2.6以降)。

あなたがdo気にするなら、Qで最初に示したように関数で物事をラップするのが最良のようであり、提案した関数の実装は問題なく、代わりにitertoolsを使用することができます、さまざまな答えが示唆するように、関数の本体としてのfor...: breakループ、genexp、またはtry/except StopIteration。これらの選択肢のいずれにも付加価値はあまりないので、最初に提案した非常にシンプルなバージョンを選びます。

366
Alex Martelli

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

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

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ブロックを作成する必要がなくなります。しかし、それは一種の曖昧で、構文を乱用しているようです。

13
Matt Anderson

くそー例外!

この答え が大好きです。ただし、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
Jossef Harush

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

"comprehension"スタイルの場合:

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

WARNING:この式はPython 2でも機能しますが、この例では、リストではなくPython 3の反復可能オブジェクトを返すrangeが使用されますPython 2のように(Python 2でイテラブルを作成する場合は、代わりにxrangeを使用します)。

この式は、内包表記next([i for ...])でリストを作成することを避けます。これにより、エレメントをフィルター処理する前にすべてのエレメントを含むリストが作成され、i == 1000で繰り返しを停止するのではなく、オプション全体が処理されます。

"functional"スタイルの場合:

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

WARNING:これはPython 2では機能しません。rangexrangeで置き換えても、filterはイテレータ(非効率的)の代わりにリストを作成し、next関数のみイテレータで動作します。

デフォルト値

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

"functional"スタイル:

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

"comprehension"スタイル:

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

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

次のビルトインが存在しない古いバージョンのPythonの場合:

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

私はこれを書きます

next(x for x in xrange(10) if x > 3)
6
Mike Graham

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

from itertools import ifilter

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

を使用して

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

the_iterableの最初の項目のvalueconditionを確認できます、およびtheindexを取得し、the_iterableのすべての項目を評価する必要はありません。

使用する完全な式は

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

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

5
blue_note

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

ジェネレーターを使用して、条件に一致する最初のアイテムのINDEXを検索する場合は、次を実行できます。

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

組み込みのワンライナーをリクエストしているので、これはStopIteration例外の問題を回避しますが、イテレート可能オブジェクトはリストにキャストできるように小さくする必要があります。 StopIterationを飲み込み、値をのぞかせます:

(lambda x:x[0] if x else None)(list(y for y in ITERABLE if CONDITION))

(一致する要素がない場合、None例外ではなくStopIterationが返されます。)

0
ninjagecko

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

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()
0
aim