Elenco Python di ricerca dizionari


450

Supponiamo di avere questo:

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

e cercando "Pam" come nome, voglio recuperare il dizionario correlato: {name: "Pam", age: 7}

Come raggiungere questo obiettivo?

Risposte:


516

È possibile utilizzare un'espressione del generatore :

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

Se devi gestire l'elemento che non si trova lì, puoi fare ciò che l'utente Matt ha suggerito nel suo commento e fornire un valore predefinito usando un'API leggermente diversa:

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

E per trovare l'indice dell'elemento, anziché l'elemento stesso, puoi enumerare () l'elenco:

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

230
Solo per risparmiare un po 'di tempo a qualcun altro, se hai bisogno di un valore predefinito nell'evento "Pam" non è nell'elenco: next ((elemento per elemento in dicts se item ["name"] == "Pam") , Nessuna)
Matt,

1
Che dire [item for item in dicts if item["name"] == "Pam"][0]?
Moberg,

3
@Moberg, questa è ancora una comprensione dell'elenco, quindi ripeterà l'intera sequenza di input indipendentemente dalla posizione dell'elemento corrispondente.
Frédéric Hamidi,

7
Ciò genererà un errore di arresto se la chiave non è presente nel dizionario
Kishan,

3
@Siemkowski: quindi aggiungere enumerate()per generare un indice di esecuzione: next(i for i, item in enumerate(dicts) if item["name"] == "Pam").
Martijn Pieters

218

Questo mi sembra il modo più pitonico:

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

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

risultato (restituito come elenco in Python 2):

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

Nota: in Python 3, viene restituito un oggetto filtro. Quindi la soluzione python3 sarebbe:

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

14
Vale la pena notare che questa risposta restituisce un elenco con tutte le corrispondenze per "Pam" nelle persone, in alternativa è possibile ottenere un elenco di tutte le persone che non sono "Pam" modificando l'operatore di confronto in! =. +1
Onema,

2
Vale anche la pena ricordare che il risultato è un oggetto filtro, non un elenco: se si desidera utilizzare cose del genere len(), è necessario prima chiamare list()il risultato. Oppure: stackoverflow.com/questions/19182188/...
wasabigeek

@wasabigeek questo è ciò che dice il mio 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) list So ris alist
PaoloC

1
Le comprensioni dell'elenco sono considerate più Pythonic di map / filter / reduce: stackoverflow.com/questions/5426754/google-python-style-guide
jrc

2
Ottieni la prima partita:next(filter(lambda x: x['name'] == 'Pam', dicts))
xgMz

60

@La risposta di Frédéric Hamidi è ottima. In Python 3.x la sintassi per è .next()leggermente cambiata. Quindi una leggera modifica:

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

Come menzionato nei commenti di @Matt, è possibile aggiungere un valore predefinito come tale:

>>> 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
Questa è la risposta migliore per Python 3.x. Se hai bisogno di un elemento specifico dai dicts, come age, puoi scrivere: next ((item.get ('age') per l'articolo in dicts se item ["name"] == "Pam"), False)
cwhisperer

48

È possibile utilizzare una comprensione dell'elenco :

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

4
Questo è bello perché restituisce tutte le partite se ce ne sono più di una. Non è esattamente quello che la domanda ha posto, ma è quello di cui avevo bisogno! Grazie!
user3303554,

Nota anche questo restituisce un elenco!
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")

Restituirà il primo dizionario nell'elenco con il nome specificato.
Ricky Robinson,

5
Giusto per rendere questa routine molto utile un po 'più generica:def search(list, key, value): for item in list: if item[key] == value: return item
Jack James,

30

Ho testato vari metodi per esaminare un elenco di dizionari e restituire i dizionari in cui la chiave x ha un determinato valore.

risultati:

  • Velocità: comprensione dell'elenco> espressione del generatore >> iterazione normale dell'elenco >>> filtro.
  • Tutte le scale sono lineari con il numero di dadi nell'elenco (10x dimensioni dell'elenco -> 10x tempo).
  • Le chiavi per dizionario non influiscono in modo significativo sulla velocità per grandi quantità (migliaia) di chiavi. Si prega di vedere questo grafico che ho calcolato: https://imgur.com/a/quQzv (nomi dei metodi vedi sotto).

Tutti i test eseguiti con 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

risultati:

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

Ho aggiunto la funzione z () che implementa successivamente come indicato da Frédéric Hamidi sopra. Ecco i risultati dal profilo Py.
leon

10

Per aggiungere solo un po 'a @ FrédéricHamidi.

Nel caso in cui non si è sicuri che una chiave sia nell'elenco dei dadi, qualcosa del genere sarebbe utile:

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

o semplicementeitem.get("name") == "Pam"
Andreas Haferburg,

10

Hai mai provato il pacchetto Panda? È perfetto per questo tipo di attività di ricerca e anche ottimizzato.

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

Ho aggiunto un po 'di benchmarking di seguito per illustrare i tempi di esecuzione più veloci dei panda su una scala più ampia, vale a dire 100k + voci:

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

Questo è un modo generale di cercare un valore in un elenco di dizionari:

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]

