recorrer - lista de diccionarios python
Python lista de búsqueda de diccionarios (16)
¿Alguna vez has probado el paquete de pandas? Es perfecto para este tipo de tareas 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 agregado un poco de evaluación comparativa a continuación para ilustrar los tiempos de ejecución más rápidos de los pandas en una escala mayor, es decir, más de 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
Supongamos que tengo esto:
[
{"name": "Tom", "age": 10},
{"name": "Mark", "age": 5},
{"name": "Pam", "age": 7}
]
y buscando "Pam" como nombre, quiero recuperar el diccionario relacionado: {nombre: "Pam", edad: 7}
¿Cómo lograr esto?
Aquí hay una comparación que utiliza iterating throuhg list, filter + lambda o refactoring (si es necesario o válido para su caso) su código para dictados de dictados en lugar de lista de dictados
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 esta:
List of Dictionries took: 0.0099310874939
Using filter took: 0.0121960639954
Dict of Dicts took: 4.05311584473e-06
Conclusión: es evidente que tener un diccionario de dictados es la forma más eficiente de poder buscar en esos casos, donde sabe que solo buscará por ID. Curiosamente usar filtro es la solución más lenta.
Encontré este hilo cuando estaba buscando una respuesta a la misma pregunta. Aunque me doy cuenta de que es una respuesta tardía, pensé que podría contribuir en caso de que sea útil para alguien más:
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
Esta es una forma 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]
Esto me parece la forma más pitónica:
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.
La respuesta de @Frédéric Hamidi es genial. En Python 3.x, la sintaxis de .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 menciona en los comentarios de @Matt, puede agregar un valor predeterminado 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
>>>
Mi primer pensamiento sería que tal vez desee considerar la creación de un diccionario de estos diccionarios ... si, por ejemplo, lo buscaría más de un pequeño número de veces.
Sin embargo, eso podría ser una optimización prematura. ¿Qué estaría mal 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]
Para agregar solo un poquito a @ FrédéricHamidi.
En caso de que no esté seguro de que haya una clave en la lista de dictados, algo como esto ayudaría:
next((item for item in dicts if item.get("name") and item["name"] == "Pam"), None)
Probé varios métodos para revisar una lista de diccionarios y devolver los diccionarios donde la clave x tiene un cierto valor.
Resultados:
- Velocidad: comprensión de lista> expresión del generador >> iteración de lista normal >>> filtro.
- Todas las escalas son lineales con el número de dictados en la lista (tamaño de lista 10x -> 10x tiempo).
- Las claves por diccionario no afectan la velocidad significativamente para grandes cantidades (miles) de claves. Por favor, vea 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 .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
Puede utilizar una lista de comprensión :
def search(name, people):
return [element for element in people if element[''name''] == name]
Puedes usar una expresión generadora :
>>> 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''}
Tienes que pasar por todos los elementos de la lista. No hay un atajo!
A menos que en algún otro lugar guarde un diccionario de los nombres que apunten a los elementos de la lista, pero luego tendrá que ocuparse de las consecuencias de sacar un elemento de su lista.
Simplemente utilizando la lista de comprensión:
[i for i in dct if i[''name''] == ''Pam''][0]
Código de muestra:
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''}
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''}
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 ...
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")