Wie man ein Dictionary in Python sortiert

Dictionaries eignen sich am besten für die Suche nach Schlüsselwerten: Wir geben einen Schlüssel (key) an und das Dictionary liefert sehr schnell den entsprechenden Wert (value).
Aber was ist, wenn man sowohl nach Schlüsselwerten suchen als auch iterieren möchte? Es ist möglich, eine Schleife über ein Dictionary zu ziehen, und dabei kann die Reihenfolge der Elemente im Dictionary von Bedeutung sein.
Mit der Reihenfolge der Einträge im Hinterkopf fragt man sich vielleicht, wie man ein Dictionary sortieren kann?

Dictionaries sind geordnet

Seit Python 3.6 sind Wörterbücher geordnet (technisch wurde diese Eigenschaft in 3.7 offiziell).

Dictionary Keys werden in Einfügereihenfolge gespeichert, das heißt, wenn ein neuer Schlüssel hinzugefügt wird, wird er ganz am Ende eingefügt.

>>> color_amounts = {"purple": 6, "green": 3, "blue": 2}
>>> color_amounts["pink"] = 4
>>> color_amounts
{'purple': 6, 'green': 3, 'blue': 2, 'pink': 4}

Wenn wir jedoch ein Schlüssel-Wert-Paar aktualisieren, bleibt der Schlüssel dort, wo er vorher war:

>>> color_amounts["green"] = 5
>>> color_amounts
{'purple': 6, 'green': 5, 'blue': 2, 'pink': 4}

Wenn man also vorhat, ein Dictionary mit bestimmten Daten zu füllen und es dann so zu belassen, wie es ist, muss man nur sicherstellen, dass die Originaldaten in der gewünschten Reihenfolge vorliegen.

Wenn man zum Beispiel eine CSV-Datei mit Abkürzungen von US-Bundesstaaten hat und die Datei alphabetisch nach dem Namen des Bundesstaates geordnet ist, wird das Dictionary auf die gleiche Weise geordnet sein:

>>> import csv
>>> state_abbreviations = {}
>>> for name, abbreviation in csv.reader("state-abbreviations.csv")
... state_abbreviations[name] = abbreviation
...
>>> state_abbreviations
{'Alabama': 'AL', 'Alaska': 'AK', 'Arizona': 'AZ', 'Arkansas': 'AR', 'California': 'CA', ...}

Wenn die Eingabedaten bereits richtig geordnet sind, wird auch das Dictionary richtig geordnet sein.

Wie sortiert man ein Wörterbuch nach seinen Schlüsseln?

Was ist, wenn die Daten noch nicht sortiert sind?

Nehmen wir an, wir haben ein Dcitionary, das Tagungsräume den entsprechenden Raumnummern zuordnet:

>>> rooms = {"Pink": "Rm 403", "Space": "Rm 201", "Quail": "Rm 500", "Lime": "Rm 503"}

Und wir möchten dieses Dictionary nach seinen Schlüsseln sortieren.

Wir könnten die items-Methode für unser Dictionary verwenden, um iterierbare key-value-Tupel zu erhalten und dann die sorted-Funktion verwenden, um diese Tupel zu sortieren:

>>> rooms.items()
dict_items([('Pink', 'Rm 403'), ('Space', 'Rm 201'), ('Quail', 'Rm 500'), ('Lime', 'Rm 503')])
>>> sorted(rooms.items())
[('Lime', 'Rm 503'), ('Pink', 'Rm 403'), ('Quail', 'Rm 500'), ('Space', 'Rm 201')]

Die Funktion sorted verwendet den Operator <, um mehrere Elemente in der angegebenen Iterable zu vergleichen und eine sortierte Liste zurückzugeben. Die Funktion sorted gibt immer eine Liste zurück.

Um diese key-value-Paare in ein Dictionary zu verwandeln, können wir sie direkt an den dict-Konstruktor übergeben:

>>> sorted_rooms = dict(sorted(rooms.items()))
>>> sorted_rooms
{'Lime': 'Rm 503', 'Pink': 'Rm 403', 'Quail': 'Rm 500', 'Space': 'Rm 201'}

Der dict-Konstruktor akzeptiert eine Liste von 2-Element-Tupeln (oder eine beliebige Iterable von 2-Element-Iterables) und erstellt daraus ein Dictionary, wobei das erste Element jedes Tupels als Schlüssel (key) und das zweite als entsprechender Wert (value) verwendet wird.

Schlüssel-Wert-Paare werden lexikografisch sortiert… was?

Wir sortieren die Tupel der Schlüssel-Wert-Paare, bevor wir aus ihnen ein Wörterbuch erstellen. Aber wie funktioniert das Sortieren von Tupeln?

>>> some_tuples = [(1, 3), (3, 1), (1, 9), (0, 3)]
>>> sorted(some_tuples)
[(0, 3), (1, 3), (1, 9), (3, 1)]

Beim Sortieren von Tupeln verwendet Python die lexikografische Sortierung. Der Vergleich eines Tupels mit 2 Elementen läuft im Wesentlichen auf diesen Algorithmus hinaus:

