Trier une liste Python par deux champs

173

J'ai la liste suivante créée à partir d'un csv trié

list1 = sorted(csv1, key=operator.itemgetter(1))

Je voudrais en fait trier la liste selon deux critères: d'abord par la valeur dans le champ 1 et ensuite par la valeur dans le champ 2. Comment faire?

à moitié plein
la source
3
Possible duplication de Trier une liste par plusieurs attributs?
Chris_Rands
Laissons-nous cette question se poser et restreignons-nous sa portée à "liste-de-listes-de-longueur-deux-types-intégrés (par exemple string / int / float)" . Ou autorisons-nous également la "liste-d'objets-définis par l'utilisateur" , comme le titre l'indique, est également autorisée, auquel cas la réponse est "Définir une __lt__()méthode sur votre classe ou hériter d'une classe qui le fait" ? Cela en ferait un bien meilleur canonique.
smci

Réponses:

158

comme ça:

import operator
list1 = sorted(csv1, key=operator.itemgetter(1, 2))
mouad
la source
1
+1: Plus élégant que le mien. J'ai oublié que itemgetter peut prendre plusieurs indices.
dappawit
7
operatorest un module qui doit être importé.
trapicki
3
comment vais-je procéder si je veux trier par ordre croissant sur un élément et descendant sur un autre, en utilisant itemgetter ??.
ashish
3
@ashish, voir ma réponse ci-dessous avec les fonctions lambda c'est clair, triez par "-x [1]" ou même "x [0] + x [1]" si vous le souhaitez
jaap
et si un critère en mode inversé?
YaserKH
328

Pas besoin d'importer quoi que ce soit lors de l'utilisation des fonctions lambda.
Les éléments suivants sont triés listpar le premier élément, puis par le deuxième élément.

sorted(list, key=lambda x: (x[0], -x[1]))
jaap
la source
12
Agréable. Comme vous l'avez noté dans le commentaire de la réponse principale ci-dessus, c'est la meilleure (seule?) Façon de faire plusieurs tris avec différents ordres de tri. Soulignez peut-être cela. De plus, votre texte n'indique pas que vous avez trié par ordre décroissant sur le deuxième élément.
PeterVermont
2
@ user1700890 Je supposais que le champ était déjà une chaîne. Il doit trier les chaînes par ordre alphabétique par défaut. Vous devez publier votre propre question séparément sur SO si elle n'est pas spécifiquement liée à la réponse ici ou à la question initiale du PO.
disponible
5
que signifie le -in -x[1]?
janvier
7
@jan it's reverse sort
jaap
3
Ne fonctionnera pas dans un cas spécifique. La solution acceptée ne fonctionnera pas non plus. Par exemple, les colonnes à utiliser comme clés sont toutes des chaînes qui ne peuvent pas être converties en nombres. Deuxièmement, on veut trier par ordre croissant d'une colonne et par ordre décroissant d'une autre colonne.
coder.in.me
20

Python a un tri stable, donc à condition que les performances ne soient pas un problème, le moyen le plus simple est de le trier par champ 2, puis de le trier à nouveau par champ 1.

Cela vous donnera le résultat que vous voulez, le seul hic, c'est que s'il s'agit d'une grande liste (ou si vous voulez la trier souvent), appeler tri deux fois peut être une surcharge inacceptable.

list1 = sorted(csv1, key=operator.itemgetter(2))
list1 = sorted(list1, key=operator.itemgetter(1))

En procédant de cette manière, il est également facile de gérer la situation dans laquelle vous souhaitez que certaines des colonnes soient triées de manière inversée, incluez simplement le paramètre 'reverse = True' si nécessaire.

