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 clasificar por un elemento, diga el elemento alto / corto, podría hacerlo a través de s = sorted(s, key = itemgetter(1)) .

Si quisiera clasificar por alto / bajo y por color, podría hacerlo dos veces, una para cada elemento, pero ¿hay una manera más rápida?

Una tecla puede ser una función que devuelve una tupla:

 s = sorted(s, key = lambda x: (x[1], x[2])) 

O puede lograr lo mismo usando itemgetter (que es más rápido y evita una llamada a la función Python):

 import operator s = sorted(s, key = operator.itemgetter(1, 2)) 

Y observe que aquí puede usar sort 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 pythonic … Tenía una lista de tuplas que necesitaban ser clasificadas primero por valores enteros descendentes y segundo alfabéticamente. Esto requirió revertir la ordenación de 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 podría “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)] 

Parece que podrías usar una list lugar de una tuple . Creo que esto se vuelve más importante cuando se están agarrando atributos en lugar de ‘índices mágicos’ de una lista / tupla.

En mi caso, quería clasificar 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 tuve que anular las ‘claves de clasificación’ cuando realmente ‘necesitaba’, pero también de una manera que pudiera almacenarlas como listas que la clase podría compartir

Así que primero definí un método de ayuda.

 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 ] 

luego 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 usará la función lambda generada, ordenar la lista por object.attrA y luego object.attrB asumiendo que el object tiene un captador que corresponde a los nombres de cadena proporcionados. Y el segundo caso se object.attrC por object.attrC luego object.attrA .

Esto también le permite exponer potencialmente las opciones de clasificación externa para que sean compartidas por un consumidor, una prueba de unidad, o para que le digan cómo quieren que se realice la clasificación para alguna operación en su api, solo tienen que darle una lista y no Acoplándolos a su implementación de back-end.

Aquí hay una forma: básicamente, vuelve a escribir su función de clasificación para tomar una lista de funciones de clasificación, cada función de clasificación compara los atributos que desea probar, en cada prueba de clasificación, observa y ve si la función cmp devuelve un retorno distinto de cero Si es así rompa y envíe el valor de retorno. Lo llamas llamando a un Lambda de una función de una lista de Lambdas.

Su ventaja es que hace un solo paso a través de los datos, no un tipo de clasificación anterior como lo hacen otros métodos. Otra cosa es que ordena en su lugar, mientras que ordenado parece hacer una copia.

Lo usé para escribir una función de clasificación, que clasifica una lista de clases donde cada objeto está en un grupo y tiene una función de puntuación, pero puede agregar cualquier lista de atributos. Tenga en cuenta que el uso de un lambda para llamar a un setter es similar a un-lambda, aunque no es correcto. La parte de rango no funcionará para un conjunto de listas, pero la clasificación sí lo hará.

 #First, here's a pure list version my_sortLambdaLst = [lambda x,y:cmp(x[0], y[0]), lambda x,y:cmp(x[1], y[1])] def multi_attribute_sort(x,y): r = 0 for l in my_sortLambdaLst: r = l(x,y) if r!=0: return r #keep looping till you see a difference return r Lst = [(4, 2.0), (4, 0.01), (4, 0.9), (4, 0.999),(4, 0.2), (1, 2.0), (1, 0.01), (1, 0.9), (1, 0.999), (1, 0.2) ] Lst.sort(lambda x,y:multi_attribute_sort(x,y)) #The Lambda of the Lambda for rec in Lst: print str(rec) 

Aquí hay una forma de clasificar una lista de objetos.

 class probe: def __init__(self, group, score): self.group = group self.score = score self.rank =-1 def set_rank(self, r): self.rank = r def __str__(self): return '\t'.join([str(self.group), str(self.score), str(self.rank)]) def RankLst(inLst, group_lambda= lambda x:x.group, sortLambdaLst = [lambda x,y:cmp(x.group, y.group), lambda x,y:cmp(x.score, y.score)], SetRank_Lambda = lambda x, rank:x.set_rank(rank)): #Inner function is the only way (I could think of) to pass the sortLambdaLst into a sort function def multi_attribute_sort(x,y): r = 0 for l in sortLambdaLst: r = l(x,y) if r!=0: return r #keep looping till you see a difference return r inLst.sort(lambda x,y:multi_attribute_sort(x,y)) #Now Rank your probes rank = 0 last_group = group_lambda(inLst[0]) for i in range(len(inLst)): rec = inLst[i] group = group_lambda(rec) if last_group == group: rank+=1 else: rank=1 last_group = group SetRank_Lambda(inLst[i], rank) #This is pure evil!! The lambda purists are gnashing their teeth Lst = [probe(4, 2.0), probe(4, 0.01), probe(4, 0.9), probe(4, 0.999), probe(4, 0.2), probe(1, 2.0), probe(1, 0.01), probe(1, 0.9), probe(1, 0.999), probe(1, 0.2) ] RankLst(Lst, group_lambda= lambda x:x.group, sortLambdaLst = [lambda x,y:cmp(x.group, y.group), lambda x,y:cmp(x.score, y.score)], SetRank_Lambda = lambda x, rank:x.set_rank(rank)) print '\t'.join(['group', 'score', 'rank']) for r in Lst: print r