辞書検索のPythonリスト


449

私がこれを持っていると仮定します:

[
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]

名前として「Pam」を検索して、関連する辞書を取得します。 {name: "Pam", age: 7}

これを達成する方法は?

回答:


510

ジェネレータ式を使用できます。

>>> dicts = [
...     { "name": "Tom", "age": 10 },
...     { "name": "Mark", "age": 5 },
...     { "name": "Pam", "age": 7 },
...     { "name": "Dick", "age": 12 }
... ]

>>> next(item for item in dicts if item["name"] == "Pam")
{'age': 7, 'name': 'Pam'}

存在しないアイテムを処理する必要がある場合は、ユーザーMatt がコメントで提案したことを実行し、少し異なるAPIを使用してデフォルトを提供できます。

next((item for item in dicts if item["name"] == "Pam"), None)

そして、アイテム自体ではなくアイテムのインデックスを見つけるために、リストを列挙()することができます

next((i for i, item in enumerate(dicts) if item["name"] == "Pam"), None)

229
他の誰かを少し時間を節約するために、イベント「Pam」のデフォルト値がリストにない場合:next((item for item in dicts if item ["name"] == "Pam") 、なし)
Matt

1
どう[item for item in dicts if item["name"] == "Pam"][0]ですか?
Moberg、2014年

3
@Moberg、それはまだリスト内包なので、一致するアイテムの位置に関係なく、入力シーケンス全体を反復します。
フレデリックハミディ2014年

7
キーが辞書に存在しない場合、これにより停止エラーが発生します
Kishan

3
@Siemkowski:次にenumerate()、実行中のインデックスを生成するために追加しますnext(i for i, item in enumerate(dicts) if item["name"] == "Pam")
Martijn Pieters

217

これは私に最もpythonicな方法を探します:

people = [
{'name': "Tom", 'age': 10},
{'name': "Mark", 'age': 5},
{'name': "Pam", 'age': 7}
]

filter(lambda person: person['name'] == 'Pam', people)

結果(Python 2ではリストとして返されます):

[{'age': 7, 'name': 'Pam'}]

注:Python 3では、フィルターオブジェクトが返されます。したがって、python3ソリューションは次のようになります。

list(filter(lambda person: person['name'] == 'Pam', people))

14
この回答は、人々の「Pam」に一致するすべてのリストを返すことに注意してください。あるいは、比較演算子を!=に変更することで、「Pam」ではないすべての人々のリストを取得できます。+1
オネマ2015年

2
また、結果はリストではなくフィルターオブジェクトであることにも言及する価値len()があります。などを使用する場合は、list()最初に結果を呼び出す必要があります。または:stackoverflow.com/questions/19182188/...
wasabigeek

@wasabigeekこれは私のPython 2.7が言うことです:people = [{'name': "Tom"、 'age':10}、{'name': "Mark"、 'age':5}、{'name': "Pam"、 'age':7}] r = filter(lambda person:person ['name'] == 'Pam'、people)type(r)リストSo ris alist
PaoloC

1
リスト内包表記は、map / filter / reduceよりもPythonicと見なされます。stackoverflow.com/ questions / 5426754 / google
python

2
最初の一致を取得:next(filter(lambda x: x['name'] == 'Pam', dicts))
xgMz

60

@フレデリック・ハミディの答えは素晴らしい。Python 3.xでは、構文.next()がわずかに変更されました。したがって、わずかな変更:

>>> dicts = [
     { "name": "Tom", "age": 10 },
     { "name": "Mark", "age": 5 },
     { "name": "Pam", "age": 7 },
     { "name": "Dick", "age": 12 }
 ]
>>> next(item for item in dicts if item["name"] == "Pam")
{'age': 7, 'name': 'Pam'}

@Mattによるコメントで述べたように、デフォルト値を追加できます。

>>> next((item for item in dicts if item["name"] == "Pam"), False)
{'name': 'Pam', 'age': 7}
>>> next((item for item in dicts if item["name"] == "Sam"), False)
False
>>>

1
これはPython 3.xの最良の答えです。年齢など、dictsの特定の要素が必要な場合は、次のように記述できます。next((item.get( 'age')item for dicts if item ["name"] == "Pam")、False)
cwhisperer

47

リスト内包表記を使用できます。

def search(name, people):
    return [element for element in people if element['name'] == name]

4
複数ある場合はすべての一致を返すため、これは便利です。質問が正確に何を要求したかではありませんが、それは私が必要としたものです!ありがとう!
user3303554

