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, ...).