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

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

preguntado el 16 de septiembre de 08 a las 12:09

Leer la respuesta y mirar operador.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. -

17 Respuestas

Puede parecer más limpio usando una clave en lugar de un cmp:

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

o como sugirieron JFSebastian y otros,

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

Para completar (como se señaló en los comentarios de fitzgeraldsteele), agregue reverse=True ordenar descendente

newlist = sorted(l, key=itemgetter('name'), reverse=True)

Respondido el 31 de diciembre de 14 a las 19:12

Usar la clave no solo es más limpio sino también más eficiente. - jfs

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

si el valor de clasificación es un número, podría decir: lambda k: (k ['edad'] * -1) para obtener una clasificación inversa - Philluminati

Esto también se aplica a una lista de tuplas, si usa itemgetter(i) donde i es el índice del elemento de tupla para ordenar. - radicando

itemgetter acepta 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 utilizarlo para realizar ordenaciones complejas. - Bakuriú

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

contestado el 01 de mayo de 13 a las 15:05

De todos modos, ¿combinar nombre y edad? (como en SQL ORDER BY nombre, edad?) - monojohnny

@monojohnny: sí, solo haz que la clave devuelva una tupla, key=lambda k: (k['name'], k['age']). (o key=itemgetter('name', 'age')). tupla cmp comparará cada elemento a su vez. es malditamente brillante. - Claudiu

En la documentación (docs.python.org/2/tutorial/datastructures.html) el opcional key argumento a favor list.sort() no se describe. ¿Alguna idea de dónde encontrar eso? - TTT

@TTT: Ver el documentación de la biblioteca en list y amigos. - Kevin

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 una key El argumento es más eficiente y 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 tu kilometraje puede variar.

Respondido el 25 de Septiembre de 20 a las 16:09

¿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. - Diana

Necesitaría más ejemplos para mirar. Intente enviar una posible solución en codereview stackexchange y pregunte si hay una mejor manera. - PJZ

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

Respondido 14 ago 20, 21:08

ordenado usando timsort que es estable, puede llamar ordenado varias veces para tener una ordenación según varios criterios: njzk2

El comentario de njzk2 no me quedó claro de inmediato, así que encontré lo siguiente. Puede ordenar dos veces como sugiere njzk2, o pasar varios argumentos a operator.itemgetter en la respuesta superior. Enlace: stackoverflow.com/questions/5212870/… - Permafactura

No es necesario convertir a cadena. Solo devuelve una tupla como clave. - winston ewert

Ordenar varias veces es la solución genérica más sencilla sin trucos: stackoverflow.com/a/29849371/1805397 - exterior reforzado

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.

Respondido el 16 de Septiembre de 08 a las 15:09

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

respondido 17 mar '17, 10:03

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

Respondido el 16 de Septiembre de 08 a las 15:09

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 obtener elementos 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'))

Respondido el 16 de Septiembre de 08 a las 15:09

Usando el Transformada de Schwartzian de Perl,

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

do

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:

En ciencias de la computación, la transformada de Schwartzian es un lenguaje de programación de Perl que se usa para mejorar la eficiencia de ordenar una lista de elementos. Este lenguaje es apropiado para la ordenación basada en comparación cuando la ordenación se basa realmente en la ordenación de una determinada propiedad (la clave) de los elementos, donde calcular esa propiedad es una operación intensiva que debe realizarse un número mínimo de veces. La Transformada de Schwartzian es notable porque no utiliza matrices temporales con nombre.

Respondido 14 ago 20, 21:08

Python ha apoyado el key= en .sort desde 2.4, que es el año 2004, hace la transformación de 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. - antti haapala

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

Respondido el 16 de Septiembre de 08 a las 15:09

Esto depende demasiado del enlace. ¿Puede dar una respuesta más completa? - Pedro Mortensen

Otros colaboradores ya han proporcionado respuestas adecuadas. Siéntase libre de mantener el enlace o eliminar la respuesta. - Matej

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

Respondido 22 Jul 20, 14:07

¿Por qué ¿Necesitamos usar lower () en este caso? - Pedro Mortensen

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)

Respondido 14 ago 20, 21:08

Que quieres decir con "ordena los elementos de un dictado por claves y valores"? ¿De qué manera se clasifica? ¿Dónde entran los valores? - Pedro Mortensen

Usando el pandas package 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

Respondido 22 Jul 20, 14:07

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

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. - abby sollozo

Digamos que tengo un diccionario D con los elementos siguientes. Para ordenar, simplemente use el argumento clave en sorted para 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

Revise este fuera.

Respondido 14 ago 20, 21:08

Si no necesita el original list of dictionaries, puede modificarlo in situ con 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"]

La list a ordenar:

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

Ordenarlo en el lugar:

data_one.sort(key=get_name)

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

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

Impresión data_one y new_data.

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

Respondido el 19 de diciembre de 17 a las 17:12

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 bucles, lo mejor de 3: 0.736 µseg por bucle

# 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 bucles, lo mejor de 3: 0.438 µseg por bucle

Respondido 14 ago 20, 22:08

Si el rendimiento es una preocupación, usaría operator.itemgetter en lugar de lambda ya que las funciones integradas funcionan más rápido que las funciones creadas a mano. La itemgetter La función parece funcionar aproximadamente un 20% más rápido que lambda basado en mis pruebas.

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

Del mismo modo, las funciones integradas se ejecutan más rápido que los equivalentes construidos a mano. Por ejemplo, map (operator.add, v1, v2) es más rápido que map (lambda x, y: x + y, v1, v2).

Aquí hay una comparación de la velocidad de clasificación usando lambda vs 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.

Respondido 14 ago 20, 22:08

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.