¿Cómo clasifico una lista de diccionarios por un valor del diccionario?

2281

Tengo una lista de diccionarios y quiero que cada elemento se ordene por un valor específico.

Tenga en cuenta la lista:

[{'name':'Homer', 'age':39}, {'name':'Bart', 'age':10}]

Cuando se ordena por name, debería convertirse en:

[{'name':'Bart', 'age':10}, {'name':'Homer', 'age':39}]
1
  • 3
    Leer la respuesta y mirar operator.itemgetter . ¿Puedo ordenar varios valores en el mismo proceso (por ejemplo, tenemos [{'name':'Bart', 'age':10, 'note':3},{'name':'Homer','age':10,'note':2},{'name':'Vasile','age':20,'note':3}] Y para usar: from operator import itemgetter newlist = sorted(old_list, key=itemgetter(-'note','name') EDITAR: Probado, y está funcionando pero no sé cómo anotar DESC y nombrar ASC.
    Claudiu
    21 de mayo de 2020 a las 7:13
2954

La sorted()función toma un key=parámetro

newlist = sorted(list_to_be_sorted, key=lambda k: k['name']) 

Alternativamente, puede usar en operator.itemgetterlugar de definir la función usted mismo

from operator import itemgetter
newlist = sorted(list_to_be_sorted, key=itemgetter('name')) 

Para completar, agregue reverse=Truepara ordenar en orden descendente

newlist = sorted(l, key=itemgetter('name'), reverse=True)
7
  • 45
    Usar la clave no solo es más limpio sino también más eficiente.
    jfs
    16 de septiembre de 2008 a las 15:03
  • 5
    La forma más rápida sería agregar una declaración newlist.reverse (). De lo contrario, puede definir una comparación como cmp = lambda x, y: - cmp (x ['nombre'], y ['nombre']).
    Mario F
    13/10/09 a las 7:14
  • 4
    si el valor de clasificación es un número, podría decir: lambda k: (k ['edad'] * -1) para obtener una clasificación inversa 20 de nov. De 2009 a las 15:16
  • 3
    Esto también se aplica a una lista de tuplas, si usa itemgetter(i)dónde iestá el índice del elemento de tupla para ordenar. 11/07/12 a las 23:14
  • 49
    itemgetteracepta más de un argumento: itemgetter(1,2,3)es una función que devuelve una tupla como obj[1], obj[2], obj[3], por lo que puede usarla para hacer ordenaciones complejas.
    Bakuriu
    7 de septiembre de 2012 a las 17:59
192
import operator

Para ordenar la lista de diccionarios por clave = 'nombre':

list_of_dicts.sort(key=operator.itemgetter('name'))

Para ordenar la lista de diccionarios por clave = 'edad':

list_of_dicts.sort(key=operator.itemgetter('age'))
4
  • 11
    De todos modos, ¿combinar nombre y edad? (como en SQL ORDER BY nombre, edad?) 17/02/10 a las 13:10
  • 37
    @monojohnny: Sí, sólo tiene la clave Devuelve una tupla, key=lambda k: (k['name'], k['age']). (o key=itemgetter('name', 'age')). la tupla cmpcomparará cada elemento a su vez. es malditamente brillante.
    Claudiu
    4 de septiembre de 2013 a las 22:21
  • 1
    En la documentación ( docs.python.org/2/tutorial/datastructures.html ) no se describe el keyargumento opcional para list.sort(). ¿Alguna idea de dónde encontrar eso?
    TTT
    21/0214 a las 15:21
  • 2
    @TTT: Consulte la documentación de la biblioteca para listy amigos.
    Kevin
    19/02/2015 a las 14:56
84
my_list = [{'name':'Homer', 'age':39}, {'name':'Bart', 'age':10}]

my_list.sort(lambda x,y : cmp(x['name'], y['name']))

my_list ahora será lo que quieras.

O mejor:

Desde Python 2.4, hay un keyargumento que es más eficiente y más ordenado:

my_list = sorted(my_list, key=lambda k: k['name'])

... la lambda es, en mi opinión, más fácil de entender que operator.itemgetter, pero su kilometraje puede variar.

3
  • ¿Qué se podría hacer si la clave es desconocida y sigue cambiando? Me refiero a una lista de dictados con solo una clave y un valor, pero la clave y el valor no se pueden definir porque siguen cambiando.
    Sam
    1 dic.20 a las 14:51
  • 1
    Necesitaría más ejemplos para mirar. Intente enviar una posible solución en codereview stackexchange y pregunte si hay una mejor manera.
    pjz
    30 dic.20 a las 1:02
  • @Sam si desea ordenar por el valor de la clave única en el dict, incluso si no conoce la clave, puede hacerlo key=lambda k: list(k.values())[0]
    pjz
    10 de marzo a las 6:38
61

Si desea ordenar la lista por varias claves, puede hacer lo siguiente:

my_list = [{'name':'Homer', 'age':39}, {'name':'Milhouse', 'age':10}, {'name':'Bart', 'age':10} ]
sortedlist = sorted(my_list , key=lambda elem: "%02d %s" % (elem['age'], elem['name']))

Es bastante pirateado, ya que se basa en convertir los valores en una representación de una sola cadena para la comparación, pero funciona como se espera para los números, incluidos los negativos (aunque deberá formatear su cadena de manera adecuada con relleno de ceros si está usando números).

4
  • 3
    ordenado usando timsort que es estable, puede llamar ordenado varias veces para tener una ordenación según varios criterios
    njzk2
    29 de mayo de 2013 a las 13:41
  • 1
    El comentario de njzk2 no me quedó claro de inmediato, así que encontré lo siguiente. Puede ordenar dos veces como sugiere njzk2, o pasar múltiples argumentos a operator.itemgetter en la respuesta superior. Enlace: stackoverflow.com/questions/5212870/… 23/0813 a las 21:05
  • 17
    No es necesario convertir a cadena. Solo devuelve una tupla como clave. 15 de diciembre de 2013 a las 4:55
  • Ordenar varias veces es la solución genérica más sencilla sin hacks: stackoverflow.com/a/29849371/1805397 24/04/15 a las 13:59
37
a = [{'name':'Homer', 'age':39}, ...]

# This changes the list a
a.sort(key=lambda k : k['name'])

# This returns a new list (a is not modified)
sorted(a, key=lambda k : k['name']) 
34
import operator
a_list_of_dicts.sort(key=operator.itemgetter('name'))

'clave' se usa para ordenar por un valor arbitrario y 'itemgetter' establece ese valor en el atributo 'nombre' de cada elemento.

25

Supongo que te refieres a:

[{'name':'Homer', 'age':39}, {'name':'Bart', 'age':10}]

Esto se ordenaría así:

sorted(l,cmp=lambda x,y: cmp(x['name'],y['name']))
23

Puede utilizar una función de comparación personalizada o puede pasar una función que calcule una clave de clasificación personalizada. Eso suele ser más eficiente ya que la clave solo se calcula una vez por elemento, mientras que la función de comparación se llamaría muchas más veces.

Podrías hacerlo de esta manera:

def mykey(adict): return adict['name']
x = [{'name': 'Homer', 'age': 39}, {'name': 'Bart', 'age':10}]
sorted(x, key=mykey)

Pero la biblioteca estándar contiene una rutina genérica para conseguir artículos de objetos arbitrarios: itemgetter. Así que prueba esto en su lugar:

from operator import itemgetter
x = [{'name': 'Homer', 'age': 39}, {'name': 'Bart', 'age':10}]
sorted(x, key=itemgetter('name'))
23

Usando la transformada de Schwartzian de Perl,

py = [{'name':'Homer', 'age':39}, {'name':'Bart', 'age':10}]

hacer

sort_on = "name"
decorated = [(dict_[sort_on], dict_) for dict_ in py]
decorated.sort()
result = [dict_ for (key, dict_) in decorated]

da

>>> result
[{'age': 10, 'name': 'Bart'}, {'age': 39, 'name': 'Homer'}]

Más sobre la transformación de Perl Schwartzian:

In computer science, the Schwartzian transform is a Perl programming idiom used to improve the efficiency of sorting a list of items. This idiom is appropriate for comparison-based sorting when the ordering is actually based on the ordering of a certain property (the key) of the elements, where computing that property is an intensive operation that should be performed a minimal number of times. The Schwartzian Transform is notable in that it does not use named temporary arrays.

1
  • 10
    Python ha soportado el key=for .sortdesde 2.4, es decir, el año 2004, hace la transformación Schwartzian dentro del código de clasificación, en C; por lo tanto, este método solo es útil en Pythons 2.0-2.3. todos los cuales tienen más de 12 años. 15 feb.15 a las 20:11
20

Debe implementar su propia función de comparación que comparará los diccionarios por valores de claves de nombre. Ver Ordenar Mini-CÓMO HACER de PythonInfo Wiki

2
  • 1
    Esto depende demasiado del enlace. ¿Puede dar una respuesta más completa? 14/08/20 a las 20:51
  • Otros colaboradores ya han proporcionado respuestas adecuadas. Siéntase libre de mantener el enlace o eliminar la respuesta.
    Matej
    17 de agosto de 2020 a las 4:25
16

A veces necesitamos usar lower(). Por ejemplo,

lists = [{'name':'Homer', 'age':39},
  {'name':'Bart', 'age':10},
  {'name':'abby', 'age':9}]

lists = sorted(lists, key=lambda k: k['name'])
print(lists)
# [{'name':'Bart', 'age':10}, {'name':'Homer', 'age':39}, {'name':'abby', 'age':9}]

lists = sorted(lists, key=lambda k: k['name'].lower())
print(lists)
# [ {'name':'abby', 'age':9}, {'name':'Bart', 'age':10}, {'name':'Homer', 'age':39}]
1
  • ¿Por qué necesitamos utilizar lower () en este caso? 14/08/20 a las 20:52
13

Usar el paquete Pandas es otro método, aunque su tiempo de ejecución a gran escala es mucho más lento que los métodos más tradicionales propuestos por otros:

import pandas as pd

listOfDicts = [{'name':'Homer', 'age':39}, {'name':'Bart', 'age':10}]
df = pd.DataFrame(listOfDicts)
df = df.sort_values('name')
sorted_listOfDicts = df.T.to_dict().values()

Aquí hay algunos valores de referencia para una lista pequeña y una lista grande (100k +) de dictados:

setup_large = "listOfDicts = [];\
[listOfDicts.extend(({'name':'Homer', 'age':39}, {'name':'Bart', 'age':10})) for _ in range(50000)];\
from operator import itemgetter;import pandas as pd;\
df = pd.DataFrame(listOfDicts);"

setup_small = "listOfDicts = [];\
listOfDicts.extend(({'name':'Homer', 'age':39}, {'name':'Bart', 'age':10}));\
from operator import itemgetter;import pandas as pd;\
df = pd.DataFrame(listOfDicts);"

method1 = "newlist = sorted(listOfDicts, key=lambda k: k['name'])"
method2 = "newlist = sorted(listOfDicts, key=itemgetter('name')) "
method3 = "df = df.sort_values('name');\
sorted_listOfDicts = df.T.to_dict().values()"

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 LC2: ' + str(t.timeit(100)))
t = timeit.Timer(method3, 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 LC2: ' + str(t.timeit(100)))
t = timeit.Timer(method3, setup_large)
print('Large Method Pandas: ' + str(t.timeit(1)))

#Small Method LC: 0.000163078308105
#Small Method LC2: 0.000134944915771
#Small Method Pandas: 0.0712950229645
#Large Method LC: 0.0321750640869
#Large Method LC2: 0.0206089019775
#Large Method Pandas: 5.81405615807
2
  • 4
    Ejecuté su código y encontré un error en el timeit.Timer args para Large Method Pandas: usted especifica "setup_small" donde debería estar "setup_large". Cambiar ese argumento hizo que el programa se ejecutara sin terminar y lo detuve después de más de 5 minutos. Cuando lo ejecuté con "timeit (1)", Large Method Pandas terminó en 7.3 segundos, mucho peor que LC o LC2.
    clp2
    7 de nov. De 2016 a las 4:05
  • Tienes toda la razón, eso fue un gran descuido de mi parte. ¡Ya no lo recomiendo para casos grandes! He editado la respuesta para permitirlo simplemente como una posibilidad, el caso de uso aún está en debate. 8/11/2016 a las 22:58
13

Aquí está la solución general alternativa: ordena los elementos de un dictado por claves y valores.

La ventaja de esto: no es necesario especificar claves, y aún funcionaría si faltan algunas claves en algunos de los diccionarios.

def sort_key_func(item):
    """ Helper function used to sort list of dicts

    :param item: dict
    :return: sorted list of tuples (k, v)
    """
    pairs = []
    for k, v in item.items():
        pairs.append((k, v))
    return sorted(pairs)
sorted(A, key=sort_key_func)
1
  • ¿Qué quieres decir con "ordena los elementos de un dictado por claves y valores" ? ¿De qué manera se clasifica? ¿Dónde entran los valores? 14/08/20 a las 20:54
9

Si no necesita el original listde dictionaries, puede modificarlo in situ con el sort()método utilizando una función de tecla personalizada.

Función de la tecla:

def get_name(d):
    """ Return the value of a key in a dictionary. """

    return d["name"]

El lista ordenar:

data_one = [{'name': 'Homer', 'age': 39}, {'name': 'Bart', 'age': 10}]

Ordenarlo en el lugar:

data_one.sort(key=get_name)

Si necesita el original list, llame a la sorted()función pasándole listla función de tecla y, luego asigne la orden devuelta lista una nueva variable:

data_two = [{'name': 'Homer', 'age': 39}, {'name': 'Bart', 'age': 10}]
new_data = sorted(data_two, key=get_name)

Impresión data_oney new_data.

>>> print(data_one)
[{'name': 'Bart', 'age': 10}, {'name': 'Homer', 'age': 39}]
>>> print(new_data)
[{'name': 'Bart', 'age': 10}, {'name': 'Homer', 'age': 39}]
9

Digamos que tengo un diccionario Dcon los elementos siguientes. Para ordenar, simplemente use el argumento clave sortedpara pasar una función personalizada como se muestra a continuación:

D = {'eggs': 3, 'ham': 1, 'spam': 2}
def get_count(tuple):
    return tuple[1]

sorted(D.items(), key = get_count, reverse=True)
# Or
sorted(D.items(), key = lambda x: x[1], reverse=True)  # Avoiding get_count function call

Mira esto .

9

He sido un gran admirador de un filtro con lambda. Sin embargo, no es la mejor opción si considera la complejidad del tiempo.

Primera opción

sorted_list = sorted(list_to_sort, key= lambda x: x['name'])
# Returns list of values

Segunda opción

list_to_sort.sort(key=operator.itemgetter('name'))
# Edits the list, and does not return a new list

Comparación rápida de tiempos de ejecución

# First option
python3.6 -m timeit -s "list_to_sort = [{'name':'Homer', 'age':39}, {'name':'Bart', 'age':10}, {'name':'Faaa', 'age':57}, {'name':'Errr', 'age':20}]" -s "sorted_l=[]" "sorted_l = sorted(list_to_sort, key=lambda e: e['name'])"

1000000 loops, best of 3: 0.736 µsec per loop

# Second option
python3.6 -m timeit -s "list_to_sort = [{'name':'Homer', 'age':39}, {'name':'Bart', 'age':10}, {'name':'Faaa', 'age':57}, {'name':'Errr', 'age':20}]" -s "sorted_l=[]" -s "import operator" "list_to_sort.sort(key=operator.itemgetter('name'))"

1000000 loops, best of 3: 0.438 µsec per loop

6

Si el rendimiento es un problema, lo usaría en operator.itemgetterlugar de las lambdafunciones integradas que funcionan más rápido que las funciones creadas a mano. La itemgetterfunción parece funcionar aproximadamente un 20% más rápido que lambdasegún mis pruebas.

De https://wiki.python.org/moin/PythonSpeed :

Likewise, the builtin functions run faster than hand-built equivalents. For example, map(operator.add, v1, v2) is faster than map(lambda x,y: x+y, v1, v2).

He aquí una comparación de la clasificación de velocidad por medio lambdavs itemgetter.

import random
import operator

# Create a list of 100 dicts with random 8-letter names and random ages from 0 to 100.
l = [{'name': ''.join(random.choices(string.ascii_lowercase, k=8)), 'age': random.randint(0, 100)} for i in range(100)]

# Test the performance with a lambda function sorting on name
%timeit sorted(l, key=lambda x: x['name'])
13 µs ± 388 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

# Test the performance with itemgetter sorting on name
%timeit sorted(l, key=operator.itemgetter('name'))
10.7 µs ± 38.1 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

# Check that each technique produces the same sort order
sorted(l, key=lambda x: x['name']) == sorted(l, key=operator.itemgetter('name'))
True

Ambas técnicas ordenan la lista en el mismo orden (verificado mediante la ejecución de la declaración final en el bloque de código), pero la primera es un poco más rápida.