これはリストを返すことにも注意してください!
アッバス

34
people = [
{'name': "Tom", 'age': 10},
{'name': "Mark", 'age': 5},
{'name': "Pam", 'age': 7}
]

def search(name):
    for p in people:
        if p['name'] == name:
            return p

search("Pam")

リスト内の指定された名前の最初の辞書を返します。
リッキーロビンソン

5
この非常に便利なルーチンをもう少し一般的にするために:def search(list, key, value): for item in list: if item[key] == value: return item
Jack James

30

辞書のリストを調べて、キーxが特定の値を持つ辞書を返すさまざまな方法をテストしました。

結果:

  • 速度:リスト内包表記>ジェネレーター式>>通常のリスト反復>>>フィルター。
  • リスト内の辞書の数に応じてすべての線形目盛(10xリストサイズ-> 10x時間)。
  • 辞書ごとのキーは、大量(数千)のキーの速度に大きな影響を与えません。私が計算した次のグラフを参照してください:https : //imgur.com/a/quQzv(メソッド名は以下を参照)。

すべてのテストはPython 3.6 .4、W7x64で行われました。

from random import randint
from timeit import timeit


list_dicts = []
for _ in range(1000):     # number of dicts in the list
    dict_tmp = {}
    for i in range(10):   # number of keys for each dict
        dict_tmp[f"key{i}"] = randint(0,50)
    list_dicts.append( dict_tmp )



def a():
    # normal iteration over all elements
    for dict_ in list_dicts:
        if dict_["key3"] == 20:
            pass

def b():
    # use 'generator'
    for dict_ in (x for x in list_dicts if x["key3"] == 20):
        pass

def c():
    # use 'list'
    for dict_ in [x for x in list_dicts if x["key3"] == 20]:
        pass

def d():
    # use 'filter'
    for dict_ in filter(lambda x: x['key3'] == 20, list_dicts):
        pass

結果:

1.7303 # normal list iteration 
1.3849 # generator expression 
1.3158 # list comprehension 
7.7848 # filter

上記のFrédéricHamidiが指摘したように、nextを実装する関数z()を追加しました。Pyプロファイルの結果を次に示します。
レオン

10

@FrédéricHamidiにほんの少しだけ追加します。

キーが辞書のリストにあるかわからない場合は、次のようなものが役立ちます:

next((item for item in dicts if item.get("name") and item["name"] == "Pam"), None)

または単にitem.get("name") == "Pam"
Andreas Haferburg

10

あなたはパンダのパッケージを試したことがありますか?この種の検索タスクに最適で、最適化もされています。

import pandas as pd

listOfDicts = [
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]

# Create a data frame, keys are used as column headers.
# Dict items with the same key are entered into the same respective column.
df = pd.DataFrame(listOfDicts)

# The pandas dataframe allows you to pick out specific values like so:

df2 = df[ (df['name'] == 'Pam') & (df['age'] == 7) ]

# Alternate syntax, same thing

df2 = df[ (df.name == 'Pam') & (df.age == 7) ]

以下のベンチマークを少し追加して、パンダのより高速なランタイムを大規模に、つまり100k +エントリで説明します。

setup_large = 'dicts = [];\
[dicts.extend(({ "name": "Tom", "age": 10 },{ "name": "Mark", "age": 5 },\
{ "name": "Pam", "age": 7 },{ "name": "Dick", "age": 12 })) for _ in range(25000)];\
from operator import itemgetter;import pandas as pd;\
df = pd.DataFrame(dicts);'

setup_small = 'dicts = [];\
dicts.extend(({ "name": "Tom", "age": 10 },{ "name": "Mark", "age": 5 },\
{ "name": "Pam", "age": 7 },{ "name": "Dick", "age": 12 }));\
from operator import itemgetter;import pandas as pd;\
df = pd.DataFrame(dicts);'

method1 = '[item for item in dicts if item["name"] == "Pam"]'
method2 = 'df[df["name"] == "Pam"]'

import timeit
t = timeit.Timer(method1, setup_small)
print('Small Method LC: ' + str(t.timeit(100)))
t = timeit.Timer(method2, setup_small)
print('Small Method Pandas: ' + str(t.timeit(100)))

t = timeit.Timer(method1, setup_large)
print('Large Method LC: ' + str(t.timeit(100)))
t = timeit.Timer(method2, setup_large)
print('Large Method Pandas: ' + str(t.timeit(100)))

#Small Method LC: 0.000191926956177
#Small Method Pandas: 0.044392824173
#Large Method LC: 1.98827004433
#Large Method Pandas: 0.324505090714

