Daftar pencarian kamus python

449

Asumsikan saya memiliki ini:

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

dan dengan mencari "Pam" sebagai nama, saya ingin mengambil kamus terkait: {name: "Pam", age: 7}

Bagaimana cara mencapai ini?

Hellnar
sumber

Jawaban:

510

Anda dapat menggunakan ekspresi generator :

>>> 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'}

Jika Anda perlu untuk menangani item tidak berada di sana, maka Anda dapat melakukan apa yang pengguna Matt disarankan dalam komentarnya dan memberikan default menggunakan API yang sedikit berbeda:

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

Dan untuk menemukan indeks item, daripada item itu sendiri, Anda dapat menghitung () daftar:

next((i for i, item in enumerate(dicts) if item["name"] == "Pam"), None)
Frédéric Hamidi
sumber
229
Hanya untuk menghemat sedikit waktu bagi orang lain, jika Anda memerlukan nilai default dalam acara "Pam", tidak ada dalam daftar: next ((item untuk item dalam dikte jika item ["name"] == "Pam") , Tidak Ada)
Matt
1
Bagaimana dengan [item for item in dicts if item["name"] == "Pam"][0]?
Moberg
3
@ Robertg, itu masih pemahaman daftar, sehingga akan mengulangi seluruh urutan input terlepas dari posisi item yang cocok.
Frédéric Hamidi
7
Ini akan memunculkan kesalahan stopiteration jika kunci tidak ada dalam kamus
Kishan
3
@Siemkowski: kemudian menambahkan enumerate()untuk menghasilkan indeks berjalan: next(i for i, item in enumerate(dicts) if item["name"] == "Pam").
Martijn Pieters
218

Ini bagi saya cara yang paling pythonic:

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

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

hasil (dikembalikan sebagai daftar di Python 2):

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

Catatan: Dalam Python 3, objek filter dikembalikan. Jadi solusi python3 adalah:

list(filter(lambda person: person['name'] == 'Pam', people))
PaoloC
sumber
14
Patut dicatat bahwa jawaban ini mengembalikan daftar dengan semua kecocokan untuk 'Pam' pada orang, atau kita bisa mendapatkan daftar semua orang yang bukan 'Pam' dengan mengubah operator perbandingan menjadi! =. +1
Onema
2
Juga patut disebutkan bahwa hasilnya adalah objek filter, bukan daftar - jika Anda ingin menggunakan hal-hal seperti len(), Anda harus memanggil list()hasilnya terlebih dahulu. Atau: stackoverflow.com/questions/19182188/…
wasabigeek
@adabigeek inilah yang Python 2.7 saya katakan: people = [{'name': "Tom", 'age': 10}, {'name': "Mark", 'age': 5}, {'name': "Pam", 'usia': 7}] r = filter (lambda orang: orang [ 'nama'] == 'Pam', orang) jenis (r) daftar Jadi radalahlist
PaoloC
1
Pemahaman daftar dianggap lebih Pythonic daripada peta / filter / kurangi: stackoverflow.com/questions/5426754/google-python-style-guide
jrc
2
Dapatkan pertandingan pertama:next(filter(lambda x: x['name'] == 'Pam', dicts))
xgMz
60

Jawaban @ Frédéric Hamidi luar biasa. Dalam Python 3.x sintaks untuk .next()sedikit berubah. Jadi sedikit modifikasi:

>>> 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'}

Seperti yang disebutkan dalam komentar oleh @Matt, Anda dapat menambahkan nilai default seperti:

>>> 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
>>>
Mike N
sumber
1
Ini adalah jawaban terbaik untuk Python 3.x. Jika Anda memerlukan elemen tertentu dari dikts, seperti usia, Anda dapat menulis: selanjutnya ((item.get ('umur') untuk item dalam dikt jika item ["name"] == "Pam"), False)
cwhisperer
47

Anda dapat menggunakan pemahaman daftar :

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

sumber
4
Ini bagus karena mengembalikan semua kecocokan jika ada lebih dari satu. Tidak persis apa yang ditanyakan, tapi itu yang saya butuhkan! Terima kasih!
user3303554
Perhatikan juga ini mengembalikan daftar!
Abbas
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")
satoru
sumber
Ini akan mengembalikan kamus pertama dalam daftar dengan nama yang diberikan.
Ricky Robinson
5
Hanya untuk membuat rutin yang sangat berguna ini menjadi sedikit lebih umum:def search(list, key, value): for item in list: if item[key] == value: return item
Jack James
30

Saya menguji berbagai metode untuk menelusuri daftar kamus dan mengembalikan kamus di mana kunci x memiliki nilai tertentu.

Hasil:

  • Kecepatan: pemahaman daftar> ekspresi generator >> iterasi daftar normal >>> filter.
  • Semua skala linier dengan jumlah dikt dalam daftar (ukuran daftar 10x -> waktu 10x).
  • Kunci per kamus tidak mempengaruhi kecepatan secara signifikan untuk sejumlah besar (ribuan) kunci. Silakan lihat grafik yang saya hitung ini: https://imgur.com/a/quQzv (nama metode lihat di bawah).

Semua tes dilakukan dengan 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

Hasil:

1.7303 # normal list iteration 
1.3849 # generator expression 
1.3158 # list comprehension 
7.7848 # filter
pengguna136036
sumber
Saya menambahkan fungsi z () yang mengimplementasikan selanjutnya seperti yang ditunjukkan oleh Frédéric Hamidi di atas. Ini adalah hasil dari profil Py.
leon
10

Untuk menambahkan sedikit saja ke @ FrédéricHamidi.

