Сортировка встроенных типов
slices.Sort сортирует любой слайс с упорядоченным типом элементов: целые, числа с плавающей точкой и строки. Она сортирует на месте и ничего не возвращает.
Вывод:
[3 7 19 42 88]
[Bob Carl alice lisa]
true
2 true
Строки сортируются по байтам, поэтому каждая заглавная буква ASCII идёт раньше любой строчной. Для имён пользователи ждут совсем не этого. Следующий раздел это исправляет.
Пакет slices появился в Go 1.21. Его Sort это pattern-defeating quicksort: O(n log n), на месте и нестабильная.
Свой порядок через SortFunc
slices.SortFunc принимает функцию сравнения func(a, b T) int. Возвращайте отрицательное число, когда первым должен идти a, положительное, когда b, и ноль, когда они равны. Помощник cmp.Compare возвращает ровно это для упорядоченных типов.
strings.ToLower внутри сравнения создаёт новую строку всякий раз, когда во входе есть заглавная буква, а сравнение выполняется примерно n log n раз. Для больших слайсов вычислите ключи в нижнем регистре один раз. Для имён на других языках (диакритика, правила локали) используйте golang.org/x/text/collate, который не входит в стандартную библиотеку.
Функция сравнения должна быть согласованной: если она говорит, что a идёт перед b, то должна говорить, что b идёт после a. Функция, которая это нарушает (например, возвращает -1 всякий раз, когда значения различаются), даёт неправильно упорядоченный слайс без всякой ошибки. Вычитание целых (return a - b) выглядит изящно, но переполняется на больших значениях; используйте cmp.Compare.
Сортировка структур
Функция сравнения получает элементы, так что сортировка структур по полю это тот же вызов:
Сотрудники с равной зарплатой здесь могут выйти в любом порядке, потому что SortFunc нестабильна. Если нужен гарантированный порядок, разрешайте равенство дополнительными полями или используйте стабильную сортировку.
Сортировка по нескольким полям
Сначала сравните самое важное поле и переходите к следующему только при равенстве. cmp.Or (Go 1.22) возвращает свой первый ненулевой аргумент, и это превращает всё в одну строку:
Вывод:
eng 150 Cy
eng 120 Ana
eng 120 Eve
sales 90 Bob
sales 90 Dee
Вычисляются все сравнения, даже если решило первое, ведь это обычные аргументы. Для сравнения полей это почти ничего не стоит. Когда дополнительное сравнение дорогое, напишите цепочку if c := ...; c != 0 { return c } вручную.
Стабильная сортировка
Стабильная сортировка сохраняет исходный порядок элементов, которые при сравнении равны. Это важно, когда во входных данных уже есть осмысленный порядок, например записи отсортированы по времени, а теперь вы группируете их по пользователю.
Программа печатает [{ana 2} {ana 4} {bob 1} {bob 3} {bob 5}]: внутри каждого пользователя исходная последовательность сохраняется. Стабильная сортировка делает больше работы, поэтому используйте её, только когда порядок равных элементов важен.
Сортировка мапы
У мап нет порядка. Чтобы показать мапу, отсортированную по ключу, отсортируйте её ключи: slices.Sorted(maps.Keys(m)) (Go 1.23). Чтобы отсортировать по значению, сортируйте ключи сравнением, которое заглядывает в значения:
Дополнительное сравнение по слову важно: без него chan и slice (у обоих 7) печатались бы в разном порядке от запуска к запуску, потому что ключи выходят из мапы в случайном порядке. Подробнее на странице о мапах.
Пакет sort: sort.Slice и компания
До Go 1.21 сортировка шла через пакет sort. Вы увидите его во множестве существующего кода:
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 { ... })
Различия, которые стоит знать:
sort.Slice | slices.SortFunc | |
|---|---|---|
| Функция получает | индексы i, j | элементы a, b |
| Возвращает | bool (меньше ли i, чем j) | int (отрицательное, ноль, положительное) |
| Типобезопасность | принимает any, использует рефлексию | обобщённая, проверяется на этапе компиляции |
| Скорость | медленнее | быстрее |
Частый баг с sort.Slice: функция «меньше» замыкается на другой слайс, а не на тот, что сортируется, ведь она обращается по позиции. У SortFunc такого бага быть не может, потому что она отдаёт вам сами элементы.
Тип sort.Interface (Len, Less, Swap) это самая старая форма. Через него по-прежнему сортируют данные, которые не являются одним слайсом, например два параллельных слайса, которые должны переставляться вместе. Начиная с Go 1.22 sort.Ints, sort.Strings и sort.Float64s просто вызывают slices.Sort.
Частые ошибки
- Ожидание, что
Sortвернёт отсортированный слайс. Она сортирует на месте и ничего не возвращает. Если нужен новый отсортированный слайс, используйтеslices.Sorted(slices.Values(s))или сначалаslices.Clone. - Расчёт на то, что равные элементы сохранят порядок. Это обещают только варианты
Stable. - Сравнение вычитанием.
a - bпереполняется. Используйтеcmp.Compare. - Сортировка float с NaN.
cmp.Compareставит NaN перед всеми остальными значениями, и сортировка остаётся согласованной. Самописное сравнениеa < bтак не умеет.
Часто задаваемые вопросы
Как отсортировать слайс в Go?
Для чисел и строк вызовите slices.Sort(s) (Go 1.21). Она сортирует на месте по возрастанию. Для всего остального или другого порядка используйте slices.SortFunc(s, func(a, b T) int { ... }), где функция возвращает отрицательное число, если a идёт первым, положительное, если первым идёт b, и ноль, если они равны.
Как отсортировать слайс по убыванию в Go?
Поменяйте аргументы местами в сравнении: slices.SortFunc(s, func(a, b int) int { return cmp.Compare(b, a) }). Или отсортируйте по возрастанию, а затем вызовите slices.Reverse(s).
Как отсортировать слайс структур по нескольким полям в Go?
Сравните первое поле и переходите к следующему, только если оно равно. Ровно это делает 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)) возвращает первый ненулевой результат.
Чем sort.Slice отличается от slices.SortFunc?
sort.Slice(s, func(i, j int) bool) это старый API: он принимает функцию «меньше» по индексам и меняет элементы местами через рефлексию. slices.SortFunc(s, func(a, b T) int) обобщённая, проверяется на этапе компиляции, получает сами элементы и работает быстрее. Новый код должен использовать пакет slices; sort.Slice по-прежнему часто встречается в коде, написанном до Go 1.21.