Який найкоротший спосіб просто сортувати масив структур за (довільними) назвами полів?


129

У мене просто була проблема, коли у мене був масив структур, наприклад

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 рядків для простого сортування за допомогою дуже простого клавіші. У мене є пітон фон, де він є так просто, як sorted(planets, key=lambda n: n.Axis)- чи є щось подібне просте в Go?)


Ось ще один сторонній пакет github.com/patrickmn/sortutil . Він може робити звичайне сортування, а також вкладене сортування. Тут я цитую документацію щодо продуктивності: "Хоча sortutil зручний, він не буде бити виділений сорт. Інтерфейс з точки зору продуктивності. Сортування впровадження. Інтерфейс для типу ByName, який вбудовується, наприклад, [] MyStruct і робить sort.Sort (ByName {MySlice}) слід враховувати, коли потрібна висока продуктивність. "
Тутомпіта

Відповіді:


63

ОНОВЛЕННЯ: Ця відповідь стосується старих версій 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фрагмент замість масиву, ви можете пропустити цю частину.


28
Мені потрібно скористатися стороннім пакетом для сортування масиву (якщо я не хочу написати неймовірну кількість багатослівного коду)? Що з цією мовою? Я маю на увазі ... Це просто сортування! Ніякої чорної магії.
Jendas

8
@jendas Go має бути простим, а не простим. Рубі легко. Навіть коли ви точно не знаєте, як щось працює, ви можете спробувати, і це буде працювати, як очікувалося. Але ви не наважуєтесь розібратися в специфікаціях мови та побудувати інтерпретатора чи прочитати код рейки під час вивчення рубіну. Їхати просто. Після туру радимо прочитати мовні характеристики - навіть початківці можуть. І вони можуть прочитати найдосконаліший код і отримати його. Тому що це просто.
kik

4
@kik Це не має сенсу. Просте не означає безхарактерне. Сортування - одна з найважливіших та найважливіших, але простих функцій, яку може мати бібліотека. Golang має стандартну бібліотеку для HTML-шаблонів, хеш-файлів crc32, принтерів, сканерів тощо. Це робить її НЕ МЕНШЕ простішою. Відсутність сортування у вашій стандартній бібліотеці не є справою, це питання про відсутність основних функцій, які всі мови вважають стандартними. Навіть С має функцію сортування. Перестаньте бути настільки елітарними з Golang і почніть вважати, що Golang може просто помилятися на цьому (якщо він насправді цього не мав).
Ексапсія

319

Станом на 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
})

Сортування змінює масив, тому якщо ви дійсно хочете, ви можете продовжувати використовувати масив замість фрагмента після сортування.


sort.Sliceє свого роду дивно. lessФункція приймає тільки індекси тому він повинен (в цій відповіді) використовувати окремо захоплений planetsмасив. Здається, нічого не переконливе, що відсортований фрагмент і lessфункціонують на одних і тих же даних. Щоб це працювало, вам потрібно набрати planetsтри рази (DRY).
Брент Бредберн

planets[:]має вирішальне значення. Але я не розумію, чому. Працює, хоча.
СТАЛЬ

@STEEL Зазвичай ви повинні використовувати фрагмент замість масиву в першу чергу. Тоді вам не потрібно [:].
AndreKR

37

На 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)
}

Це саме багатослівне рішення, яке я пов’язав, чи не так?
Мартін Тома

1
Ви пов’язали це, поки я писав це. Мої вибачення. Але, використовуючи лише стандартні інструменти, немає більш короткого способу зробити це.
jimt

5

Ви можете замість того, щоб реалізовувати функцію Sort interfaceon []Planeton, що містить колекцію та закриття, яке буде робити порівняння. Ви повинні надати реалізацію для порівняння закриття для кожного ресурсу.

Я вважаю, що цей метод є кращим, ніж реалізація типу Сортування для кожної властивості структури.

Ця відповідь майже зірвана прямо з сортувальних документів, тому я не можу брати на це велику заслугу

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)
}

Ось демонстрація


3

Ось ще один спосіб зменшити частину плити котла. Відмова від відповідальності, вона використовує безпеку типу відображення та втрат.

Ось демонстрація

Вся магія відбувається у Propфункції. Для сортування потрібне властивість struktur та порядок її сортування (за зростанням, убуванням) та повертає функцію, яка виконуватиме порівняння.

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's 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}
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.