Jika Anda tidak yakin kunci ada dalam daftar dicts, sesuatu seperti ini akan membantu:

next((item for item in dicts if item.get("name") and item["name"] == "Pam"), None)
Ursa Drazen
sumber
atau hanyaitem.get("name") == "Pam"
Andreas Haferburg
10

Pernahkah Anda mencoba paket panda? Ini sempurna untuk tugas pencarian semacam ini dan juga dioptimalkan.

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

Saya telah menambahkan sedikit pembandingan di bawah ini untuk mengilustrasikan runtime lebih cepat panda pada skala yang lebih besar yaitu entri 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
abby sobh
sumber
7

Ini adalah cara umum untuk mencari nilai dalam daftar kamus:

def search_dictionaries(key, value, list_of_dictionaries):
    return [element for element in list_of_dictionaries if element[key] == value]
ipegasus
sumber
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]

Ini adalah salah satu cara ...

Niclas Nilsson
sumber
1
Saya mungkin menyarankan [d untuk x dalam nama jika d.get ('name', '') == 'Pam'] ... untuk dengan anggun menangani entri dalam "nama" yang tidak memiliki kunci "nama".
Jim Dennis
6

Cukup menggunakan pemahaman daftar:

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

Kode sampel:

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'}
Teoretik
sumber
5

Anda dapat mencapai ini dengan menggunakan filter dan metode selanjutnya dengan Python.

filter metode menyaring urutan yang diberikan dan mengembalikan iterator. nextMetode menerima iterator dan mengembalikan elemen berikutnya dalam daftar.

Jadi Anda dapat menemukan elemen dengan,

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)

dan hasilnya adalah,

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

Catatan: Kode di atas akan kembali Nonememetikan jika nama yang kita cari tidak ditemukan.

Manoj Kumar S
sumber
Ini jauh lebih lambat daripada daftar pemahaman.
AnupamChugh
4

Pikiran pertama saya adalah Anda mungkin ingin mempertimbangkan membuat kamus kamus ini ... jika, misalnya, Anda akan mencarinya lebih dari beberapa kali.

Namun itu mungkin merupakan optimasi prematur. Apa yang salah dengan:

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]
Jim Dennis
sumber
Sebenarnya Anda dapat memiliki kamus dengan nama = Tidak ada item di dalamnya; tetapi itu tidak akan benar-benar berfungsi dengan pemahaman daftar ini dan mungkin tidak waras untuk mengizinkannya di penyimpanan data Anda.
Jim Dennis
1
menegaskan dapat dilewati jika mode debug tidak aktif.
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'}
raja robert
sumber
3

Salah satu cara sederhana menggunakan pemahaman daftar adalah, jika ldaftar

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

kemudian

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

Anda dapat mencoba ini:

''' 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'} 
Siddharth Satpathy
sumber
1

Berikut ini adalah perbandingan menggunakan daftar iterating throuhg, menggunakan filter + lambda atau refactoring (jika diperlukan atau valid untuk kasus Anda) kode Anda untuk dict dicts daripada daftar dicts

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

Dan hasilnya adalah ini:

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

Kesimpulan: Jelas memiliki kamus dicts adalah cara yang paling efisien untuk dapat mencari dalam kasus-kasus itu, di mana Anda tahu mengatakan Anda akan mencari dengan id saja. Menariknya menggunakan filter adalah solusi paling lambat.

Kőhalmy Zoltán
sumber
0

Anda harus melalui semua elemen daftar. Tidak ada jalan pintas!

Kecuali di tempat lain Anda menyimpan kamus dari nama-nama yang menunjuk ke item-item dari daftar, tetapi kemudian Anda harus berhati-hati dengan konsekuensi memunculkan elemen dari daftar Anda.

jimifiki
sumber
Dalam kasus daftar yang tidak disortir dan kunci yang hilang pernyataan ini benar, tetapi tidak secara umum. Jika daftar diketahui disortir, semua elemen tidak perlu diulangi. Juga, jika satu catatan dipukul dan Anda tahu kunci unik atau hanya memerlukan satu elemen, maka iterasi dapat dihentikan dengan item tunggal dikembalikan.
user25064
lihat jawaban dari @ user334856
Melih Yıldız
@ MelihYıldız 'mungkin saya tidak jelas dalam pernyataan saya. Dengan menggunakan daftar pemahaman user334856 dalam jawaban stackoverflow.com/a/8653572/512225 akan melalui seluruh daftar. Ini menegaskan pernyataan saya. Jawaban yang Anda referensikan adalah cara lain untuk mengatakan apa yang saya tulis.
jimifiki
0

Saya menemukan utas ini ketika saya mencari jawaban untuk pertanyaan yang sama. Sementara saya menyadari bahwa ini adalah jawaban yang terlambat, saya pikir saya akan berkontribusi jika itu berguna bagi orang lain:

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
Doug R.
sumber
0

Sebagian besar (jika tidak semua) implementasi yang diusulkan di sini memiliki dua kelemahan:

  • Mereka menganggap hanya satu kunci yang harus dilewati untuk pencarian, sementara itu mungkin menarik untuk memiliki lebih banyak untuk dict kompleks
  • Mereka menganggap semua kunci yang dilewati untuk pencarian ada di dikt, maka mereka tidak berurusan dengan benar dengan KeyError yang terjadi ketika tidak.

Proposisi yang diperbarui:

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)

Mungkin bukan yang paling pythonic, tapi setidaknya sedikit lebih aman.

Pemakaian:

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

The Inti .

onekiloparsec
sumber