Eine Liste nach mehreren Attributen sortieren?

456

Ich habe eine Liste von Listen:

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

Wenn ich nach einem Element sortieren wollte, sagen wir das große / kurze Element, könnte ich es über tun s = sorted(s, key = itemgetter(1)).

Wenn ich sowohl nach groß / klein als auch nach Farbe sortieren wollte , könnte ich die Sortierung zweimal durchführen, einmal für jedes Element, aber gibt es einen schnelleren Weg?

Kopfschmerzen
quelle
8
Wenn Sie Tupel anstelle von Listen verwenden, werden Python-Ordnungen beim Ausführen nach Einträgen von links nach rechts sortiert sort. Das heißt , sorted([(4, 2), (0, 3), (0, 1)]) == [(0, 1), (0, 3), (4, 2)].
Mateen Ulhaq

Antworten:

771

Ein Schlüssel kann eine Funktion sein, die ein Tupel zurückgibt:

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

Oder Sie können dasselbe erreichen itemgetter(was schneller ist und einen Python-Funktionsaufruf vermeidet):

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

Und beachten Sie, dass Sie hier verwenden können, sortanstatt zu verwenden sortedund dann neu zuzuweisen:

s.sort(key = operator.itemgetter(1, 2))
Mark Byers
quelle
20
Der Vollständigkeit halber: Für mich gab zuerst 6 uns pro Schleife und die zweite 4,4 uns pro Schleife
Brian Larsen
10
Gibt es eine Möglichkeit, den ersten aufsteigend und den zweiten absteigend zu sortieren? (Angenommen, beide Attribute sind Zeichenfolgen, also keine Hacks wie das Hinzufügen -von Ganzzahlen)
Martin Thoma
73
Wie wäre es, wenn ich mich revrse=Truenur bewerben möchte x[1]? Ist das möglich?
Amyth
28
@moose, @Amyth, um nur ein Attribut umzukehren, können Sie zweimal sortieren: zuerst nach dem sekundären, s = sorted(s, key = operator.itemgetter(2))dann nach dem primären. s = sorted(s, key = operator.itemgetter(1), reverse=True)Nicht ideal, funktioniert aber.
Tomcounsell
52
@Amyth oder eine andere Option, wenn der Schlüssel eine Zahl ist, können Sie ihn multiplizieren, um ihn umzukehren -1.
Serge
37

Ich bin mir nicht sicher, ob dies die pythonischste Methode ist ... Ich hatte eine Liste von Tupeln, die zuerst nach absteigenden Ganzzahlwerten und zweitens alphabetisch sortiert werden mussten. Dies erforderte die Umkehrung der ganzzahligen Sortierung, jedoch nicht der alphabetischen Sortierung. Hier war meine Lösung: (Im laufenden Betrieb in einer Prüfung war mir übrigens nicht einmal bewusst, dass Sie sortierte Funktionen "verschachteln" können)

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)]
Clint Blatchford
quelle
13
Da 2nd eine Zahl ist, funktioniert es so b = sorted(a, key = lambda x: (-x[1], x[0])), als wäre es besser, welche Kriterien zuerst gelten. Was die Effizienz angeht, bin ich mir nicht sicher, ob jemand Zeit hat.
Andrei-Niculae Petre
5

Es scheint, Sie könnten a listanstelle von a verwenden tuple. Dies wird meiner Meinung nach wichtiger, wenn Sie Attribute anstelle von 'magischen Indizes' einer Liste / eines Tupels abrufen.

In meinem Fall wollte ich nach mehreren Attributen einer Klasse sortieren, wobei die eingehenden Schlüssel Zeichenfolgen waren. Ich brauchte unterschiedliche Sortierungen an verschiedenen Orten und wollte eine gemeinsame Standardsortierung für die übergeordnete Klasse, mit der Clients interagierten. Ich musste die 'Sortierschlüssel' nur überschreiben, wenn ich es wirklich 'brauchte', aber auch so, dass ich sie als Listen speichern konnte, die die Klasse gemeinsam nutzen konnte

Also habe ich zuerst eine Hilfsmethode definiert

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 ]

dann, um es zu benutzen

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

Dies verwendet die generierte Lambda-Funktion, sortiert die Liste nach object.attrAund nimmt dann an, object.attrBdass objectein Getter vorhanden ist, der den angegebenen Zeichenfolgennamen entspricht. Und der zweite Fall würde bis object.attrCdahin sortieren object.attrA.

Auf diese Weise können Sie auch potenzielle Sortieroptionen nach außen offenlegen, die von einem Verbraucher gleichermaßen geteilt werden können, einen Komponententest durchführen oder Ihnen möglicherweise mitteilen, wie die Sortierung für einen Vorgang in Ihrer API erfolgen soll, indem Sie nur eine Liste angeben müssen und nicht Koppeln Sie sie mit Ihrer Back-End-Implementierung.

UpAndAdam
quelle
Gute Arbeit. Was ist, wenn die Attribute in verschiedenen Reihenfolgen sortiert werden sollen? Angenommen, attrA sollte aufsteigend und attrB absteigend sortiert werden? Gibt es darüber hinaus eine schnelle Lösung? Vielen Dank!
mhn_namak
4

Einige Jahre zu spät zur Party, aber ich möchte sowohl nach 2 Kriterien sortieren als auch verwenden reverse=True. Falls jemand anderes wissen möchte, wie, können Sie Ihre Kriterien (Funktionen) in Klammern setzen:

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

Hier ist eine Möglichkeit: Sie schreiben Ihre Sortierfunktion im Grunde neu, um eine Liste von Sortierfunktionen zu erstellen. Jede Sortierfunktion vergleicht die Attribute, die Sie testen möchten. Bei jedem Sortiertest prüfen Sie, ob die cmp-Funktion eine Rückgabe ungleich Null zurückgibt Wenn ja, brechen Sie und senden Sie den Rückgabewert. Sie nennen es, indem Sie ein Lambda einer Funktion einer Liste von Lambdas aufrufen.

Sein Vorteil ist, dass es die Daten einmalig durchläuft und nicht wie andere Methoden. Eine andere Sache ist, dass es an Ort und Stelle sortiert, während sortiert eine Kopie zu machen scheint.

Ich habe es verwendet, um eine Rangfunktion zu schreiben, die eine Liste von Klassen ordnet, in denen sich jedes Objekt in einer Gruppe befindet und eine Bewertungsfunktion hat, aber Sie können eine beliebige Liste von Attributen hinzufügen. Beachten Sie die un-Lambda-ähnliche, wenn auch hackige Verwendung eines Lambda, um einen Setter zu nennen. Der Rangteil funktioniert nicht für eine Reihe von Listen, die Sortierung jedoch.

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

Hier ist eine Möglichkeit, eine Liste von Objekten zu ordnen

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
Dominic Suciu
quelle