Comment trier une liste de dictionnaires par valeur du dictionnaire?

1899

J'ai une liste de dictionnaires et je veux que chaque élément soit trié par une valeur de propriété spécifique.

Tenez compte du tableau ci-dessous,

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

Lorsque trié par name, devrait devenir

[{'name':'Bart', 'age':10}, {'name':'Homer', 'age':39}]
masi
la source
Lire la réponse et regarder operator.itemgetter . Puis-je trier plusieurs valeurs dans le même processus (par exemple, nous avons [{'name':'Bart', 'age':10, 'note':3},{'name':'Homer','age':10,'note':2},{'name':'Vasile','age':20,'note':3}] Et utiliser: from operator import itemgetter newlist = sorted(old_list, key=itemgetter(-'note','name') EDIT: Testé, et cela fonctionne mais je ne sais pas comment noter DESC et nommer ASC.
Claudiu

Réponses:

2471

Cela peut sembler plus propre en utilisant une clé à la place d'un cmp:

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

ou comme JFSebastian et d'autres l'ont suggéré,

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

Pour être complet (comme indiqué dans les commentaires de fitzgeraldsteele), ajoutez reverse=Truepour trier par ordre décroissant

newlist = sorted(l, key=itemgetter('name'), reverse=True)
Mario F
la source
34
L'utilisation de la clé est non seulement plus propre mais aussi plus efficace.
jfs
5
Le moyen le plus rapide serait d'ajouter une instruction newlist.reverse (). Sinon, vous pouvez définir une comparaison comme cmp = lambda x, y: - cmp (x ['nom'], y ['nom']).
Mario F
3
si la valeur de tri est un nombre, vous pouvez dire: lambda k: (k ['age'] * -1) pour obtenir un tri inversé
Philluminati
2
Cela s'applique également à une liste de tuples, si vous utilisez itemgetter(i)iest l'index de l'élément tuple sur lequel effectuer le tri.
radicand
42
itemgetteraccepte plus d'un argument: itemgetter(1,2,3)est une fonction qui retourne un tuple comme obj[1], obj[2], obj[3], vous pouvez donc l'utiliser pour faire des tris complexes.
Bakuriu
167
import operator

Pour trier la liste des dictionnaires par key = 'name':

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

Pour trier la liste des dictionnaires par clé = 'âge':

list_of_dicts.sort(key=operator.itemgetter('age'))
cedbeu
la source
9
Quoi qu'il en soit pour combiner le nom et l'âge? (comme dans SQL ORDER BY nom, âge?)
monojohnny
28
@monojohnny: oui, juste avoir la clé revenir un tuple, key=lambda k: (k['name'], k['age']). (ou key=itemgetter('name', 'age')). les tuples cmpcompareront tour à tour chaque élément. c'est sanglant brillant.
Claudiu
1
Dans la documentation ( docs.python.org/2/tutorial/datastructures.html ) l' keyargument facultatif de list.sort()n'est pas décrit. Une idée où trouver ça?
TTT
2
@TTT: Voir la documentation de la bibliothèque pour listet amis.
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 sera maintenant ce que vous voulez.

(3 ans plus tard) Modifié pour ajouter:

Le nouvel keyargument est plus efficace et plus net. Une meilleure réponse ressemble maintenant à:

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

... le lambda est, OMI, plus facile à comprendre que operator.itemgetter, mais YMMV.

pjz
la source
51

Si vous souhaitez trier la liste par plusieurs clés, vous pouvez procéder comme suit:

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

C'est plutôt hackish, car il repose sur la conversion des valeurs en une seule représentation de chaîne pour la comparaison, mais cela fonctionne comme prévu pour les nombres, y compris les nombres négatifs (bien que vous devrez formater votre chaîne de manière appropriée avec des remplissages zéro si vous utilisez des nombres)

Dologan
la source
2
trié à l'aide de timsort qui est stable, vous pouvez appeler trié plusieurs fois pour avoir un tri sur plusieurs critères
njzk2
Le commentaire de njzk2 n'était pas immédiatement clair pour moi, j'ai donc trouvé ce qui suit. Vous pouvez simplement trier deux fois comme le suggère njzk2, ou passer plusieurs arguments à operator.itemgetter dans la réponse du haut. Lien: stackoverflow.com/questions/5212870/…
Permafacture
15
Pas besoin de convertir en chaîne. Renvoyez simplement un tuple comme clé.
Winston Ewert
Trier plusieurs fois est la solution générique la plus simple sans hacks: stackoverflow.com/a/29849371/1805397
wouter bolsterlee
30
import operator
a_list_of_dicts.sort(key=operator.itemgetter('name'))

'key' est utilisé pour trier par une valeur arbitraire et 'itemgetter' définit cette valeur à l'attribut 'name' de chaque élément.

efotinis
la source
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
la source
21

Je suppose que vous vouliez dire:

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

Ce serait trié comme ceci:

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

Vous pouvez utiliser une fonction de comparaison personnalisée ou passer une fonction qui calcule une clé de tri personnalisée. C'est généralement plus efficace car la clé n'est calculée qu'une fois par article, tandis que la fonction de comparaison serait appelée plusieurs fois.

Vous pouvez le faire de cette façon:

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

Mais la bibliothèque standard contient une routine générique pour obtenir des éléments d'objets arbitraires: itemgetter. Essayez donc ceci à la place:

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

En utilisant la transformation Schwartzian de Perl,

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

faire

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

donne

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

En savoir plus sur la transformation de Perl Schwartzian

En informatique, la transformée de Schwartzian est un idiome de programmation Perl utilisé pour améliorer l'efficacité du tri d'une liste d'éléments. Cet idiome est approprié pour le tri basé sur la comparaison lorsque l'ordre est réellement basé sur l'ordre d'une certaine propriété (la clé) des éléments, où le calcul de cette propriété est une opération intensive qui doit être effectuée un nombre minimal de fois. La transformation Schwartzian est remarquable en ce qu'elle n'utilise pas de tableaux temporaires nommés.

kiriloff
la source
9
Python supporte le key=for .sortdepuis 2.4, c'est-à-dire l'année 2004, il fait la transformation schwartzienne au sein du code de tri, en C; cette méthode n'est donc utile que sur Pythons 2.0-2.3. qui ont tous plus de 12 ans.
Antti Haapala
12

nous devons parfois utiliser lower()par exemple

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
la source
11

Voici la solution générale alternative - elle trie les éléments de dict par clés et valeurs. L'avantage - pas besoin de spécifier de clés, et cela fonctionnerait toujours si certaines clés manquaient dans certains dictionnaires.

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
la source
10

L'utilisation du package pandas est une autre méthode, bien que son exécution à grande échelle soit beaucoup plus lente que les méthodes plus traditionnelles proposées par d'autres:

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

Voici quelques valeurs de référence pour une petite liste et une grande liste (100k +) de dictés:

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
la source
3
J'ai exécuté votre code et trouvé une erreur dans l'argument timeit.Timer pour les pandas de grande méthode: vous spécifiez "setup_small" où il devrait être "setup_large". La modification de cet argument a provoqué l'exécution du programme sans terminer, et je l'ai arrêté après plus de 5 minutes. Lorsque je l'ai exécuté avec "timeit (1)", les pandas à grande méthode ont terminé en 7,3 secondes, bien pire que LC ou LC2.
clp2
Vous avez tout à fait raison, c'était tout à fait une erreur de ma part. Je ne le recommande plus pour les gros cas! J'ai édité la réponse pour la laisser simplement comme une possibilité, le cas d'utilisation est toujours en débat.
abby sobh
6

Si vous n'avez pas besoin de l'original listde dictionaries, vous pouvez le modifier sur place avec la sort()méthode à l'aide d'une fonction de touche personnalisée.

Fonction clef:

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

    return d["name"]

À listtrier:

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

Tri sur place:

data_one.sort(key=get_name)

Si vous avez besoin de l'original list, appelez la sorted()fonction en lui passant la listet la fonction clé, puis affectez le retour trié listà une nouvelle variable:

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

Impression data_oneet 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
la source
6

Disons que j'ai un dictionnaire Davec des éléments ci-dessous. Pour trier, utilisez simplement l'argument clé trié pour passer la fonction personnalisée comme ci-dessous:

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

Regardez ça .

Shank_Transformer
la source
3

J'ai été un grand fan du filtre w / lambda mais ce n'est pas la meilleure option si vous considérez la complexité du temps

Première option

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

Deuxième option

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

Comparaison rapide des temps d'exécution

# 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 boucles, meilleur de 3: 0,736 usec par boucle

# 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 boucles, meilleur de 3: 0,438 usec par boucle

Bejür
la source
2

Si les performances sont un problème, je les utiliserais operator.itemgetterplutôt lambdaque car les fonctions intégrées fonctionnent plus rapidement que les fonctions artisanales. La itemgetterfonction semble fonctionner environ 20% plus rapidement que lambdasur la base de mes tests.

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

De même, les fonctions intégrées s'exécutent plus rapidement que les équivalents fabriqués à la main. Par exemple, map (operator.add, v1, v2) est plus rapide que map (lambda x, y: x + y, v1, v2).

Voici une comparaison de tri en utilisant la vitesse 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

Les deux techniques trient la liste dans le même ordre (vérifiée par l'exécution de l'instruction finale dans le bloc de code) mais l'une est un peu plus rapide.

swac
la source
-1

Vous pouvez utiliser le code suivant

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