辞書の値を照合して、リスト内の辞書のインデックスを見つけます


130

辞書のリストがあります:

list = [{'id':'1234','name':'Jason'},
        {'id':'2345','name':'Tom'},
        {'id':'3456','name':'Art'}]

name = 'Tom'で照合して、インデックス位置[0]、[1]、または[2]を効率的に見つけるにはどうすればよいですか?

これが1次元のリストである場合は、list.index()を実行できますが、リスト内のディクショナリの値を検索する方法がわかりません。


6
"list"はリストコンストラクターです。リストに別の名前を選択することをお勧めします(例でも)。そして、要素が見つからない場合の応答はどうでしょうか?例外を発生させますか?戻り値なし?
tokland

7
これが必要になる場合は、より適切なデータ構造を使用してください(おそらく{ 'Jason': {'id': '1234'}, 'Tom': {'id': '1245'}, ...}?)

3
@delnanそれは災害のレシピだからです!どちらかといえば、それはする必要があります{'1234': {'name': 'Jason'}, ...}。これがこのユースケースに役立つわけではありません。
OJFord 2016

回答:


145
tom_index = next((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
# 1

名前から繰り返しフェッチする必要がある場合は、(ディクショナリを使用して)名前でインデックスを付ける必要があります。これにより、取得操作はO(1)時間になります。アイデア:

def build_dict(seq, key):
    return dict((d[key], dict(d, index=index)) for (index, d) in enumerate(seq))

info_by_name = build_dict(lst, key="name")
tom_info = info_by_name.get("Tom")
# {'index': 1, 'id': '2345', 'name': 'Tom'}

2
私見これは読みにくいか、Pythonicが@Emileの答えです。ジェネレータを作成することは意図していないため(そしてnext()これを使用するのは私には奇妙に思えます)、インデックスを取得するだけです。また、これはStopIterationを発生させますが、Python lst.index()メソッドはValueErrorを発生させます。
Ben Hoyt

@benhoyt:StopIteration例外も好きではありませんが、next()のデフォルト値は変更できますが、発生する例外は修正されています。pythonicityはいくぶん主観的なので、私はそれを論争しません、おそらくforループはよりpythonicです。一方、first()の代わりにnext()をエイリアスする人もいますが、これは間違いなく優れています:first(index for(index、d)in ...)。
tokland

first()よく聞こえます。StopIterationを常に試行/除外し、ValueErrorを発生させて、呼び出し側に一貫性を持たせることができます。または、next()のデフォルトを-1に設定します。
Ben Hoyt、2010

1
@ gdw2:そうするSyntaxError: Generator expression must be parenthesized if not sole argumentときに私は得る。
avoliva

2
@avolivaは次のように括弧を次の周りに追加しますnext((index for (index, d) in enumerate(lst) if d["name"] == "Tom"), None)
HussienK

45

シンプルで読みやすいバージョンは

def find(lst, key, value):
    for i, dic in enumerate(lst):
        if dic[key] == value:
            return i
    return -1

8
これは最も読みやすく、Pythonらしい。また、の動作をstr.find()うまく模倣します。必要に応じてindex()、それを呼び出して、ValueError-1を返す代わりにを発生させることもできます。
Ben Hoyt

6
同意-一致するものが見つからないときに-1を返すことにより、リストの最後の辞書を常に取得しますが、これはおそらく望んでいるものではありません。Noneを返し、呼び出しコードで一致の存在を確認することをお勧めします。
シャッカー2016年

9

リストのすべての項目をチェックするリストを歩く必要があるため、効率的ではありません(O(n))。効率が必要な場合は、dict of dictsを使用できます。質問では、これを見つける方法の1つを示します(ただし、このデータ構造を使用したい場合は、Brent Neweyがコメントに書いているように、ジェネレーター使用する方が実際には効率的です。toklandの回答も参照してください)。

>>> L = [{'id':'1234','name':'Jason'},
...         {'id':'2345','name':'Tom'},
...         {'id':'3456','name':'Art'}]
>>> [i for i,_ in enumerate(L) if _['name'] == 'Tom'][0]
1

1
ジェネレーターを使用することで、必要な効率を得ることができます。toklandの答えを見てください。
Brent Newey、2010

2
@Brent Newey:ジェネレーターはリスト全体をトラバースする必要があるという事実を変更しません。検索をO(n)として要求します...リストの長さに応じて、ジェネレーターの使用と使用の違いforループまたは無視できるものは何でも、dictを使用する場合とリストを使用する場合の違いはそうではありません
Dirk

@ブレント:その通りですが、検索されたアイテムがリストの最後にある場合は、辞書のO(1)ルックアップに勝ることはありますか?
aeter、

1
@Dirk一致が見つかると、ジェネレーターのnext()呼び出しが停止するため、リスト全体を走査する必要はありません。
Brent Newey、2010

@aeterあなたは公正な主張をします。一致が見つかったときに停止できることについて言及していました。
Brent Newey、2010

2

辞書のインデックス位置が存在する場合、それを見つける関数を次に示します。

dicts = [{'id':'1234','name':'Jason'},
         {'id':'2345','name':'Tom'},
         {'id':'3456','name':'Art'}]

def find_index(dicts, key, value):
    class Null: pass
    for i, d in enumerate(dicts):
        if d.get(key, Null) == value:
            return i
    else:
        raise ValueError('no dict with the key and value combination found')

print find_index(dicts, 'name', 'Tom')
# 1
find_index(dicts, 'name', 'Ensnare')
# ValueError: no dict with the key and value combination found

2

フィルター/インデックスの組み合わせを使用するのが最も論理的であるようです:

names=[{}, {'name': 'Tom'},{'name': 'Tony'}]
names.index(filter(lambda n: n.get('name') == 'Tom', names)[0])
1

そして、もしあなたが複数のマッチがあると思うなら:

[names.index(n) for item in filter(lambda n: n.get('name') == 'Tom', names)]
[1]

2

@fahamが提供する回答はすばらしい1行ですが、値を含む辞書へのインデックスは返しません。代わりに、辞書自体を返します。簡単な取得方法は次のとおりです。インデックスが複数ある場合は1つ以上のリスト、ない場合は空のリスト。

list = [{'id':'1234','name':'Jason'},
        {'id':'2345','name':'Tom'},
        {'id':'3456','name':'Art'}]

[i for i, d in enumerate(list) if 'Tom' in d.values()]

出力:

>>> [1]

このアプローチについて私が気に入っているのは、簡単な編集で、インデックスと辞書の両方のリストをタプルとして取得できることです。これは私が解決する必要があった問題であり、これらの答えを見つけました。以下では、別の辞書に重複する値を追加して、それがどのように機能するかを示します。

list = [{'id':'1234','name':'Jason'},
        {'id':'2345','name':'Tom'},
        {'id':'3456','name':'Art'},
        {'id':'4567','name':'Tom'}]

[(i, d) for i, d in enumerate(list) if 'Tom' in d.values()]

出力:

>>> [(1, {'id': '2345', 'name': 'Tom'}), (3, {'id': '4567', 'name': 'Tom'})]

このソリューションは、任意の値に「トム」を含むすべての辞書を検索します。


1

一発ギャグ!?

elm = ([i for i in mylist if i['name'] == 'Tom'] or [None])[0]

0

指定されたイテラブルについて、more_itertools.locate述語を満たす項目の位置を生成します。

import more_itertools as mit


iterable = [
    {"id": "1234", "name": "Jason"},
    {"id": "2345", "name": "Tom"},
    {"id": "3456", "name": "Art"}
]

list(mit.locate(iterable, pred=lambda d: d["name"] == "Tom"))
# [1]

more_itertools他の有用なツールの中でitertoolsレシピを実装するサードパーティのライブラリです。


弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.