def compare_two_item_tuples(a, b):
    """This is the same as a < b for two 2-item tuples."""
    if a[0] != b[0]: # If the first item of each tuple is unequal
        return a[0] < b[0] # Compare the first item from each tuple
    else:
        return a[1] < b[1] # Compare the second item from each tuple

Man wird vielleicht denken: Das scheint nicht nur nach Schlüsseln, sondern nach Schlüsseln und Werten zu sortieren. Und man hat Recht! Aber nur so ungefähr.

Die Schlüssel in einem Dictionary sollten immer als ungleich verglichen werden (wenn zwei Schlüssel gleich sind, werden sie als derselbe Schlüssel angesehen). Solange also die Schlüssel mit dem Kleiner-als-Operator (<) miteinander vergleichbar sind, sollte die Sortierung von 2-Element-Tupeln von Schlüssel-Wert-Paaren immer nach den Schlüsseln erfolgen.

Dictionaries können nicht an Ort und Stelle sortiert werden

Was ist, wenn wir unsere Elemente bereits in einem Dictionary haben und dieses Dictionary sortieren möchten? Anders als bei Listen gibt es bei Dictionaries keine Sortiermethode.

Wir können ein Dictionary nicht an Ort und Stelle sortieren, aber wir können die Elemente aus unserem Dictionary holen, diese Elemente mit der gleichen Technik wie zuvor sortieren und dann diese Elemente in ein neues Wörterbuch umwandeln:

>>> rooms = {"Pink": "Rm 403", "Space": "Rm 201", "Quail": "Rm 500", "Lime": "Rm 503"}
>>> sorted_rooms = dict(sorted(rooms.items()))
>>> sorted_rooms
{'Lime': 'Rm 503', 'Pink': 'Rm 403', 'Quail': 'Rm 500', 'Space': 'Rm 201'}

Dadurch wird ein neues Dictionary-Objekt erstellt. Wenn wir unser ursprüngliches Dictionary-Objekt wirklich aktualisieren wollten, könnten wir die Elemente aus dem Dictionary nehmen, sie sortieren, das Dictionary von allen Elementen löschen und dann alle Elemente wieder in das Dictionary einfügen:

>>> old_dictionary = {"Pink": "Rm 403", "Space": "Rm 201", "Quail": "Rm 500", "Lime": "Rm 503"}
>>> sorted_items = sorted(old_dictionary.items())
>>> old_dictionary.clear()
>>> old_dictionary.update(sorted_items)

Aber warum die Mühe? Normalerweise wollen wir in Python nicht an Ort und Stelle mit Datenstrukturen arbeiten: Wir neigen dazu, lieber eine neue Datenstruktur zu erstellen, als eine alte wiederzuverwenden.

Wie man ein Dictionary nach seinen Werten sortiert

Was wäre, wenn wir ein Dictionary nach seinen Werten und nicht nach seinen Schlüsseln sortieren wollten?

Wir könnten eine neue Liste von Wert-Schlüssel-Tupeln erstellen (in unserem Fall ein Generator), diese sortieren, sie dann wieder in Schlüssel-Wert-Tupel umwandeln und unser Dictionary neu erstellen:

>>> rooms = {"Pink": "Rm 403", "Space": "Rm 201", "Quail": "Rm 500", "Lime": "Rm 503"}
>>> room_to_name = sorted((room, name) for (name, room) in rooms.items())
>>> sorted_rooms = {
... name: room
... for room, name in room_to_name
... }
>>> sorted_rooms
{'Space': 'Rm 201', 'Pink': 'Rm 403', 'Quail': 'Rm 500', 'Lime': 'Rm 503'}

Das funktioniert, ist aber ein bisschen lang. Außerdem werden bei dieser Technik sowohl die Werte als auch die Schlüssel sortiert (wobei die Werte bei der Sortierung Vorrang haben).

Was wäre, wenn wir unser Dictionary nur nach den Werten sortieren und den Inhalt der Schlüssel völlig ignorieren wollten? Pythons sorted-Funktion akzeptiert ein Schlüsselargument, das wir dafür verwenden können!

>>> help(sorted)
Help on built-in function sorted in module builtins:

sorted(iterable, /, *, key=None, reverse=False)
    Return a new list containing all items from the iterable in ascending order.

    A custom key function can be supplied to customize the sort order, and the
    reverse flag can be set to request the result in descending order.

Die Schlüsselfunktion, die wir an sorted übergeben, sollte ein Element aus der zu sortierenden Iterablen akzeptieren und den Schlüssel zurückgeben, nach dem sortiert werden soll. Es ist zu beachten, dass das Wort „Schlüssel“ hier nichts mit Dictionary-Schlüsseln zu tun hat. Dictionary-Schlüssel werden zum Nachschlagen von Dictionary-WErten verwendet, während diese Schlüsselfunktion ein Objekt zurückgibt, das bestimmt, wie die Elemente in einer Iterable zu sortieren sind.

