2011-12-28 11 views
217

Supongamos que tengo esto:lista de Python de diccionarios de búsqueda

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

y mediante la búsqueda "Pam", como nombre, quiero recuperar el diccionario relacionada: {name: "Pam", edad: 7}

¿Cómo lograr esto?

Respuesta

17
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") 
+0

Se devolverán el primer diccionario en la lista con el nombre dado. –

+4

Simplemente para que esta rutina sea un poco más genérica: 'def search (list, key, value): para el elemento en la lista: if item [key] == value: return item' –

279

Se puede utilizar un generator expression:

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

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

Solo para salvar a alguien un poco de tiempo, si necesita un valor predeterminado en caso de que "Pam" simplemente no esté en la lista: siguiente ((elemento para el elemento en los artículos si el elemento ["nombre"] == "Pam"), Ninguno) – Matt

+0

¿Qué pasa con '[item for item in dicts if item [" name "] ==" Pam "] [0]'? – Moberg

+2

@Moberg, sigue siendo una lista de comprensión, por lo que se repetirá en toda la secuencia de entrada, independientemente de la posición del elemento coincidente. –

27

Se puede utilizar un list comprehension:

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

Esto es bueno porque devuelve todas las coincidencias si hay más de una. No es exactamente lo que me pidió la pregunta, ¡pero es lo que necesitaba! ¡Gracias! – user3303554

4

Mi primer pensamiento sería que es posible que desee considerar la creación de un diccionario de estos diccionarios ... si, por ejemplo, lo estuvieras buscando más que pocas veces.

Sin embargo, eso podría ser una optimización prematura. ¿Qué tendría de malo:

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] 
+0

En realidad, puede tener un diccionario con un nombre = Ninguno elemento; pero eso no funcionaría realmente con esta lista de comprensión y probablemente no es sensato permitirlo en su tienda de datos. –

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

Esta es una manera ...

+1

Podría sugerir [d para x en los nombres si d.get ('nombre', '') == 'Pam'] ... para manejar con gracia cualquier entrada en "nombres" que no tuviera una clave de "nombre". –

+1

Claro. Esa es la forma correcta de hacerlo. –

-1

Tienes que ir a través de todos los elementos de la lista. ¡No hay un atajo!

A menos que esté en otro lugar, conserve un diccionario de los nombres que apuntan a los elementos de la lista, pero luego debe ocuparse de las consecuencias de sacar un elemento de su lista.

+0

En el caso de una lista desordenada y una clave faltante, esta afirmación es correcta, pero no en general. Si se sabe que la lista está ordenada, no es necesario repetir todos los elementos. Además, si se golpea un solo registro y usted sabe que las claves son únicas o solo requieren un elemento, entonces la iteración puede detenerse con el único elemento devuelto. – user25064

+0

ver la respuesta de @ user334856 –

+0

@ MelihYıldız 'tal vez no estaba claro en mi declaración. Al usar una lista de comprensión, el usuario334856 en la respuesta http://stackoverflow.com/a/8653572/512225 está revisando toda la lista. Esto confirma mi afirmación. La respuesta que refieres es otra forma de decir lo que escribí. – jimifiki

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

Ésta es una manera general de buscar un valor en una lista de diccionarios:

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

Esto me parece la forma más Pythonic:

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

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

resultado (devuelto como una lista en Python 2):

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

Nota: en Python 3, se devuelve un objeto de filtro.

+8

Vale la pena señalar que esta respuesta arroja una lista con todas las coincidencias para 'Pam' en las personas, alternativamente, podríamos obtener una lista de todas las personas que no son 'Pam' cambiando el operador de comparación a! =. +1 – Onema

+0

También vale la pena mencionar que el resultado es un objeto de filtro, no una lista: si desea usar cosas como 'len()', primero debe llamar a 'list()' en el resultado. O bien: https://stackoverflow.com/questions/19182188/how-to-find-the-length-of-a-filter-object-in-python – wasabigeek

+0

@wasabigeek Esto es lo que mi Python 2.7 dice: personas = [ {'name': "Tom", 'age': 10}, {'name': "Mark", "age": 5}, {'name': "Pam", 'age': 7 }] r = filtro (persona lambda: persona [ 'nombre'] == 'Pam', personas) tipo (r) lista Así '' R' es una list' – PaoloC

23

@ La respuesta de Frédéric Hamidi es genial. En Python 3.x la sintaxis para .next() cambió ligeramente. Así, una ligera modificación:

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

Como se mencionó en los comentarios de @ Matt, se puede añadir un valor por defecto como tal:

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

Para añadir un poquito a @ FrédéricHamidi.

En caso de que no está seguro de una clave se encuentra en la la lista de dicts, algo como esto ayudaría:

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

Aquí es una comparación utilizando la lista throuhg iteración, usando el filtro + lambda o refactorización (si es necesario o válida en su caso) su código para dict de dicts en lugar de lista de 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 

y la salida es la siguiente:

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

Conclusión: Claramente tener un diccionario de dicts es la forma más eficiente de poder buscar en esos casos, donde sabe que solo buscará por id. interesante usar filtro es la solución más lenta.

3

¿Alguna vez ha probado el paquete de pandas? Es perfecto para este tipo de tarea de búsqueda y optimizado también.

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

He añadido un poco de la evaluación comparativa a continuación para ilustrar pandas tiempos de ejecución más rápido en una mayor escala, es decir, 100k + entradas:

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 
0

encontré este hilo cuando yo estaba buscando una respuesta a la misma pregunta . Mientras me di cuenta que se trata de una respuesta tardía, pensé que sería contribuyen en caso de que sea útil para cualquier otra persona:

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 
1

He probado varios métodos para ir a través de una lista de diccionarios y devuelven los diccionarios donde tecla x tiene un cierto valor

Resultados:

  • Velocidad: lista de comprensión> generador de expresión normal de la lista >> >>> iteración del filtro.
  • Todas las escalas lineales con el número de dicts en la lista (10x list size -> 10x time).
  • Las claves por diccionario no afectan significativamente la velocidad para grandes cantidades (miles) de claves. Por favor, consulte este gráfico que calculé: https://imgur.com/a/quQzv (los nombres de los métodos se encuentran a continuación).

Todas las pruebas realizadas con Python 3.6 0.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 

Resultados:

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