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

1899

Tengo una lista de diccionarios y quiero que cada elemento se ordene por valores de propiedad específicos.

Tenga en cuenta la matriz a continuación,

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

Cuando se ordena por name, debe convertirse

[{'name':'Bart', 'age':10}, {'name':'Homer', 'age':39}]
masi
fuente
Leyendo la respuesta y mirando operator.itemgetter . Puedo ordenar en el valor múltiple 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}] ya utilizar: from operator import itemgetter newlist = sorted(old_list, key=itemgetter(-'note','name') EDIT: Probado, y está funcionando pero no sabemos cómo hacer nota DESC ASC y el nombre.
Claudiu

Respuestas:

2471

Puede verse más limpio usando una tecla en lugar de un cmp:

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

o como JFSebastian y otros sugirieron,

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

Para completar (como se señala en los comentarios de fitzgeraldsteele), agregue reverse=Truepara ordenar descendente

newlist = sorted(l, key=itemgetter('name'), reverse=True)
Mario F
fuente
34
Usar la llave no solo es más limpio sino también más eficiente.
jfs
55
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
3
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
2
Esto también se aplica a una lista de tuplas, si usa itemgetter(i)where ies el índice del elemento de tupla para ordenar.
radicando el
42
itemgetteracepta más de un argumento: itemgetter(1,2,3)es una función que devuelve una tupla obj[1], obj[2], obj[3], por lo que puede usarla para hacer tipos complejos.
Bakuriu
167
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'))
cedbeu
fuente
99
De todos modos para combinar nombre y edad? (como en SQL ORDER BY nombre, edad?)
monojohnny
28
@monojohnny: Sí, sólo tiene la clave Devuelve una tupla, key=lambda k: (k['name'], k['age']). (o key=itemgetter('name', 'age')) las tuplas cmpcompararán cada elemento por turno. Es sangrientamente brillante.
Claudiu
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
2
@TTT: consulte la documentación de la biblioteca para listamigos.
Kevin
65
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.

(3 años después) Editado para agregar:

El nuevo keyargumento es más eficiente y ordenado. Una mejor respuesta ahora se ve así:

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

pjz
fuente
51

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 hack, ya que se basa en convertir los valores en una representación de cadena única para la comparación, pero funciona como se espera para los números, incluidos los negativos (aunque necesitará formatear su cadena de manera apropiada con rellenos de cero si está usando números)

Dologan
fuente
2
ordenadas utilizando timsort que es estable, puede llamar ordenado varias veces para tener una especie de 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 múltiples argumentos a operator.itemgetter en la respuesta superior. Enlace: stackoverflow.com/questions/5212870/…
Permafacture
15
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 fácil sin hacks: stackoverflow.com/a/29849371/1805397
wouter bolsterlee
30
import operator
a_list_of_dicts.sort(key=operator.itemgetter('name'))

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

efotinis
fuente
27
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']) 
forzagreen
fuente
21

Supongo que has querido decir:

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

Esto se ordenaría así:

sorted(l,cmp=lambda x,y: cmp(x['name'],y['name']))
Bartosz Radaczyński
fuente
19

Puede usar 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 veces más.

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. Intenta esto en su lugar:

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

Usando la transformación 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 información sobre la transformación de Perl Schwartzian

En informática, la transformación de Schwartz es un lenguaje de programación de Perl que se usa para mejorar la eficiencia de ordenar una lista de elementos. Este modismo es apropiado para la ordenación basada en la comparación cuando la ordenación se basa realmente en la ordenación de una determinada propiedad (la clave) de los elementos, donde el cálculo de esa propiedad es una operación intensiva que debe realizarse un número mínimo de veces. La Transformación de Schwartzian es notable porque no utiliza matrices temporales con nombre.

Kiriloff
fuente
99
Python ha apoyado la key=de .sortdesde 2,4, que es el año 2004, no transformar el Schwartzian dentro del código de clasificación, en C; por lo tanto, este método es útil solo en Pythons 2.0-2.3. todos los cuales tienen más de 12 años.
Antti Haapala
12

alguna vez 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}]
Uingtea
fuente
11

Aquí está la solución general alternativa: clasifica los elementos de dict por claves y valores. La ventaja de esto es que no es necesario especificar claves, y aún funcionaría si faltan algunas claves en algunos 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)
vvladymyrov
fuente
10

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 (más de 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
abby sobh
fuente
3
Ejecuté su código y encontré un error en el timeit. El temporizador argumenta para Pandas de método grande: 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)", el método grande Pandas terminó en 7.3 segundos, mucho peor que LC o LC2.
clp2
Tienes toda la razón, fue un gran descuido de mi parte. ¡Ya no lo recomiendo para casos grandes! He editado la respuesta para permitirla simplemente como una posibilidad, el caso de uso aún está en debate.
abby sobh
6

Si no necesita el original listde dictionaries, puede modificarlo en el lugar 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 listpara ser clasificado:

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

Clasificándolo en el lugar:

data_one.sort(key=get_name)

Si necesita el original list, llame a la sorted()función pasándole la función listy la tecla, luego asigne el ordenado devuelto lista una nueva variable:

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

Imprenta 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}]
Srisaila
fuente
6

Digamos que tengo un diccionario Dcon los siguientes elementos. Para ordenar, simplemente use el argumento clave en ordenado para pasar la 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 .

Shank_Transformer
fuente
3

He sido un gran fanático del 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, does not return a new list

Comparación rápida de tiempos ejecutivos

# 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 usec 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 usec por bucle

Bejür
fuente
2

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

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

Del mismo modo, las funciones integradas se ejecutan más rápido que los equivalentes hechos a mano. Por ejemplo, map (operator.add, v1, v2) es más rápido que 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 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 una es un poco más rápida.

swac
fuente
-1

Puedes usar el siguiente código

sorted_dct = sorted(dct_name.items(), key = lambda x : x[1])
Loochie
fuente