Eingebaute Typen sortieren
slices.Sort sortiert jeden Slice, dessen Elementtyp geordnet ist: Ganzzahlen, Floats und Strings. Es sortiert direkt im Slice und gibt nichts zurück.
Ausgabe:
[3 7 19 42 88]
[Bob Carl alice lisa]
true
2 true
Strings werden nach Bytes sortiert, also kommt jeder ASCII-Großbuchstabe vor jedem Kleinbuchstaben. Bei Namen erwartet ein Nutzer das selten. Der nächste Abschnitt behebt das.
Das Paket slices kam mit Go 1.21. Sein Sort ist ein Pattern-Defeating Quicksort: O(n log n), direkt im Slice und nicht stabil.
Eigene Reihenfolge mit SortFunc
slices.SortFunc nimmt eine Vergleichsfunktion func(a, b T) int. Gib eine negative Zahl zurück, wenn a zuerst kommen soll, eine positive, wenn b zuerst kommen soll, und null, wenn beide gleich sind. Der Helfer cmp.Compare liefert genau das für geordnete Typen.
strings.ToLower in einem Vergleich legt jedes Mal einen neuen String an, wenn die Eingabe einen Großbuchstaben enthält, und der Vergleich läuft etwa n log n Mal. Bei großen Slices berechnest du die kleingeschriebenen Schlüssel einmal vorab. Für Namen in anderen Sprachen (Akzente, Regeln des Gebietsschemas) nimm golang.org/x/text/collate, das außerhalb der Standardbibliothek liegt.
Eine Vergleichsfunktion muss konsistent sein: Sagt sie, dass a vor b kommt, muss sie auch sagen, dass b nach a kommt. Eine Funktion, die das verletzt (zum Beispiel immer -1 zurückgibt, wenn sich zwei Werte unterscheiden), erzeugt einen falsch sortierten Slice ohne jeden Fehler. Ganzzahlen zu subtrahieren (return a - b) sieht elegant aus, läuft aber bei großen Werten über; nimm cmp.Compare.
Structs sortieren
Die Vergleichsfunktion bekommt Elemente, also ist das Sortieren von Structs nach einem Feld derselbe Aufruf:
Mitarbeiter mit gleichem Gehalt können hier in beliebiger Reihenfolge herauskommen, weil SortFunc nicht stabil ist. Brauchst du eine garantierte Reihenfolge, löse Gleichstände mit weiteren Feldern auf oder nimm eine stabile Sortierung.
Nach mehreren Feldern sortieren
Vergleiche zuerst das wichtigste Feld und geh nur bei Gleichstand zum nächsten. cmp.Or (Go 1.22) gibt sein erstes Argument ungleich null zurück und macht daraus eine Zeile:
Ausgabe:
eng 150 Cy
eng 120 Ana
eng 120 Eve
sales 90 Bob
sales 90 Dee
Jeder Vergleich wird ausgewertet, auch wenn schon der erste entscheidet, da es gewöhnliche Argumente sind. Bei Feldvergleichen kostet das wenig. Ist ein Tie-Breaker teuer, schreib die Kette if c := ...; c != 0 { return c } von Hand.
Stabile Sortierung
Eine stabile Sortierung lässt Elemente, die als gleich gelten, in ihrer ursprünglichen Reihenfolge. Das zählt, wenn die Eingabe schon eine sinnvolle Reihenfolge hat, zum Beispiel nach Zeit sortierte Datensätze, die du jetzt nach Nutzer gruppierst.
Das gibt [{ana 2} {ana 4} {bob 1} {bob 3} {bob 5}] aus: Innerhalb jedes Nutzers bleibt die ursprüngliche Folge erhalten. Stabiles Sortieren macht mehr Arbeit, also nutz es nur, wenn die Reihenfolge gleicher Elemente zählt.
Eine Map sortieren
Maps haben keine Reihenfolge. Um eine Map nach Schlüssel sortiert anzuzeigen, sortierst du ihre Schlüssel: slices.Sorted(maps.Keys(m)) (Go 1.23). Um nach Wert zu sortieren, sortier die Schlüssel mit einem Vergleich, der die Werte nachschlägt:
Der Tie-Breaker auf dem Wort zählt: Ohne ihn würden chan und slice (beide 7) von Lauf zu Lauf in anderer Reihenfolge erscheinen, weil die Schlüssel in zufälliger Reihenfolge aus der Map kommen. Mehr dazu unter Maps.
Das Paket sort: sort.Slice und Verwandte
Vor Go 1.21 lief das Sortieren über das Paket sort. Du siehst es in viel bestehendem Code:
sort.Ints(nums)
sort.Strings(names)
sort.Slice(staff, func(i, j int) bool {
return staff[i].Salary < staff[j].Salary
})
sort.SliceStable(staff, func(i, j int) bool { ... })
Unterschiede, die du kennen solltest:
sort.Slice | slices.SortFunc | |
|---|---|---|
| Die Funktion bekommt | Indizes i, j | Elemente a, b |
| Gibt zurück | bool (ist i kleiner als j) | int (negativ, null, positiv) |
| Typsicherheit | nimmt any, nutzt Reflection | generisch, zur Compilezeit geprüft |
| Geschwindigkeit | langsamer | schneller |
Ein häufiger Bug bei sort.Slice ist, in der Closure einen anderen Slice einzufangen als den, der sortiert wird, da die Less-Funktion über Positionen indiziert. SortFunc kann diesen Bug nicht haben, weil es dir die Elemente übergibt.
Der Typ sort.Interface (Len, Less, Swap) ist die älteste Form. Mit ihm sortierst du nach wie vor Daten, die kein einzelner Slice sind, etwa zwei parallele Slices, die sich gemeinsam bewegen müssen. Seit Go 1.22 rufen sort.Ints, sort.Strings und sort.Float64s einfach slices.Sort auf.
Häufige Fehler
- Erwarten, dass
Sortden sortierten Slice zurückgibt. Es sortiert direkt im Slice und gibt nichts zurück. Nimmslices.Sorted(slices.Values(s)), wenn du einen neuen sortierten Slice willst, oder vorherslices.Clone. - Annehmen, dass gleiche Elemente ihre Reihenfolge behalten. Das versprechen nur die
Stable-Varianten. - Zum Vergleichen subtrahieren.
a - bläuft über. Nimmcmp.Compare. - Floats mit NaN sortieren.
cmp.Compareordnet NaN vor jedem anderen Wert ein, und das hält die Sortierung konsistent. Ein selbst geschriebener Vergleicha < btut das nicht.
Häufig gestellte Fragen
Wie sortiert man einen Slice in Go?
Für Zahlen und Strings rufst du slices.Sort(s) auf (Go 1.21). Es sortiert direkt im Slice in aufsteigender Reihenfolge. Für alles andere oder eine andere Reihenfolge nimm slices.SortFunc(s, func(a, b T) int { ... }), wobei die Funktion eine negative Zahl zurückgibt, wenn a zuerst kommt, eine positive, wenn b zuerst kommt, und null, wenn beide gleich sind.
Wie sortiere ich einen Slice in Go absteigend?
Vertausch die Argumente im Vergleich: slices.SortFunc(s, func(a, b int) int { return cmp.Compare(b, a) }). Oder sortier aufsteigend und ruf dann slices.Reverse(s) auf.
Wie sortiere ich in Go einen Slice von Structs nach mehreren Feldern?
Vergleiche das erste Feld und geh nur bei Gleichheit zum nächsten weiter. cmp.Or (Go 1.22) macht genau das: return cmp.Or(cmp.Compare(a.Dept, b.Dept), cmp.Compare(b.Salary, a.Salary), strings.Compare(a.Name, b.Name)) gibt das erste Ergebnis ungleich null zurück.
Was ist der Unterschied zwischen sort.Slice und slices.SortFunc?
sort.Slice(s, func(i, j int) bool) ist die ältere API: Sie nimmt eine Less-Funktion über Indizes und tauscht Elemente per Reflection. slices.SortFunc(s, func(a, b T) int) ist generisch, typgeprüft, bekommt die Elemente direkt und ist schneller. Neuer Code sollte das Paket slices nutzen; sort.Slice ist in Code, der vor Go 1.21 geschrieben wurde, noch verbreitet.