Ordinare un elenco per più attributi?


457

Ho un elenco di elenchi:

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

Se volessi ordinare per un elemento, ad esempio l'elemento alto / corto, potrei farlo tramite s = sorted(s, key = itemgetter(1)).

Se volessi ordinare sia per altezza / lunghezza che per colore, potrei fare l'ordinamento due volte, una volta per ogni elemento, ma esiste un modo più rapido?



8
Se usi le tuple invece degli elenchi, quando esegui Python ordina gli ordinamenti per voci da sinistra a destra sort. Cioè, sorted([(4, 2), (0, 3), (0, 1)]) == [(0, 1), (0, 3), (4, 2)].
Mateen Ulhaq,

Risposte:


772

Un tasto può essere una funzione che restituisce una tupla:

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

Oppure puoi ottenere lo stesso usando itemgetter(che è più veloce ed evita una chiamata alla funzione Python):

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

E nota che qui puoi usare sortinvece di usare sortede quindi riassegnare:

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

20
Per completezza da timeit: per me prima ho dato 6 us per loop e il secondo 4.4 us per loop
Brian Larsen

10
C'è un modo per ordinare il primo in ordine crescente e il secondo in ordine decrescente? (Supponiamo che entrambi gli attributi siano stringhe, quindi nessun hack come l'aggiunta -di numeri interi)
Martin Thoma

73
che ne dite se voglio fare domanda revrse=Truesolo per x[1]è possibile?
Amyth,

28
@moose, @Amyth, per invertire solo un attributo, è possibile ordinare due volte: prima per secondario, s = sorted(s, key = operator.itemgetter(2))poi per primario s = sorted(s, key = operator.itemgetter(1), reverse=True)Non ideale, ma funziona.
Tomcounsell,

52
@Amyth o un'altra opzione, se la chiave è il numero, per renderlo inverso, puoi moltiplicarlo per -1.
Serge

37

Non sono sicuro che questo sia il metodo più pitonico ... Avevo un elenco di tuple che avevano bisogno di ordinare prima in ordine decrescente di valori interi e in secondo in ordine alfabetico. Ciò ha richiesto l'inversione dell'ordinamento intero ma non dell'ordinamento alfabetico. Ecco la mia soluzione: (al volo in un esame tra l'altro, non ero nemmeno consapevole che potevi "annidare" le 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)]

13
poiché 2nd è un numero, funziona per farlo come se fosse b = sorted(a, key = lambda x: (-x[1], x[0]))più visibile su quali criteri si applicano per primi. per quanto riguarda l'efficienza non sono sicuro, qualcuno ha bisogno di tempo.
Andrei-Niculae Petre,

5

Diversi anni di ritardo alla festa, ma voglio sia ordinare su 2 criteri che utilizzare reverse=True. Se qualcun altro vuole sapere come, puoi racchiudere i tuoi criteri (funzioni) tra parentesi:

s = sorted(my_list, key=lambda i: ( criteria_1(i), criteria_2(i) ), reverse=True)

5

Sembra che tu possa usare a listinvece di a tuple. Questo diventa più importante, penso, quando prendi gli attributi invece di "indici magici" di un elenco / tupla.

Nel mio caso, volevo ordinare in base a più attributi di una classe, in cui le chiavi in ​​arrivo erano stringhe. Avevo bisogno di un ordinamento diverso in luoghi diversi e volevo un ordinamento predefinito comune per la classe genitore con cui i clienti interagivano; dovendo sovrascrivere le 'chiavi di ordinamento' solo quando "avevo davvero bisogno", ma anche in modo da poterle archiviare come elenchi che la classe poteva condividere

Quindi per prima cosa 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 ]

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

Questo utilizzerà la funzione lambda generata per ordinare l'elenco in base a object.attrAe quindi object.attrBsupponendo che objectabbia un getter corrispondente ai nomi di stringa forniti. E il secondo caso sarebbe in ordine per object.attrCallora object.attrA.

Questo ti consente anche di esporre potenzialmente le scelte di ordinamento verso l'esterno per essere condivise allo stesso modo da un consumatore, un test unitario o per loro forse per dirti come vogliono fare l'ordinamento per alcune operazioni nella tua api solo per darti un elenco e non associandoli all'implementazione del back-end.


Bel lavoro. Cosa succede se gli attributi devono essere ordinati in diversi ordini? Supponiamo che attrA sia ordinato in ordine crescente e attrB in ordine decrescente? Esiste una soluzione rapida? Grazie!
mhn_namak,

1

Ecco un modo: fondamentalmente riscrivi la tua funzione di ordinamento per prendere un elenco di funzioni di ordinamento, ogni funzione di ordinamento confronta gli attributi che vuoi testare, su ogni test di ordinamento, guardi e vedi se la funzione cmp restituisce un ritorno diverso da zero in tal caso, interrompere e inviare il valore restituito. Lo chiami chiamando un Lambda di una funzione di un elenco di Lambdas.

Il suo vantaggio è che passa attraverso i dati non una sorta di ordinamento precedente come fanno altri metodi. Un'altra cosa è che si ordina sul posto, mentre ordinato sembra fare una copia.

L'ho usato per scrivere una funzione di rango, che classifica un elenco di classi in cui ogni oggetto è in un gruppo e ha una funzione di punteggio, ma è possibile aggiungere qualsiasi elenco di attributi. Nota il non-lambda-like, anche se uso hacker di un lambda per chiamare un setter. La parte di rango non funziona per una serie di elenchi, ma l'ordinamento funzionerà.

#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
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.