Sinon, vous pouvez transmettre plusieurs paramètres à itemgetter ou créer manuellement un tuple. Cela va probablement être plus rapide, mais a le problème que cela ne se généralise pas bien si certaines des colonnes veulent être triées par inversion (les colonnes numériques peuvent toujours être inversées en les annulant, mais cela empêche le tri d'être stable).

Donc, si vous n'avez pas besoin de colonnes triées inversement, optez pour plusieurs arguments dans itemgetter, si vous le pouvez, et les colonnes ne sont pas numériques ou si vous voulez garder le tri stable pour plusieurs tris consécutifs.

Edit: Pour les commentateurs qui ont du mal à comprendre comment cela répond à la question d'origine, voici un exemple qui montre exactement comment la nature stable du tri garantit que nous pouvons faire des tris séparés sur chaque clé et finir avec des données triées sur plusieurs critères:

DATA = [
    ('Jones', 'Jane', 58),
    ('Smith', 'Anne', 30),
    ('Jones', 'Fred', 30),
    ('Smith', 'John', 60),
    ('Smith', 'Fred', 30),
    ('Jones', 'Anne', 30),
    ('Smith', 'Jane', 58),
    ('Smith', 'Twin2', 3),
    ('Jones', 'John', 60),
    ('Smith', 'Twin1', 3),
    ('Jones', 'Twin1', 3),
    ('Jones', 'Twin2', 3)
]

# Sort by Surname, Age DESCENDING, Firstname
print("Initial data in random order")
for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

print('''
First we sort by first name, after this pass all
Twin1 come before Twin2 and Anne comes before Fred''')
DATA.sort(key=lambda row: row[1])

for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

print('''
Second pass: sort by age in descending order.
Note that after this pass rows are sorted by age but
Twin1/Twin2 and Anne/Fred pairs are still in correct
firstname order.''')
DATA.sort(key=lambda row: row[2], reverse=True)
for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

print('''
Final pass sorts the Jones from the Smiths.
Within each family members are sorted by age but equal
age members are sorted by first name.
''')
DATA.sort(key=lambda row: row[0])
for d in DATA:
    print("{:10s} {:10s} {}".format(*d))

Ceci est un exemple exécutable, mais pour sauver les personnes qui l'exécutent, le résultat est:

Initial data in random order
Jones      Jane       58
Smith      Anne       30
Jones      Fred       30
Smith      John       60
Smith      Fred       30
Jones      Anne       30
Smith      Jane       58
Smith      Twin2      3
Jones      John       60
Smith      Twin1      3
Jones      Twin1      3
Jones      Twin2      3

First we sort by first name, after this pass all
Twin1 come before Twin2 and Anne comes before Fred
Smith      Anne       30
Jones      Anne       30
Jones      Fred       30
Smith      Fred       30
Jones      Jane       58
Smith      Jane       58
Smith      John       60
Jones      John       60
Smith      Twin1      3
Jones      Twin1      3
Smith      Twin2      3
Jones      Twin2      3

Second pass: sort by age in descending order.
Note that after this pass rows are sorted by age but
Twin1/Twin2 and Anne/Fred pairs are still in correct
firstname order.
Smith      John       60
Jones      John       60
Jones      Jane       58
Smith      Jane       58
Smith      Anne       30
Jones      Anne       30
Jones      Fred       30
Smith      Fred       30
Smith      Twin1      3
Jones      Twin1      3
Smith      Twin2      3
Jones      Twin2      3

Final pass sorts the Jones from the Smiths.
Within each family members are sorted by age but equal
age members are sorted by first name.

Jones      John       60
Jones      Jane       58
Jones      Anne       30
Jones      Fred       30
Jones      Twin1      3
Jones      Twin2      3
Smith      John       60
Smith      Jane       58
Smith      Anne       30
Smith      Fred       30
Smith      Twin1      3
Smith      Twin2      3

Notez en particulier comment dans la deuxième étape le reverse=Trueparamètre maintient les prénoms dans l'ordre alors que le simple tri puis l'inversion de la liste perdraient l'ordre souhaité pour la troisième clé de tri.

Duncan
la source
1
Un tri stable ne signifie pas qu'il n'oubliera pas votre tri précédent. Cette réponse est fausse.
Mike Axiak
7
Le tri stable signifie que vous pouvez trier par colonnes a, b, c simplement en triant par colonne c puis b puis a. À moins que vous ne souhaitiez développer votre commentaire, je pense que c'est vous qui vous trompez.
Duncan
7
Cette réponse est tout à fait correcte, même si pour les listes plus volumineuses, elle n'est pas idéale: si la liste était déjà partiellement triée, vous perdrez l'essentiel de l'optimisation du tri de Python en mélangeant beaucoup plus la liste. @Mike, vous avez tort; Je suggère de tester les réponses avant de les déclarer fausses.
Glenn Maynard
6
@MikeAxiak: docs.python.org/2/library/stdtypes.html#index-29 déclare dans le commentaire 9: À partir de Python 2.3, la méthode sort () est garantie d'être stable. Un tri est stable s'il garantit de ne pas changer l'ordre relatif des éléments qui se comparent égaux - cela est utile pour le tri en plusieurs passes (par exemple, trier par département, puis par classe de salaire).
trapicki
Ce n'est pas correct car cela ne répond pas à la question qu'il a posée. il veut une liste triée par le premier index et dans le cas où il y a des liens dans le premier index, il veut utiliser le deuxième index comme critère de tri. Un tri stable garantit seulement que toutes choses étant égales par ailleurs, l'ordre d'origine passé sera l'ordre d'apparition des articles.
Jon
14
list1 = sorted(csv1, key=lambda x: (x[1], x[2]) )
dappawit
la source
4
Je ne pense pas tuple()pouvoir recevoir deux arguments (ou plutôt trois, si vous comptez avec self)
Filipe Correia
3
tuple prend seulement un argument
therealprashant
1
returndéclaration devrait être return tuple((x[1], x[2]))ou simplement return x[1], x[2]. Référez- vous à la réponse @jaap ci-dessous si vous recherchez un tri dans différentes directions
Jo Kachikaran
… Ou tuple(x[1:3]), si vous souhaitez utiliser le constructeur de tuple pour une raison quelconque au lieu d'une simple liste d'affichage de tuple x[1], x[2]. Ou keyfunc = operator.itemgetter(1, 2)et n'écrivez même pas une fonction vous-même.
abarnert le
3
employees.sort(key = lambda x:x[1])
employees.sort(key = lambda x:x[0])

Nous pouvons également utiliser .sort avec lambda 2 fois car le tri python est en place et stable. Cela triera d'abord la liste en fonction du deuxième élément, x [1]. Ensuite, il triera le premier élément, x [0] (priorité la plus élevée).

employees[0] = Employee's Name
employees[1] = Employee's Salary

Cela équivaut à faire ce qui suit: employés.sort (clé = lambda x: (x [0], x [1]))

Deepak Yadav
la source
1
non, cette règle de tri doit avoir priorité sur la seconde.
CodeFarmer
1

Par ordre croissant, vous pouvez utiliser:

sorted_data= sorted(non_sorted_data, key=lambda k: (k[1],k[0]))

ou dans l'ordre décroissant, vous pouvez utiliser:

sorted_data= sorted(non_sorted_data, key=lambda k: (k[1],k[0]),reverse=True)
Majid Arasteh
la source
0

Le tri de la liste des dictionnaires ci-dessous triera la liste par ordre décroissant sur la première colonne comme salaire et la deuxième colonne comme âge

d=[{'salary':123,'age':23},{'salary':123,'age':25}]
d=sorted(d, key=lambda i: (i['salary'], i['age']),reverse=True)

Résultat: [{'salaire': 123, 'âge': 25}, {'salaire': 123, 'âge': 23}]

Saurabh
la source