Ordenar una lista de Python por dos campos

173

Tengo la siguiente lista creada a partir de un csv ordenado

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

En realidad, me gustaría ordenar la lista por dos criterios: primero por el valor en el campo 1 y luego por el valor en el campo 2. ¿Cómo hago esto?

medio lleno
fuente
3
¿Posible duplicado de Ordenar una lista por múltiples atributos?
Chris_Rands
¿Dejamos esta pregunta en pie y restringimos su alcance a "list-of-lists-of-length-two-builtin-types (ej. String / int / float)" . ¿O también permitimos la "lista de objetos definidos por el usuario" , como el título sugiere que también está permitido, en cuyo caso la respuesta es "Defina el __lt__()método en su clase o herede de alguna clase que lo haga" ? Eso lo haría mucho mejor canónico.
smci

Respuestas:

158

Me gusta esto:

import operator
list1 = sorted(csv1, key=operator.itemgetter(1, 2))
mouad
fuente
1
+1: Más elegante que el mío. Olvidé que itemgetter puede tomar múltiples índices.
Dappawit
77
operatores un módulo que necesita ser importado.
trapicki
3
¿Cómo procederé si quiero ordenar ascendente en un elemento y descender en otro, usando itemgetter ??.
Ashish
3
@ashish, mira mi respuesta a continuación con las funciones lambda, esto está claro, ordena por "-x [1]" o incluso "x [0] + x [1]" si lo deseas
jaap
¿Qué pasa si un criterio en modo inverso?
YaserKH
328

No es necesario importar nada al usar funciones lambda.
Lo siguiente ordena listpor el primer elemento, luego por el segundo elemento.

sorted(list, key=lambda x: (x[0], -x[1]))
jaap
fuente
12
Agradable. Como notó en el comentario a la respuesta principal anterior, esta es la mejor (¿solo?) Forma de hacer múltiples clasificaciones con diferentes órdenes de clasificación. Quizás resalte eso. Además, su texto no indica que ordenó descender en el segundo elemento.
PeterVermont
2
@ user1700890 Estaba asumiendo que el campo ya era una cadena. Debería ordenar las cadenas en orden alfabético por defecto. Debe publicar su propia pregunta por separado en SO si no está específicamente relacionada con la respuesta aquí o con la pregunta original del OP.
pbible
55
¿Qué significa el -en -x[1]?
ene
77
@jan es el tipo inverso
jaap
3
No funcionará en un caso específico. La solución aceptada tampoco funcionará. Por ejemplo, las columnas que se utilizarán como claves son todas cadenas que no se pueden convertir a números. En segundo lugar, uno quiere ordenar en orden ascendente por una columna y en orden descendente por otra columna.
coder.in.me
20

Python tiene una clasificación estable, por lo que siempre que el rendimiento no sea un problema, la forma más simple es ordenarlo por el campo 2 y luego ordenarlo nuevamente por el campo 1.

Eso le dará el resultado que desea, el único inconveniente es que si se trata de una lista grande (o si desea ordenarla con frecuencia) llamar a ordenar dos veces podría ser una sobrecarga inaceptable.

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

Hacerlo de esta manera también facilita el manejo de la situación en la que desea que algunas de las columnas se ordenen de forma inversa, solo incluya el parámetro 'reverse = True' cuando sea necesario.

De lo contrario, puede pasar múltiples parámetros a itemgetter o construir manualmente una tupla. Probablemente sea más rápido, pero tiene el problema de que no se generaliza bien si algunas de las columnas quieren ser ordenadas en reversa (las columnas numéricas aún pueden revertirse negándolas pero eso impide que la ordenación sea estable).

Entonces, si no necesita ninguna columna ordenada inversamente, busque múltiples argumentos para agregar elementos, si lo desea, y las columnas no son numéricas o si desea mantener la clasificación estable, vaya a varias clasificaciones consecutivas.

Editar: Para los comentaristas que tienen problemas para entender cómo esto responde a la pregunta original, aquí hay un ejemplo que muestra exactamente cómo la naturaleza estable de la clasificación garantiza que podamos hacer clasificaciones separadas en cada clave y terminar con datos ordenados en múltiples criterios:

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

Este es un ejemplo ejecutable, pero para salvar a las personas que lo ejecutan, el resultado es:

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

Observe en particular cómo en el segundo paso el reverse=Trueparámetro mantiene los nombres en orden, mientras que simplemente ordenar y revertir la lista perdería el orden deseado para la tercera clave de clasificación.

Duncan
fuente
1
La clasificación estable no significa que no olvidará cuál fue su clasificación anterior. Esta respuesta es incorrecta.
Mike Axiak
77
La ordenación estable significa que puede ordenar por columnas a, b, c simplemente ordenando por columna c, luego b y luego a. A menos que quiera ampliar su comentario, creo que es usted quien está equivocado.
Duncan
77
Esta respuesta es definitivamente correcta, aunque para listas más grandes es irreal: si la lista ya estaba parcialmente ordenada, perderá la mayor parte de la optimización de la clasificación de Python al mezclar la lista mucho más. @ Mike, eres incorrecto; Sugiero probar las respuestas antes de declararlas incorrectas.
Glenn Maynard
66
@MikeAxiak: docs.python.org/2/library/stdtypes.html#index-29 declara en el comentario 9: Comenzando con Python 2.3, el método sort () se garantiza que sea estable. Una ordenación es estable si garantiza no cambiar el orden relativo de los elementos que se comparan entre sí; esto es útil para ordenar en varios pases (por ejemplo, ordenar por departamento, luego por grado salarial).
trapicki
Esto no es correcto porque no responde la pregunta que hizo. quiere una lista ordenada por el primer índice y, en el caso de que haya vínculos en el primer índice, quiere usar el segundo índice como criterio de clasificación. Una ordenación estable solo garantiza que todas las cosas sean iguales, el orden original aprobado será el orden en que aparecen los elementos.
Jon
14
list1 = sorted(csv1, key=lambda x: (x[1], x[2]) )
dappawit
fuente
44
No creo que tuple()pueda recibir dos argumentos (o más bien, tres, si cuenta self)
Filipe Correia
3
tuple toma solo puede tomar un argumento
therealprashant
1
returndeclaración debe ser return tuple((x[1], x[2]))o simplemente return x[1], x[2]. Consulte @jaap respuesta a continuación si está buscando ordenar en diferentes direcciones
Jo Kachikaran
... o tuple(x[1:3]), si desea utilizar el constructor de tuplas por algún motivo en lugar de solo una lista de visualización de tuplas x[1], x[2]. O keyfunc = operator.itemgetter(1, 2)y ni siquiera escriba una función usted mismo.
abarnert
3
employees.sort(key = lambda x:x[1])
employees.sort(key = lambda x:x[0])

También podemos usar .sort con lambda 2 veces porque la ordenación de Python está en su lugar y es estable. Esto ordenará primero la lista según el segundo elemento, x [1]. Luego, ordenará el primer elemento, x [0] (máxima prioridad).

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

Esto es equivalente a hacer lo siguiente: employee.sort (key = lambda x: (x [0], x [1]))

Deepak Yadav
fuente
1
no, esta regla de clasificación debe tener prioridad y luego ser la segunda.
CodeFarmer
1

En orden ascendente puede usar:

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

o en orden descendente puedes usar:

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

La lista de clasificación de los dictados utilizando a continuación ordenará la lista en orden descendente en la primera columna como salario y la segunda columna como edad

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

Resultado: [{'salario': 123, 'edad': 25}, {'salario': 123, 'edad': 23}]

Saurabh
fuente