Qual è il modo più breve per ordinare semplicemente un array di strutture in base a nomi di campo (arbitrari)?


129

Ho appena avuto un problema in cui avevo un array di strutture, ad es

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

Diciamo che vuoi ordinarlo Axis. Come si fa a farlo?

(Nota: ho visto http://golang.org/pkg/sort/ e sembra funzionare, ma devo aggiungere circa 20 righe solo per un semplice ordinamento con una chiave molto semplice. Ho uno sfondo Python dove si trova semplice come sorted(planets, key=lambda n: n.Axis)- c'è qualcosa di simile semplice in Go?)


Qui un altro pacchetto github.com/patrickmn/sortutil di terze parti . Può eseguire l'ordinamento normale e anche l'ordinamento annidato. Qui cito dalla documentazione sulle prestazioni: "Sebbene sortutil sia conveniente, non batterà un ordinamento dedicato. Interfaccia in termini di prestazioni. Implementazione dell'ordinamento. Interfaccia per un tipo ByName che incorpora ad es. [] MyStruct e esegue l'ordinamento. (ByName {MySlice}) deve essere preso in considerazione quando sono richieste prestazioni elevate. "
Tutompita

Risposte:


63

AGGIORNAMENTO: questa risposta si riferisce alle versioni precedenti di go. Per Go 1.8 e versioni successive, vedere la risposta di AndreKR di seguito .


Se vuoi qualcosa di un po 'meno dettagliato del sortpacchetto della libreria standard , puoi usare il github.com/bradfitz/slicepacchetto di terze parti . Utilizza alcuni trucchi per generare i metodi Lene Swapnecessari per ordinare la tua fetta, quindi devi solo fornire un Lessmetodo.

Con questo pacchetto, puoi eseguire l'ordinamento con:

slice.Sort(planets[:], func(i, j int) bool {
    return planets[i].Axis < planets[j].Axis
})

La planets[:]parte è necessaria per produrre una fetta che copre il tuo array. Se crei planetsuno slice invece di un array potresti saltare quella parte.


28
Devo usare un pacchetto di terze parti per ordinare un array (a meno che non voglia scrivere una quantità incredibile di codice dettagliato)? Cosa c'è di sbagliato in questa lingua? Voglio dire ... È solo una specie! Nessuna magia nera.
Jendas

8
@jendas Go vuole essere semplice, non facile. Ruby è facile. Anche quando non sai esattamente come funziona qualcosa, puoi provare e funzionerà come previsto. Ma non osare cercare di capire le specifiche della lingua e costruire un interprete, o leggere il codice di rails mentre impari ruby. Andare è semplice. Ti consigliamo, dopo il tour, di leggere le specifiche della lingua, anche i principianti possono farlo. E possono leggere il codice più avanzato e ottenerlo. Perché è semplice.
kik

4
@kik Questo non ha senso. Semplice non significa senza caratteristiche. L'ordinamento è una delle funzionalità più importanti e fondamentali, ma allo stesso tempo semplici , che una libreria potrebbe avere. Golang ha una libreria standard per modelli html, hash crc32, stampanti, scanner ecc. Questo lo rende NON MENO semplice. Non avere alcun ordinamento nella tua libreria standard non è una questione di semplicità, è una questione di caratteristiche fondamentali mancanti che tutti i linguaggi considerano uno standard. Anche C ha una funzione di ordinamento. Smettila di essere così elitario con Golang e inizia a considerare che Golang potrebbe semplicemente sbagliarsi su questo (se davvero non ce l'aveva).
Eksapsy

319

A partire da Go 1.8 puoi ora usare sort.Slice per ordinare una fetta:

sort.Slice(planets, func(i, j int) bool {
  return planets[i].Axis < planets[j].Axis
})

Non c'è normalmente alcun motivo per utilizzare un array invece di una fetta, ma nel tuo esempio si stanno utilizzando una matrice, quindi bisogna sovrapporre con una fetta (add [:]) per farlo funzionare con sort.Slice:

sort.Slice(planets[:], func(i, j int) bool {
  return planets[i].Axis < planets[j].Axis
})

L'ordinamento cambia l'array, quindi se vuoi davvero puoi continuare a usare l'array invece della slice dopo l'ordinamento.


sort.Sliceè un po 'sorprendente. La lessfunzione accetta solo indici quindi deve (in questa risposta) utilizzare un planetsarray acquisito separatamente . Sembra che non ci sia nulla che imponga che lo slice ordinato e la lessfunzione operino sugli stessi dati. Perché funzioni, devi digitare planetstre volte (DRY).
Brent Bradburn

planets[:]è fondamentale. Ma non capisco perché. Funziona comunque.
ACCIAIO

@STEEL Di solito dovresti usare una slice invece di un array in primo luogo. Allora non hai bisogno [:].
AndreKR

37

A partire da Go 1.8, la risposta di @ AndreKR è la soluzione migliore.


È possibile implementare un tipo di raccolta che implementa l' interfaccia di ordinamento .

Ecco un esempio di due di questi tipi che ti consentono di ordinare per Asse o Nome:

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

Questa è esattamente la soluzione dettagliata che ho collegato, non è vero?
Martin Thoma

1
L'hai collegato mentre scrivevo questo. Mie scuse. Ma utilizzando solo gli strumenti standard, non esiste un modo più breve per farlo.
jimt

5

Puoi, invece di implementare l' Sort interfaceon []Planetche implementi su un tipo che contiene la raccolta e una chiusura che farà il confronto. Devi fornire l'implementazione per la chiusura del confronto per ogni proprietà.

Questo metodo credo sia migliore dell'implementazione di un tipo Sort per ogni proprietà della struttura.

Questa risposta è quasi strappata direttamente dai documenti di ordinamento, quindi non posso prendermi molto merito

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

Come chiamarlo.

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

Ecco una demo


3

Ecco un altro modo per ridurre parte della piastra della caldaia. Disclaimer, utilizza la sicurezza di tipo riflessione e perdite.

Ecco una demo

Tutta la magia avviene nella Propfunzione. Prende la proprietà struct su cui ordinare e l'ordine in cui si desidera ordinare (crescente, decrescente) e restituisce una funzione che eseguirà i confronti.

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}
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.