Python: Wie sortiert man ein Dictionary nach Werten?
Ein Dictionary nach Werten zu sortieren ist ein Einzeiler. Interessant wird es bei Gleichständen — und wenn in einer Sortierung aufsteigend und absteigend gemischt werden sollen.
Die Antwort
sortiert = dict(sorted(punkte.items(), key=lambda kv: kv[1]))
scores = {'ada': 92, 'alan': 78, 'grace': 92, 'linus': 65}
ascending : {'linus': 65, 'alan': 78, 'ada': 92, 'grace': 92}
descending : reverse=True
Drei Dinge passieren dabei. .items() liefert Paare aus (Schlüssel, Wert),
die key=-Funktion holt den Wert aus jedem Paar heraus, und
sorted liefert
eine Liste — das dict() darum macht daraus wieder ein Mapping:
type : list
[('linus', 65), ('alan', 78), ('ada', 92), ('grace', 92)]
Dieser letzte Schritt erhält die Reihenfolge nur deshalb, weil Dictionaries die
Einfügereihenfolge behalten, garantiert seit Python 3.7. Auf älteren Versionen
bräuchte man collections.OrderedDict.
Oft will man gar kein Dictionary zurück — eine Liste von Paaren ist einfacher zu handhaben und darf Dubletten enthalten.
operator.itemgetter(1) ist eine etwas schnellere und wohl klarere Alternative
zum Lambda:
key=itemgetter(1) : [('linus', 65), ('alan', 78), ('ada', 92), ('grace', 92)]
Gleichstände
ada und grace haben beide 92. Wer kommt zuerst?
ascending : ['linus', 'alan', 'ada', 'grace']
ada, weil sie im ursprünglichen Dictionary vorne stand. Pythons Sortierung
ist stabil: Gleichwertige Elemente behalten ihre relative Reihenfolge. Das
ist eine Zusage und kein Zufall, darauf kannst du dich verlassen.
Sollen Gleichstände ausdrücklich aufgelöst werden, sortierst du nach einem Tupel:
key=lambda kv: (kv[1], kv[0])
by value, then name : ['linus', 'alan', 'ada', 'grace']
Tupel werden elementweise verglichen, das liest sich also als “nach Wert, dann nach Name”.
Richtungen mischen
Und jetzt der wirklich sperrige Fall. Du willst die höchste Punktzahl zuerst
und innerhalb derselben Punktzahl die Namen alphabetisch. reverse=True dreht
alles um, die Namen eingeschlossen.
Bei Zahlen negierst du:
key=lambda kv: (-kv[1], kv[0])
value DESC, name ASC : ['ada', 'grace', 'alan', 'linus']
Punkte absteigend, Namen bei Gleichstand aufsteigend. Negieren funktioniert natürlich nur mit Zahlen. Bei Strings sortiert man zweimal, das unwichtigste Kriterium zuerst, und verlässt sich auf die Stabilität:
zeilen = sorted(zeilen, key=itemgetter(0)) # Name, aufsteigend
zeilen = sorted(zeilen, key=itemgetter(1), reverse=True) # Punkte, absteigend
Die zweite Sortierung erhält die Reihenfolge der ersten bei gleichen Punkten. Das funktioniert für jede Kombination aus Richtungen und Typen.
Der Fehlgriff, den man benennen sollte
keys sorted by value : ['linus', 'alan', 'ada', 'grace']
sorted(scores) : ['ada', 'alan', 'grace', 'linus']
sorted(punkte) iteriert über das Dictionary, und das liefert Schlüssel —
du bekommst also die Namen alphabetisch. Das sieht plausibel aus und ist nicht
das, wonach du gefragt hast.
Willst du nur die Schlüssel in der Reihenfolge der Werte, übergib die Nachschlagefunktion des Dictionary selbst:
sorted(punkte, key=punkte.get)
Wenn du nur die ersten paar brauchst
Alles zu sortieren, um sich drei Einträge anzusehen, ist Verschwendung.
heapq.nlargest
kommt ohne vollständige Sortierung aus:
heapq.nlargest(2, scores, key=scores.get) : ['ada', 'grace']
Und wenn du zählst, hat
Counter
das eingebaut:
Counter('mississippi').most_common(2) : [('i', 4), ('s', 4)]
most_common() ist bereits nach Anzahl absteigend sortiert. Kein key= nötig.
Zwei Kleinigkeiten
Eine Variante an Ort und Stelle gibt es nicht:
original unchanged : True
Listen haben .sort(), Dictionaries nicht. sorted() liefert immer etwas
Neues.
Und wenn deine key-Funktion fehlschlagen kann, tut sie das lautstark mitten
in der Sortierung:
-> KeyError: 'n'
Das ist ein Dictionary, dessen Werte selbst Dictionaries sind, und einem davon
fehlt das Feld nach dem sortiert wird. .get('n', 0) behebt es:
with .get(..., 0) : [('b', {}), ('a', {'n': 1})]
Hinweis zu Netcup (Werbung)
Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).