Wenn wir das Dictionary nach seinen Werten sortieren wollen, könnten wir eine Schlüsselfunktion erstellen, die jedes Element in unserer Liste von 2-Element-Tupeln akzeptiert und nur den Wert zurückgibt:

def value_from_item(item):
   """Return just the value from a given (key, value) tuple."""
    key, value = item
    return value

Dann würden wir unsere Schlüsselfunktion verwenden, indem wir sie an die sortierte Funktion übergeben und das Ergebnis an dict übergeben, um ein neues Dictionary zu erstellen:

>>> sorted_rooms = dict(sorted(rooms.items(), key=value_from_item))
>>> sorted_rooms
{'Space': 'Rm 201', 'Pink': 'Rm 403', 'Quail': 'Rm 500', 'Lime': 'Rm 503'}

Wenn man es vorzieht, keine benutzerdefinierte Schlüsselfunktion zu erstellen, nur um sie einmal zu verwenden, kann man eine Lambda-Funktion verwenden:

>>> sorted_rooms = dict(sorted(rooms.items(), key=lambda item: item[1]))
>>> sorted_rooms
{'Space': 'Rm 201', 'Pink': 'Rm 403', 'Quail': 'Rm 500', 'Lime': 'Rm 503'}

Oder man könnte operator.itemgetter verwenden, um eine Schlüsselfunktion zu erstellen, die das zweite Element aus jedem Schlüssel-Wert-Tupel abruft:

>>> from operator import itemgetter
>>> sorted_rooms = dict(sorted(rooms.items(), key=itemgetter(1)))
>>> sorted_rooms
{'Space': 'Rm 201', 'Pink': 'Rm 403', 'Quail': 'Rm 500', 'Lime': 'Rm 503'}

Sortieren eines Dictionary auf eine andere Weise

Was wäre, wenn wir unser Dictionary nach etwas anderem als nur einem Schlüssel oder einem Wert sortieren müssten? Was wäre zum Beispiel, wenn die Zeichenketten mit den Zimmernummern Zahlen enthalten, die nicht immer die gleiche Länge haben?

rooms = {
    "Pink": "Rm 403",
    "Space": "Rm 201",
    "Quail": "Rm 500",
    "Lime": "Rm 503",
    "Ocean": "Rm 2000",
    "Big": "Rm 30",
}

Wenn wir diese Räume nach Werten sortieren würden, wären diese Zeichenfolgen nicht so sortiert, wie wir es uns wünschen:

>>> from operator import itemgetter
>>> sorted_rooms = dict(sorted(rooms.items(), key=itemgetter(1)))
>>> sorted_rooms
{'Ocean': 'Rm 2000', 'Space': 'Rm 201', 'Big': 'Rm 30', 'Pink': 'Rm 403', 'Quail': 'Rm 500', 'Lime': 'Rm 503'}

Rm 30 sollte an erster und Rm 2000 an letzter Stelle stehen. Aber wir sortieren Zeichenketten, die Zeichen für Zeichen auf der Grundlage des Unicode-Werts jedes Zeichens sortiert werden.

Wir könnten die Schlüsselfunktion, die wir verwenden, anpassen, um stattdessen numerisch zu sortieren:

def by_room_number(item):
    """Return numerical room given a (name, room_number) tuple."""
    name, room = item
    _, number = room.split()
    return int(number)

Wenn wir diese Schlüsselfunktion verwenden, um unser Wörterbuch zu sortieren:

>>> sorted_rooms = dict(sorted(rooms.items(), key=by_room_number))

Die Sortierung erfolgt erwartungsgemäß nach der ganzzahligen Raumnummer:

>>> sorted_rooms
{'Big': 'Rm 30', 'Space': 'Rm 201', 'Pink': 'Rm 403', 'Quail': 'Rm 500', 'Lime': 'Rm 503', 'Ocean': 'Rm 2000'}

Sollten man ein Dictionary sortieren?

Wenn man ein Dictionary sortieren möchte, fragt man sich zuerst: „Muss ich das tun“? Wenn man überlegt, eine Schleife über ein Dictionary zu ziehen, könnte man sich sogar fragen: „Brauche ich hier wirklich ein Dictionary“?

Dictionaries werden für die Suche nach Schlüsselwerten verwendet: Man kannn schnell einen Wert anhand eines Schlüssels abrufen. Sie sind sehr schnell beim Abrufen von Werten für Schlüssel. Aber Dictionaries nehmen mehr Platz ein als eine Liste von Tupeln.
Wenn man im Code mit einer Liste von Tupeln auskommen kann (weil man eigentlich keine Schlüssel-Wert-Abfrage benötigt), sollte mann eher eine Liste von Tupeln anstelle eines Dictionary verwenden.
Aber wenn man Schlüsselabfragen benötigt, ist es unwahrscheinlich, dass man auch eine Schleife über das Dictionary ausführen muss.

Es ist sicherlich möglich, dass man gerade jetzt einen guten Anwendungsfall für die Sortierung eines Dictionary hat (z. B. wenn man die Schlüssel in einem Attribut-Dictionary sortiert), aber man sollte bedenken, dass man ein Dictionary nur sehr selten sortieren muss.