Questo è un modo ...


1
Potrei suggerire [d per x nei nomi se d.get ('name', '') == 'Pam'] ... per gestire con grazia tutte le voci in "names" che non avevano una chiave "name".
Jim Dennis,

6

Semplicemente usando la comprensione della lista:

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

Codice di esempio:

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

Puoi farlo con l'uso del filtro e dei metodi successivi in ​​Python.

filter Il metodo filtra la sequenza specificata e restituisce un iteratore. nextIl metodo accetta un iteratore e restituisce l'elemento successivo nell'elenco.

Quindi puoi trovare l'elemento per,

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)

e l'output è,

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

Nota: il codice sopra riportato verrà restituito in Nonecaso di mancata ricerca del nome da cercare.


Questo è molto più lento della comprensione dell'elenco.
AnupamChugh

4

Il mio primo pensiero sarebbe che potresti voler prendere in considerazione la creazione di un dizionario di questi dizionari ... se, ad esempio, avessi cercato più di un piccolo numero di volte.

Tuttavia, questa potrebbe essere un'ottimizzazione prematura. Cosa sarebbe sbagliato con:

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]

In realtà puoi avere un dizionario con un nome = nessuna voce in esso; ma questo non funzionerebbe davvero con questa comprensione dell'elenco e probabilmente non è ragionevole consentirlo nel tuo archivio dati.
Jim Dennis,

1
gli assert possono essere saltati se la modalità debug è disattivata.
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

Un modo semplice di usare la comprensione dell'elenco è, se lo lè, l'elenco

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

poi

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

2

Puoi provare questo:

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

Ecco un confronto utilizzando l'elenco iterativo iterante, usando filter + lambda o refactoring (se necessario o valido per il tuo caso) il tuo codice per dettare i dadi anziché l'elenco dei dadi

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

E l'output è questo:

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

Conclusione: avere chiaramente un dizionario di dicts è il modo più efficace per cercare in quei casi, dove sai che cercherai solo per ID. interessante usare il filtro è la soluzione più lenta.


1

La maggior parte (se non tutte) le implementazioni proposte qui hanno due difetti:

  • Presumono solo una chiave da passare per la ricerca, mentre può essere interessante averne di più per un dict complesso
  • Presumono che tutti i tasti passati per la ricerca esistano nei dicts, quindi non gestiscono correttamente KeyError che si verifica quando non lo è.

Una proposta aggiornata:

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)

Forse non il più pitonico, ma almeno un po 'più sicuro.

Uso:

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

L' essenza .


0

Devi passare attraverso tutti gli elementi dell'elenco. Non esiste una scorciatoia!

A meno che da qualche altra parte conservi un dizionario dei nomi che punta agli elementi dell'elenco, ma poi devi prenderti cura delle conseguenze della comparsa di un elemento dal tuo elenco.


Nel caso di un elenco non ordinato e di una chiave mancante questa affermazione è corretta, ma non in generale. Se l'elenco è noto per essere ordinato, non è necessario ripetere l'iterazione di tutti gli elementi. Inoltre, se viene colpito un singolo record e sai che le chiavi sono univoche o richiedono solo un elemento, l'iterazione può essere interrotta con il singolo elemento restituito.
user25064

vedi la risposta di @ user334856
Melih Yıldız "

@ MelihYıldız 'forse non ero chiaro nella mia dichiarazione. Usando un'user334856 di lista in risposta stackoverflow.com/a/8653572/512225 sta attraversando l'intera lista. Questo conferma la mia affermazione. La risposta a cui fai riferimento è un altro modo di dire quello che ho scritto.
jimifiki,

0

Ho trovato questa discussione mentre cercavo una risposta alla stessa domanda. Mentre mi rendo conto che è una risposta tardiva, ho pensato di contribuire nel caso fosse utile a chiunque altro:

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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.