2010-11-20 18 views
232

que tienen una lista de listas:Ordenar una lista por atributos múltiples?

[[12, 'tall', 'blue', 1], 
[2, 'short', 'red', 9], 
[4, 'tall', 'blue', 13]] 

Si quería para ordenar por un elemento, por ejemplo el elemento de alto/bajo, podría hacerlo a través de s = sorted(s, key = itemgetter(1)).

Si quisiera ordenar por ambos alto/corto y color, podría hacer el tipo dos veces, una para cada elemento, pero ¿hay una manera más rápida?

+0

[pregunta relacionada PPCG] (http://codegolf.stackexchange.com/q/85321/34718) – mbomb007

Respuesta

406

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

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

O se puede lograr el mismo usando itemgetter:

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

Y cuenta de que aquí se puede utilizar sort en lugar de utilizar sorted y luego reasignar:

s.sort(key = operator.itemgetter(1, 2)) 
+2

Se aprende algo nuevo cada día ! ¿Sabes si esto es computacionalmente más rápido que el método anterior? ¿O simplemente hace lo mismo en el fondo? – headache

+2

@headache: No sé cuál es más rápido, sospecho que son más o menos lo mismo. Puede usar el módulo 'timeit' para medir el rendimiento de ambos si está interesado. –

+14

Para completar desde el momento: primero di 6 us por loop y el segundo 4.4 us por loop –

18

Estoy no estoy seguro de si este es el método más pitónico ... Tenía una lista de tuplas que necesitaba ordenar primero por valores enteros descendentes y 2º alfabéticamente. Esto requirió revertir el género entero pero no el orden alfabético. Aquí estaba mi solución: (sobre la marcha en un examen por cierto, ni siquiera estaba consciente de que podría 'nido', enumerados funciones)

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)] 
+4

ya que segundo es un número, funciona para hacerlo como 'b = ordenado (a, clave) = lambda x: (-x [1], x [0])) 'que es más visible en qué criterios se aplica primero. en cuanto a la eficiencia, no estoy seguro, alguien necesita medir el tiempo. –

+0

@AndreiPetre tu respuesta tiene mucho sentido para mí. Es tan fácil de recordar y codificar. Gracias. – Sriram

1

Parece que podría utilizar un list en lugar de un tuple. Esto se vuelve más importante, creo que cuando está tomando 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 un tipo común predeterminado para la clase principal con la que los clientes interactuaban; Sólo tener que reemplazar las 'claves de clasificación' cuando realmente 'necesitaba', sino también de una manera que podría guardarlas como listas que la clase podría compartir

Así que primero he definido 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 ] 

después utilizarla

# 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 Ordenar la lista por object.attrA y luego object.attrB asumiendo object tiene un captador correspondiente a los nombres de cadena proporcionadas. Y el segundo caso ordenaría por object.attrC y luego object.attrA.

Esto también le permite exponer las opciones de clasificación externa para que sean idénticas a las de un consumidor, una prueba de unidad, o para que le digan cómo quieren que se haga la ordenación para alguna operación en su API, solo debe proporcionarle una lista y no unirlas a su implementación de back-end.

0

Aquí hay una forma: Básicamente vuelve a escribir su función de ordenamiento 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, mira y ve si la función cmp devuelve un el retorno no nulo en caso afirmativo se rompe y envía el valor de retorno. Lo llamas llamando a un Lambda de una función de una lista de Lambdas.

Su ventaja es que pasa de una sola vez a través de los datos no es una especie de clasificación previa como lo hacen otros métodos. Otra cosa es que se ordena en su lugar, mientras que el ordenado parece hacer una copia.

Lo usé para escribir una función de rango, que clasifica una lista de clases donde cada objeto está en un grupo y tiene una función de puntaje, pero puede agregar cualquier lista de atributos. Tenga en cuenta el uso no lambda, aunque hackoso, de una lambda para llamar a un colocador. La parte de rango no funcionará para una matriz de listas, pero el género 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í está una manera 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 
Cuestiones relacionadas