Sortowanie typów wbudowanych
slices.Sort sortuje dowolny slice, którego typ elementu jest uporządkowany: liczby całkowite, zmiennoprzecinkowe i stringi. Sortuje w miejscu i nic nie zwraca.
Wynik:
[3 7 19 42 88]
[Bob Carl alice lisa]
true
2 true
Stringi są sortowane według bajtów, więc każda wielka litera ASCII trafia przed każdą małą. Rzadko tego oczekuje użytkownik przy imionach. Następna sekcja to naprawia.
Pakiet slices pojawił się w Go 1.21. Jego Sort to pattern-defeating quicksort: O(n log n), w miejscu i niestabilny.
Własna kolejność z SortFunc
slices.SortFunc przyjmuje funkcję porównującą func(a, b T) int. Zwróć liczbę ujemną, gdy a ma być pierwsze, dodatnią, gdy pierwsze ma być b, i zero, gdy są równe. Funkcja pomocnicza cmp.Compare zwraca dokładnie to dla typów uporządkowanych.
strings.ToLower wewnątrz porównania alokuje nowy string za każdym razem, gdy wejście ma wielką literę, a porównanie wykonuje się około n log n razy. Dla dużych slice'ów oblicz klucze małymi literami raz. Dla imion w innych językach (znaki diakrytyczne, reguły lokalne) użyj golang.org/x/text/collate, który jest poza biblioteką standardową.
Funkcja porównująca musi być spójna: jeśli mówi, że a jest przed b, musi też mówić, że b jest po a. Funkcja, która to łamie (na przykład zwraca -1 zawsze, gdy dwie wartości się różnią), daje źle posortowany slice bez żadnego błędu. Odejmowanie liczb (return a - b) wygląda zgrabnie, ale przepełnia się dla dużych wartości; użyj cmp.Compare.
Sortowanie struktur
Funkcja porównująca dostaje elementy, więc sortowanie struktur po polu to to samo wywołanie:
Pracownicy z równą pensją mogą tu wyjść w dowolnej kolejności, bo SortFunc nie jest stabilne. Jeśli potrzebujesz gwarantowanej kolejności, rozstrzygaj remisy kolejnymi polami albo użyj sortowania stabilnego.
Sortowanie po wielu polach
Najpierw porównaj najważniejsze pole i przejdź do następnego tylko przy remisie. cmp.Or (Go 1.22) zwraca pierwszy niezerowy argument, co zamienia to w jedną linię:
Wynik:
eng 150 Cy
eng 120 Ana
eng 120 Eve
sales 90 Bob
sales 90 Dee
Każde porównanie jest obliczane, nawet gdy pierwsze już rozstrzyga, bo to zwykłe argumenty. Przy porównywaniu pól kosztuje to niewiele. Gdy rozstrzyganie remisu jest drogie, napisz łańcuch if c := ...; c != 0 { return c } ręcznie.
Sortowanie stabilne
Sortowanie stabilne zachowuje pierwotną kolejność elementów, które są sobie równe. Ma to znaczenie, gdy dane wejściowe mają już sensowną kolejność, na przykład rekordy posortowane po czasie, które teraz grupujesz według użytkownika.
To wypisuje [{ana 2} {ana 4} {bob 1} {bob 3} {bob 5}]: w obrębie każdego użytkownika pierwotna kolejność zostaje zachowana. Sortowanie stabilne wykonuje więcej pracy, więc używaj go tylko wtedy, gdy kolejność równych elementów ma znaczenie.
Sortowanie mapy
Mapy nie mają kolejności. Aby pokazać mapę posortowaną po kluczu, posortuj jej klucze: slices.Sorted(maps.Keys(m)) (Go 1.23). Aby posortować po wartości, posortuj klucze funkcją porównującą, która odczytuje wartości:
Rozstrzyganie remisu po słowie ma znaczenie: bez niego chan i slice (oba 7) wypisywałyby się w innej kolejności przy każdym uruchomieniu, bo klucze wychodzą z mapy w losowej kolejności. Więcej na stronie o mapach.
Pakiet sort: sort.Slice i spółka
Przed Go 1.21 sortowanie odbywało się przez pakiet sort. Zobaczysz go w dużej ilości istniejącego kodu:
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 { ... })
Różnice, które warto znać:
sort.Slice | slices.SortFunc | |
|---|---|---|
| Funkcja dostaje | indeksy i, j | elementy a, b |
| Zwraca | bool (czy i jest mniejsze od j) | int (ujemny, zero, dodatni) |
| Bezpieczeństwo typów | przyjmuje any, używa refleksji | generyczna, sprawdzana podczas kompilacji |
| Szybkość | wolniejsza | szybsza |
Częsty błąd z sort.Slice to domknięcie na innym slice'ie niż ten, który jest sortowany, bo funkcja less indeksuje według pozycji. SortFunc nie może mieć tego błędu, bo przekazuje ci elementy.
Typ sort.Interface (Len, Less, Swap) to najstarsza forma. Nadal służy do sortowania danych, które nie są pojedynczym slice'em, na przykład dwóch równoległych slice'ów, które muszą przesuwać się razem. Od Go 1.22 sort.Ints, sort.Strings i sort.Float64s po prostu wywołują slices.Sort.
Typowe błędy
- Oczekiwanie, że
Sortzwróci posortowany slice. Sortuje w miejscu i nic nie zwraca. Użyjslices.Sorted(slices.Values(s)), jeśli chcesz nowy posortowany slice, albo najpierwslices.Clone. - Założenie, że równe elementy zachowają kolejność. Obiecują to tylko warianty
Stable. - Odejmowanie w porównaniu.
a - bsię przepełnia. Użyjcmp.Compare. - Sortowanie liczb zmiennoprzecinkowych z NaN.
cmp.Comparestawia NaN przed każdą inną wartością, co zachowuje spójność sortowania. Ręcznie napisane porównaniea < btego nie robi.
Najczęściej zadawane pytania
Jak posortować slice w Go?
Dla liczb i stringów wywołaj slices.Sort(s) (Go 1.21). Sortuje w miejscu, rosnąco. Dla innych typów albo innej kolejności użyj slices.SortFunc(s, func(a, b T) int { ... }), gdzie funkcja zwraca liczbę ujemną, jeśli a ma być pierwsze, dodatnią, jeśli pierwsze ma być b, i zero, gdy są równe.
Jak posortować slice malejąco w Go?
Zamień argumenty w porównaniu: slices.SortFunc(s, func(a, b int) int { return cmp.Compare(b, a) }). Albo posortuj rosnąco, a potem wywołaj slices.Reverse(s).
Jak posortować slice struktur po kilku polach w Go?
Porównaj pierwsze pole i przejdź do następnego tylko wtedy, gdy są równe. Dokładnie to robi cmp.Or (Go 1.22): return cmp.Or(cmp.Compare(a.Dept, b.Dept), cmp.Compare(b.Salary, a.Salary), strings.Compare(a.Name, b.Name)) zwraca pierwszy niezerowy wynik.
Czym różni się sort.Slice od slices.SortFunc?
sort.Slice(s, func(i, j int) bool) to starsze API: przyjmuje funkcję less działającą na indeksach i zamienia elementy przez refleksję. slices.SortFunc(s, func(a, b T) int) jest generyczna, sprawdzana przez kompilator, dostaje bezpośrednio elementy i jest szybsza. Nowy kod powinien używać pakietu slices; sort.Slice nadal często występuje w kodzie napisanym przed Go 1.21.