7

これは、辞書のリストの値を検索する一般的な方法です。

def search_dictionaries(key, value, list_of_dictionaries):
    return [element for element in list_of_dictionaries if element[key] == value]

6
names = [{'name':'Tom', 'age': 10}, {'name': 'Mark', 'age': 5}, {'name': 'Pam', 'age': 7}]
resultlist = [d    for d in names     if d.get('name', '') == 'Pam']
first_result = resultlist[0]

これは片道です...


1
「名前」キーのない「名前」のエントリを適切に処理するために、[d for x in names if d.get( 'name'、 '')== 'Pam'] ...をお勧めします。
ジム・デニス

6

単にリスト内包表記を使用する:

[i for i in dct if i['name'] == 'Pam'][0]

サンプルコード:

dct = [
    {'name': 'Tom', 'age': 10},
    {'name': 'Mark', 'age': 5},
    {'name': 'Pam', 'age': 7}
]

print([i for i in dct if i['name'] == 'Pam'][0])

> {'age': 7, 'name': 'Pam'}

5

これは、Pythonでfilterおよびnextメソッドを使用することで実現できます。

filter メソッドは、指定されたシーケンスをフィルタリングし、イテレータを返します。 nextメソッドはイテレータを受け取り、リストの次の要素を返します。

要素を見つけるには、

my_dict = [
    {"name": "Tom", "age": 10},
    {"name": "Mark", "age": 5},
    {"name": "Pam", "age": 7}
]

next(filter(lambda obj: obj.get('name') == 'Pam', my_dict), None)

出力は、

{'name': 'Pam', 'age': 7}

注:None検索する名前が見つからない場合、上記のコードは大文字と小文字を返します。


これはリスト内包表記よりもかなり遅いです。
AnupamChugh

4

私が最初に思ったのは、これらの辞書の辞書を作成することを検討したいと思うかもしれません。たとえば、もしあなたがそれを何度も検索するつもりなら、

しかし、それは時期尚早の最適化かもしれません。何が問題になるでしょう:

def get_records(key, store=dict()):
    '''Return a list of all records containing name==key from our store
    '''
    assert key is not None
    return [d for d in store if d['name']==key]

実際には、name = Noneアイテムを含む辞書を作成できます。しかし、それはこのリスト内包では実際には機能せず、データストアで許可することはおそらく正気ではありません。
ジムデニス

1
デバッグモードがオフの場合、アサートはスキップされます。
bluppfisk

4
dicts=[
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]

from collections import defaultdict
dicts_by_name=defaultdict(list)
for d in dicts:
    dicts_by_name[d['name']]=d

print dicts_by_name['Tom']

#output
#>>>
#{'age': 10, 'name': 'Tom'}

3

リスト内包表記を使用する1つの簡単な方法 lは、

l = [
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]

その後

[d['age'] for d in l if d['name']=='Tom']

2

あなたはこれを試すことができます:

''' lst: list of dictionaries '''
lst = [{"name": "Tom", "age": 10}, {"name": "Mark", "age": 5}, {"name": "Pam", "age": 7}]

search = raw_input("What name: ") #Input name that needs to be searched (say 'Pam')

print [ lst[i] for i in range(len(lst)) if(lst[i]["name"]==search) ][0] #Output
>>> {'age': 7, 'name': 'Pam'} 

1

これは、繰り返しリストを使用した比較、filter + lambdaまたはリファクタリング(必要に応じて、またはケースに当てはまる場合)を使用して、dictsのリストではなく、dictsのdictにコードを比較します。

import time

# Build list of dicts
list_of_dicts = list()
for i in range(100000):
    list_of_dicts.append({'id': i, 'name': 'Tom'})

# Build dict of dicts
dict_of_dicts = dict()
for i in range(100000):
    dict_of_dicts[i] = {'name': 'Tom'}


# Find the one with ID of 99

# 1. iterate through the list
lod_ts = time.time()
for elem in list_of_dicts:
    if elem['id'] == 99999:
        break
lod_tf = time.time()
lod_td = lod_tf - lod_ts

# 2. Use filter
f_ts = time.time()
x = filter(lambda k: k['id'] == 99999, list_of_dicts)
f_tf = time.time()
f_td = f_tf- f_ts

# 3. find it in dict of dicts
dod_ts = time.time()
x = dict_of_dicts[99999]
dod_tf = time.time()
dod_td = dod_tf - dod_ts


