Как проще всего отсортировать срез структур по произвольному полю в Go?
Краткий ответ: используйте sort.Slice и анонимные функции
Да, в Go есть элегантный способ — функция sort.Slice из стандартного пакета sort. Она появилась в Go 1.8 и позволяет сортировать срез без создания отдельного типа. Вам нужно только передать функцию-компаратор, которая сравнивает два элемента по нужному полю.
Вот как это выглядит для вашего примера:
package main
import (
"fmt"
"sort"
)
type Person struct {
Name string
Age int
}
func main() {
people := []Person{
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35},
}
// Сортировка по имени (алфавитный порядок)
sort.Slice(people, func(i, j int) bool {
return people[i].Name < people[j].Name
})
fmt.Println("По имени:", people)
// Сортировка по возрасту (по возрастанию)
sort.Slice(people, func(i, j int) bool {
return people[i].Age < people[j].Age
})
fmt.Println("По возрасту:", people)
}
Вывод:
По имени: [{Alice 30} {Bob 25} {Charlie 35}]
По возрасту: [{Bob 25} {Alice 30} {Charlie 35}]
Как это работает
sort.Sliceпринимает два аргумента: сам срез (какinterface{}) и функцию-компаратор.- Компаратор
func(i, j int) boolдолжен вернутьtrue, если элемент с индексомiдолжен стоять раньше элемента с индексомj. - Внутри компаратора вы обращаетесь к нужному полю структуры и сравниваете его.
- Для обратной сортировки (по убыванию) просто поменяйте знак:
people[i].Age > people[j].Age.
Сортировка по нескольким полям
Если нужно отсортировать сначала по одному полю, а при равенстве — по другому, просто добавьте проверку:
sort.Slice(people, func(i, j int) bool {
if people[i].Age != people[j].Age {
return people[i].Age < people[j].Age // сначала по возрасту
}
return people[i].Name < people[j].Name // потом по имени
})
Почему это лучший подход
- Минимум кода — не нужно писать отдельный тип и три метода.
- Гибкость — можно сортировать по любому полю, даже по вычисляемому значению.
- Производительность —
sort.Sliceработает так же эффективно, как и реализация через интерфейс. - Безопасность типов — компаратор работает с конкретным типом, ошибки на этапе компиляции.
Важное замечание
Начиная с Go 1.21 появилась функция slices.SortFunc из пакета slices, которая делает то же самое, но с более строгой типизацией и чуть лучшей производительностью. Пример:
import "slices"
slices.SortFunc(people, func(a, b Person) int {
if a.Age < b.Age {
return -1
} else if a.Age > b.Age {
return 1
}
return 0
})
Но для простоты и совместимости с более старыми версиями Go я рекомендую sort.Slice — он идеально подходит для 95% задач сортировки структур.
Альтернатива: сортировка через sort.Interface с дженериками
Если вы работаете с Go 1.18+ и у вас много разных структур, которые нужно сортировать по разным полям, можно написать универсальную обёртку с использованием дженериков. Это более "инженерный" подход, который пригодится в больших проектах.
Пример:
package main
import (
"fmt"
"sort"
)
type Person struct {
Name string
Age int
}
// Slice сортирует срез по переданной функции-компаратору
func Slice[T any](s []T, less func(a, b T) bool) {
sort.Slice(s, func(i, j int) bool {
return less(s[i], s[j])
})
}
func main() {
people := []Person{
{"Alice", 30},
{"Bob", 25},
{"Charlie", 35},
}
// Сортировка по имени
Slice(people, func(a, b Person) bool {
return a.Name < b.Name
})
fmt.Println(people)
}
Этот подход:
- Даёт читаемый код с явным именем функции
Slice. - Легко переиспользуется для любых типов.
- Позволяет вынести компараторы в отдельные переменные или функции.
Однако для разовой сортировки проще и быстрее использовать sort.Slice напрямую — не нужно писать вспомогательные функции. Выбирайте этот вариант, если в вашем проекте сортировка структур встречается десятки раз.