Каков самый короткий способ просто отсортировать массив структур с помощью (произвольных) имен полей?
У меня была проблема, когда у меня был массив структур, например
package main
import "log"
type Planet struct {
Name string `json:"name"`
Aphelion float64 `json:"aphelion"` // in million km
Perihelion float64 `json:"perihelion"` // in million km
Axis int64 `json:"Axis"` // in km
Radius float64 `json:"radius"`
}
func main() {
var mars = new(Planet)
mars.Name = "Mars"
mars.Aphelion = 249.2
mars.Perihelion = 206.7
mars.Axis = 227939100
mars.Radius = 3389.5
var earth = new(Planet)
earth.Name = "Earth"
earth.Aphelion = 151.930
earth.Perihelion = 147.095
earth.Axis = 149598261
earth.Radius = 6371.0
var venus = new(Planet)
venus.Name = "Venus"
venus.Aphelion = 108.939
venus.Perihelion = 107.477
venus.Axis = 108208000
venus.Radius = 6051.8
planets := [...]Planet{*mars, *venus, *earth}
log.Println(planets)
}
Предположим, вы хотите отсортировать его по Axis
. Как вы это делаете?
(Примечание: я видел http://golang.org/pkg/sort/, и, похоже, он работает, но я должен добавить около 20 строк для простой сортировки по очень простой ключ. У меня есть фон python, где он так же прост, как sorted(planets, key=lambda n: n.Axis)
- есть ли что-то подобное в Go?)
Ответы
Ответ 1
ОБНОВЛЕНИЕ: Этот ответ относится к более старым версиям go
. Для Go 1.8 и новее см. Ответ AndreKR ниже.
Если вы хотите что-то менее подробное, чем стандартный пакет sort
библиотеки, вы можете использовать сторонний пакет github.com/bradfitz/slice
. Он использует некоторые приемы для генерации методов Len
и Swap
необходимых для сортировки вашего среза, поэтому вам нужно только предоставить метод Less
.
С этим пакетом вы можете выполнить сортировку с помощью:
slice.Sort(planets[:], func(i, j int) bool {
return planets[i].Axis < planets[j].Axis
})
Часть planets[:]
необходима для создания среза, охватывающего ваш массив. Если вы сделаете planets
срезом вместо массива, вы можете пропустить эту часть.
Ответ 2
Начиная с Go 1.8 теперь вы можете использовать sort.Slice для сортировки фрагмента:
sort.Slice(planets, func(i, j int) bool {
return planets[i].Axis < planets[j].Axis
})
Обычно нет смысла использовать массив вместо среза, но в вашем примере вы используете массив, поэтому вам нужно наложить его на срез (добавить [:]
), чтобы он работал с sort.Slice
sort.Slice(planets[:], func(i, j int) bool {
return planets[i].Axis < planets[j].Axis
})
Сортировка изменяет массив, поэтому, если вы действительно хотите, вы можете продолжать использовать массив вместо среза после сортировки.
Ответ 3
Начиная с Go 1.8, ответ @AndreKR является лучшим решением.
Вы можете реализовать тип коллекции, который реализует интерфейс сортировки.
Вот пример двух таких типов, которые позволяют вам сортировать по оси или по имени:
package main
import "log"
import "sort"
// AxisSorter sorts planets by axis.
type AxisSorter []Planet
func (a AxisSorter) Len() int { return len(a) }
func (a AxisSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a AxisSorter) Less(i, j int) bool { return a[i].Axis < a[j].Axis }
// NameSorter sorts planets by name.
type NameSorter []Planet
func (a NameSorter) Len() int { return len(a) }
func (a NameSorter) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a NameSorter) Less(i, j int) bool { return a[i].Name < a[j].Name }
type Planet struct {
Name string 'json:"name"'
Aphelion float64 'json:"aphelion"' // in million km
Perihelion float64 'json:"perihelion"' // in million km
Axis int64 'json:"Axis"' // in km
Radius float64 'json:"radius"'
}
func main() {
var mars Planet
mars.Name = "Mars"
mars.Aphelion = 249.2
mars.Perihelion = 206.7
mars.Axis = 227939100
mars.Radius = 3389.5
var earth Planet
earth.Name = "Earth"
earth.Aphelion = 151.930
earth.Perihelion = 147.095
earth.Axis = 149598261
earth.Radius = 6371.0
var venus Planet
venus.Name = "Venus"
venus.Aphelion = 108.939
venus.Perihelion = 107.477
venus.Axis = 108208000
venus.Radius = 6051.8
planets := []Planet{mars, venus, earth}
log.Println("unsorted:", planets)
sort.Sort(AxisSorter(planets))
log.Println("by axis:", planets)
sort.Sort(NameSorter(planets))
log.Println("by name:", planets)
}
Ответ 4
Вместо реализации Sort interface
on []Planet
вы можете реализовать тип, содержащий коллекцию и закрытие, которые будут выполнять сравнение. Вы должны обеспечить реализацию для закрытия сравнения для каждого свойства.
Этот метод, который я чувствую, лучше, чем реализация типа Sort для каждого свойства структуры.
Этот ответ почти разорван прямо из сортировки docs, поэтому я не могу с этим похвастаться
package main
import (
"log"
"sort"
)
type Planet struct {
Name string `json:"name"`
Aphelion float64 `json:"aphelion"` // in million km
Perihelion float64 `json:"perihelion"` // in million km
Axis int64 `json:"Axis"` // in km
Radius float64 `json:"radius"`
}
type By func(p1, p2 *Planet) bool
func (by By) Sort(planets []Planet) {
ps := &planetSorter{
planets: planets,
by: by,
}
sort.Sort(ps)
}
type planetSorter struct {
planets []Planet
by func(p1, p2 *Planet) bool
}
func (s *planetSorter) Len() int {
return len(s.planets)
}
func (s *planetSorter) Swap(i, j int) {
s.planets[i], s.planets[j] = s.planets[j], s.planets[i]
}
func (s *planetSorter) Less(i, j int) bool {
return s.by(&s.planets[i], &s.planets[j])
}
Как это назвать.
func main() {
/* Same code as in the question */
planets := []Planet{*mars, *venus, *earth}
By(func(p1, p2 *Planet) bool {
return p1.Name < p2.Name
}).Sort(planets)
log.Println(planets)
By(func(p1, p2 *Planet) bool {
return p1.Axis < p2.Axis
}).Sort(planets)
log.Println(planets)
}
Вот демонстрация
Ответ 5
Вот еще один способ уменьшить часть плиты котла. Отказ от ответственности использует защиту от отражений и потерь.
Вот демонстрация
Вся магия происходит в функции Prop
. Требуется свойство struct для сортировки и порядок, который вы хотите сортировать (восходящий, нисходящий) и возвращает функцию, которая будет выполнять сравнения.
package main
import (
"log"
"reflect"
"sort"
)
func test(planets []Planet) {
log.Println("Sort Name")
By(Prop("Name", true)).Sort(planets)
log.Println(planets)
log.Println("Sort Aphelion")
By(Prop("Aphelion", true)).Sort(planets)
log.Println(planets)
log.Println("Sort Perihelion")
By(Prop("Perihelion", true)).Sort(planets)
log.Println(planets)
log.Println("Sort Axis")
By(Prop("Axis", true)).Sort(planets)
log.Println(planets)
log.Println("Sort Radius")
By(Prop("Radius", true)).Sort(planets)
log.Println(planets)
}
func Prop(field string, asc bool) func(p1, p2 *Planet) bool {
return func(p1, p2 *Planet) bool {
v1 := reflect.Indirect(reflect.ValueOf(p1)).FieldByName(field)
v2 := reflect.Indirect(reflect.ValueOf(p2)).FieldByName(field)
ret := false
switch v1.Kind() {
case reflect.Int64:
ret = int64(v1.Int()) < int64(v2.Int())
case reflect.Float64:
ret = float64(v1.Float()) < float64(v2.Float())
case reflect.String:
ret = string(v1.String()) < string(v2.String())
}
if asc {
return ret
}
return !ret
}
}
type Planet struct {
Name string `json:"name"`
Aphelion float64 `json:"aphelion"` // in million km
Perihelion float64 `json:"perihelion"` // in million km
Axis int64 `json:"Axis"` // in km
Radius float64 `json:"radius"`
}
type By func(p1, p2 *Planet) bool
func (by By) Sort(planets []Planet) {
ps := &planetSorter{
planets: planets,
by: by, // The Sort method receiver is the function (closure) that defines the sort order.
}
sort.Sort(ps)
}
type planetSorter struct {
planets []Planet
by func(p1, p2 *Planet) bool // Closure used in the Less method.
}
// Len is part of sort.Interface.
func (s *planetSorter) Len() int { return len(s.planets) }
// Swap is part of sort.Interface.
func (s *planetSorter) Swap(i, j int) {
s.planets[i], s.planets[j] = s.planets[j], s.planets[i]
}
// Less is part of sort.Interface. It is implemented by calling the "by" closure in the sorter.
func (s *planetSorter) Less(i, j int) bool {
return s.by(&s.planets[i], &s.planets[j])
}
func main() {
test(dataSet())
}
func dataSet() []Planet {
var mars = new(Planet)
mars.Name = "Mars"
mars.Aphelion = 249.2
mars.Perihelion = 206.7
mars.Axis = 227939100
mars.Radius = 3389.5
var earth = new(Planet)
earth.Name = "Earth"
earth.Aphelion = 151.930
earth.Perihelion = 147.095
earth.Axis = 149598261
earth.Radius = 6371.0
var venus = new(Planet)
venus.Name = "Venus"
venus.Aphelion = 108.939
venus.Perihelion = 107.477
venus.Axis = 108208000
venus.Radius = 6051.8
return []Planet{*mars, *venus, *earth}
}