2010-11-20 11 views
232

Ho una lista di liste:Ordina una lista per più attributi?

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

Se avessi voluto ordinare in base a un elemento, ad esempio l'elemento di alto/basso, ho potuto farlo tramite s = sorted(s, key = itemgetter(1)).

Se volevo ordinare per sia alto/basso e colore, potrei fare l'ordinamento due volte, una volta per ogni elemento, ma c'è un modo più veloce?

+0

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

risposta

406

Una chiave può essere una funzione che restituisce una tupla:

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

Oppure si può ottenere lo stesso utilizzando itemgetter:

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

e notare che qui si può utilizzare sort invece di utilizzare sorted e quindi riassegnare:

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

si impara qualcosa di nuovo ogni giorno ! Sai se questo è computazionalmente più veloce del metodo precedente? O fa semplicemente la stessa cosa in background? – headache

+2

@headache: non so quale sia più veloce, ho il sospetto che siano quasi uguali. Puoi usare il modulo 'timeit' per misurare le prestazioni di entrambi, se sei interessato. –

+14

Per completezza da timeit: per me prima ha dato 6 us per loop e il secondo 4.4 us per loop –

18

I'm non sono sicuro se questo è il metodo più pignolo ... Ho avuto una lista di tuple che avevano bisogno di ordinare la prima decrescente di valori interi e la seconda in ordine alfabetico. Ciò ha richiesto di invertire l'ordinamento intero ma non l'ordinamento alfabetico. Qui era la mia soluzione: (al volo in un esame btw, non ero nemmeno a conoscenza si potrebbe 'nido' funzioni ordinate)

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

poiché 2nd è un numero, funziona per farlo come 'b = ordinato (a, tasto = lambda x: (-x [1], x [0])) 'che è più visibile su quale criterio si applica per primo. per quanto riguarda l'efficienza non sono sicuro, qualcuno ha bisogno di timeit. –

+0

@AndreiPetre la tua risposta è stata molto sensata per me. È così facile da ricordare e codice. Grazie. – Sriram

1

Sembra si potrebbe usare un list invece di un tuple. Questo diventa più importante quando penso che prendo gli attributi invece di "indici magici" di una lista/tupla.

Nel mio caso volevo ordinare per più attributi di una classe, dove le chiavi in ​​entrata erano stringhe. Avevo bisogno di diversi ordinamenti in posti diversi e volevo un ordinamento predefinito comune per la classe genitore con cui i client interagivano; solo di dover sostituire le 'chiavi di ordinamento' quando ho davvero 'stato necessario per', ma anche in un modo che io potessi conservare gli elenchi che la classe potesse condividere

Quindi, prima ho definito un metodo di supporto

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 ] 

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

Questa utilizzerà il lambda generato funzione ordina l'elenco per object.attrA quindi object.attrB assumendo object ha un getter corrispondente ai nomi di stringa fornite. E il secondo caso sarebbe ordinare per object.attrC quindi object.attrA.

Ciò consente anche di esporre potenzialmente le scelte di ordinamento verso l'esterno per essere suddiviso in parti uguali da un consumatore, un test unitario o per indicare forse come desiderano effettuare l'ordinamento per qualche operazione nella propria API. una lista e non li accoppia alla tua implementazione back-end.

0

Ecco un modo: in pratica si riscrive la funzione di ordinamento per fare un elenco di funzioni di ordinamento, ogni funzione di ordinamento confronta gli attributi che si desidera testare, su ciascun test di ordinamento, si guarda e si vede se la funzione cmp restituisce un return diverso da zero se si interrompe e invia il valore restituito. Si chiama chiamando un Lambda di una funzione di un elenco di Lambdas.

Il vantaggio è che esegue il passaggio singolo dei dati non una specie di tipo precedente come fanno altri metodi. Un'altra cosa è che ordina in posizione, mentre ordinata sembra fare una copia.

L'ho usato per scrivere una funzione di classificazione, che classifica un elenco di classi in cui ogni oggetto si trova in un gruppo e ha una funzione di punteggio, ma è possibile aggiungere qualsiasi elenco di attributi. Si noti l'uso non-lambda, anche se hackish di un lambda per chiamare un setter. La parte del rango non funzionerà per un array di liste, ma l'ordinamento sarà.

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

Ecco un modo per classificare un elenco di oggetti

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