print 'List of Dictionries took: %s' % lod_td
print 'Using filter took: %s' % f_td
print 'Dict of Dicts took: %s' % dod_td

そして出力はこれです:

List of Dictionries took: 0.0099310874939
Using filter took: 0.0121960639954
Dict of Dicts took: 4.05311584473e-06

結論: 辞書のディクショナリがあることは、IDのみで検索することがわかっている場合に、検索を実行できる最も効率的な方法です。興味深いことに、フィルターの使用は最も遅い解決策です。


0

リストのすべての要素を通過する必要があります。近道はありません!

他の場所を除いて、リストの項目を指す名前のディクショナリを保持している場合を除き、リストから要素をポップした結果に注意する必要があります。


並べ替えられていないリストと欠落しているキーの場合、このステートメントは正しいですが、一般的ではありません。リストがソートされていることがわかっている場合、すべての要素を繰り返す必要はありません。また、単一のレコードがヒットし、キーが一意であるか、要素が1つだけ必要であることがわかっている場合、単一の項目が返されて反復が停止することがあります。
user25064 2014

@ user334856の回答を参照してください
MelihYıldız '

@MelihYıldız '多分私は私の声明ではっきりしていませんでした。回答内のリスト内包user334856を使用することにより、stackoverflow.com / a / 8653572/512225はリスト全体を処理します。これは私の声明を裏付けるものです。あなたが参照する答えは、私が書いたものを言う別の方法です。
jimifiki 2016年

0

同じ質問への回答を探しているときにこのスレッドを見つけました。遅い答えだとは思いますが、他の人に役立つ場合に備えて貢献したいと思いました。

def find_dict_in_list(dicts, default=None, **kwargs):
    """Find first matching :obj:`dict` in :obj:`list`.

    :param list dicts: List of dictionaries.
    :param dict default: Optional. Default dictionary to return.
        Defaults to `None`.
    :param **kwargs: `key=value` pairs to match in :obj:`dict`.

    :returns: First matching :obj:`dict` from `dicts`.
    :rtype: dict

    """

    rval = default
    for d in dicts:
        is_found = False

        # Search for keys in dict.
        for k, v in kwargs.items():
            if d.get(k, None) == v:
                is_found = True

            else:
                is_found = False
                break

        if is_found:
            rval = d
            break

    return rval


if __name__ == '__main__':
    # Tests
    dicts = []
    keys = 'spam eggs shrubbery knight'.split()

    start = 0
    for _ in range(4):
        dct = {k: v for k, v in zip(keys, range(start, start+4))}
        dicts.append(dct)
        start += 4

    # Find each dict based on 'spam' key only.  
    for x in range(len(dicts)):
        spam = x*4
        assert find_dict_in_list(dicts, spam=spam) == dicts[x]

    # Find each dict based on 'spam' and 'shrubbery' keys.
    for x in range(len(dicts)):
        spam = x*4
        assert find_dict_in_list(dicts, spam=spam, shrubbery=spam+2) == dicts[x]

    # Search for one correct key, one incorrect key:
    for x in range(len(dicts)):
        spam = x*4
        assert find_dict_in_list(dicts, spam=spam, shrubbery=spam+1) is None

    # Search for non-existent dict.
    for x in range(len(dicts)):
        spam = x+100
        assert find_dict_in_list(dicts, spam=spam) is None

0

ここで提案されているほとんど(すべてではない)の実装には、2つの欠点があります。

  • 彼らは、検索のために渡されるキーが1つだけであると想定していますが、複雑な辞書の場合、もっと多くのキーがあると興味深いかもしれません。
  • 彼らは、検索のために渡されたすべてのキーが辞書に存在することを前提としているため、そうでない場合に発生するKeyErrorを正しく処理しません。

更新された命題:

def find_first_in_list(objects, **kwargs):
    return next((obj for obj in objects if
                 len(set(obj.keys()).intersection(kwargs.keys())) > 0 and
                 all([obj[k] == v for k, v in kwargs.items() if k in obj.keys()])),
                None)

多分最もpythonicではないかもしれませんが、少なくとももう少しフェイルセーフです。

使用法:

>>> obj1 = find_first_in_list(list_of_dict, name='Pam', age=7)
>>> obj2 = find_first_in_list(list_of_dict, name='Pam', age=27)
>>> obj3 = find_first_in_list(list_of_dict, name='Pam', address='nowhere')
>>> 
>>> print(obj1, obj2, obj3)
{"name": "Pam", "age": 7}, None, {"name": "Pam", "age": 7}

要旨

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