Sortierung von Strukturen in Go: Der kurze Weg und die Magie unter der Haube

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Sortierung von Strukturen in Go: Wie man kurz schreibt und sich nicht das Gehirn verknotet

\n\n

Stellen Sie sich vor: Sie haben ein Slice von Strukturen und müssen es nach einem beliebigen Feld sortieren. Name, Alter, Gehalt, Geburtsdatum – was auch immer. Auf Stack Overflow hat die Frage „What is the shortest way to simply sort an array of structs by (arbitrary) field names?“ tausende Aufrufe gesammelt. Und das ist kein Zufall. In Go gibt es keine eingebaute „magische“ Methode wie list.sort(key=lambda x: x.field) aus Python. Aber es gibt einen eleganten und mächtigen Weg – sort.Slice. In diesem Artikel zeigen wir nicht nur den Code, sondern schauen unter die Haube: wie die Sortierung funktioniert, was die Runtime macht und wie man typische Fallstricke vermeidet.

\n\n

Das Problem: „Warum kann man in Go nicht einfach sort() schreiben?“

\n\n

Wenn Sie von Python, JavaScript oder Ruby kommen, sind Sie eine einzige Zeile gewohnt:

\n
users.sort(key=lambda u: u.age)
\n

Diesen Luxus gibt es in Go nicht. Starke Typisierung und das Fehlen von Generics (bis Go 1.18) zwangen dazu, für jeden Strukturtyp eine separate Implementierung von sort.Interface zu schreiben. Das war umständlich. Zum Beispiel:

\n
type ByAge []User\nfunc (a ByAge) Len() int           { return len(a) }\nfunc (a ByAge) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }\nfunc (a ByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }\n\nfunc main() {\n    users := []User{{"Alice", 30}, {"Bob", 25}}\n    sort.Sort(ByAge(users))\n}
\n

Es funktioniert, aber für jedes Feld ein neuer Typ. Und wenn es 10 Felder sind? Der Code wird zu einem langen Text. Man wünscht sich etwas Kompakteres.

\n\n

Die Lösung: sort.Slice – Ihr Allround-Werkzeug

\n\n

Ab Go 1.8 gibt es im Paket sort die Funktion Slice. Sie nimmt ein Slice und eine anonyme Funktion (Closure) entgegen, die die Reihenfolge bestimmt. Hier ist er – der kürzeste Weg:

\n
sort.Slice(users, func(i, j int) bool {\n    return users[i].Age < users[j].Age\n})
\n

Das war's. Drei Zeilen. Ohne Deklaration neuer Typen, ohne überflüssige Methoden. Möchten Sie nach Namen sortieren? Wir ändern das Feld:

\n
sort.Slice(users, func(i, j int) bool {\n    return users[i].Name < users[j].Name\n})
\n

Und wenn es absteigend sein soll? Einfach das Vorzeichen ändern:

\n
sort.Slice(users, func(i, j int) bool {\n    return users[i].Age > users[j].Age // jetzt absteigend\n})
\n

Wunderschön. Aber wie funktioniert das? Und wo ist der Haken?

\n\n

Unter der Haube: sort.Slice und anonyme Funktionen

\n\n

Lassen Sie uns analysieren, was passiert, wenn Sie sort.Slice aufrufen. Die Funktion ist so deklariert:

\n
func Slice(slice interface{}, less func(i, j int) bool)
\n

Das erste, was ins Auge fällt, ist interface{}. Die Funktion akzeptiert jedes Slice. Wie erfährt sie seine Länge und wie vertauscht sie die Elemente? Die Antwort – über Reflection (Paket reflect).

\n

Im Inneren macht sort.Slice Folgendes:

\n
    \n
  1. Überprüft, ob ein Slice übergeben wurde. Wenn kein Slice übergeben wird (z. B. ein Array oder eine Struktur), gerät das Programm zur Laufzeit in Panik.
  2. \n
  3. Ermittelt die Länge über reflect.ValueOf(slice).Len().
  4. \n
  5. Erstellt ein Hilfsobjekt, das das Interface sort.Interface dynamisch implementiert. Dieses Objekt speichert einen Verweis auf das ursprüngliche Slice (über Reflection) und auf Ihre less-Funktion.
  6. \n
  7. Startet den Standard-Sortieralgorithmus (Quicksort aus dem Paket sort). Wenn der Algorithmus zwei Elemente vergleichen muss, ruft er Ihre less-Funktion auf. Wenn er Elemente vertauschen muss, verwendet er reflect.Swapper(slice).
  8. \n
\n

Der entscheidende Punkt: Ihre less-Funktion erfasst das ursprüngliche Slice aus dem äußeren Kontext. Dies ist ein Closure. Der Compiler platziert die Variable users auf dem Heap, damit sie auch nach dem Verlassen der Funktion m verfügbar ist.

Blogs

Buchempfehlungen