¿Ordenar una lista por múltiples atributos?
Tengo una lista de listas:
[[12, 'tall', 'blue', 1],
[2, 'short', 'red', 9],
[4, 'tall', 'blue', 13]]
Si quisiera ordenar por un elemento, digamos el elemento alto/bajo, podría hacerlo a través de s = sorted(s, key = itemgetter(1))
.
Si quisiera ordenar por alto /bajo y por color, podría hacerlo dos veces, una para cada elemento, pero ¿hay alguna manera más rápida?
Una clave puede ser una función que devuelve una tupla:
s = sorted(s, key = lambda x: (x[1], x[2]))
O puedes lograr lo mismo usando itemgetter
(que es más rápido y evita una llamada a una función de Python):
import operator
s = sorted(s, key = operator.itemgetter(1, 2))
Y observe que aquí puede usar sort
en lugar de usar sorted
y luego reasignar:
s.sort(key = operator.itemgetter(1, 2))
No estoy seguro de si este es el método más pitónico... Tenía una lista de tuplas que necesitaban ordenarse primero por valores enteros descendentes y segundo alfabéticamente. Esto requirió invertir la ordenación de números enteros pero no la ordenación alfabética. Aquí estaba mi solución: (sobre la marcha en un examen, por cierto, ni siquiera sabía que se podían "anidar" funciones ordenadas)
a = [('Al', 2),('Bill', 1),('Carol', 2), ('Abel', 3), ('Zeke', 2), ('Chris', 1)]
b = sorted(sorted(a, key = lambda x : x[0]), key = lambda x : x[1], reverse = True)
print(b)
[('Abel', 3), ('Al', 2), ('Carol', 2), ('Zeke', 2), ('Bill', 1), ('Chris', 1)]
Varios años de retraso para la fiesta, pero quiero clasificar según 2 criterios y utilizar reverse=True
. En caso de que alguien más quiera saber cómo, puede incluir sus criterios (funciones) entre paréntesis:
s = sorted(my_list, key=lambda i: ( criteria_1(i), criteria_2(i) ), reverse=True)
Parece que podrías usar a list
en lugar de a tuple
. Creo que esto se vuelve más importante cuando se obtienen atributos en lugar de 'índices mágicos' de una lista/tupla.
En mi caso quería ordenar por múltiples atributos de una clase, donde las claves entrantes eran cadenas. Necesitaba una clasificación diferente en diferentes lugares y quería una clasificación predeterminada común para la clase principal con la que interactuaban los clientes; solo tener que anular las 'claves de clasificación' cuando realmente 'lo necesitaba', pero también de una manera que pudiera almacenarlas como listas que la clase podría compartir
Primero definí un método auxiliar.
def attr_sort(self, attrs=['someAttributeString']:
'''helper to sort by the attributes named by strings of attrs in order'''
return lambda k: [ getattr(k, attr) for attr in attrs ]
entonces para usarlo
# would defined elsewhere but showing here for consiseness
self.SortListA = ['attrA', 'attrB']
self.SortListB = ['attrC', 'attrA']
records = .... #list of my objects to sort
records.sort(key=self.attr_sort(attrs=self.SortListA))
# perhaps later nearby or in another function
more_records = .... #another list
more_records.sort(key=self.attr_sort(attrs=self.SortListB))
Esto utilizará la función lambda generada para ordenar la lista object.attrA
y luego object.attrB
suponiendo que object
tiene un captador correspondiente a los nombres de cadena proporcionados. Y el segundo caso se solucionaría para object.attrC
entonces object.attrA
.
Esto también le permite exponer potencialmente opciones de clasificación externas para que un consumidor las comparta por igual, una prueba unitaria, o que tal vez le digan cómo quieren que se realice la clasificación para alguna operación en su API con solo tener que darle una lista y no acoplándolos a su implementación de back-end.
convierta la lista de listas en una lista de tuplas y luego ordene la tupla por múltiples campos.
data=[[12, 'tall', 'blue', 1],[2, 'short', 'red', 9],[4, 'tall', 'blue', 13]]
data=[tuple(x) for x in data]
result = sorted(data, key = lambda x: (x[1], x[2]))
print(result)
producción:
[(2, 'short', 'red', 9), (12, 'tall', 'blue', 1), (4, 'tall', 'blue', 13)]