Bir yapı dizisini (keyfi) alan adlarına göre basitçe sıralamanın en kısa yolu nedir?


130

Bir dizi yapıma sahip olduğum bir sorun yaşadım, örneğin

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

Sıralamak istediğinizi varsayalım Axis. Bunu nasıl yaptın?

(Not: http://golang.org/pkg/sort/ gördüm ve işe yarıyor gibi görünüyor, ancak çok basit bir anahtarla basit sıralama için yaklaşık 20 satır eklemem gerekiyor. Olduğu yerde bir python arka planım var kadar basit sorted(planets, key=lambda n: n.Axis)- Go'da benzer basit bir şey var mı?)


Burada başka bir üçüncü taraf github.com/patrickmn/sortutil paketi. Normal sıralama ve ayrıca iç içe sıralama yapabilir. Burada performansla ilgili dokümantasyondan alıntı yapıyorum: "sortutil uygun olsa da, özel bir sıralamayı geçmeyecek. Performans açısından arayüz. Sıralama uygulama. Örneğin [] MyStruct ve sort.Sort'u içeren bir tür ByName için arayüz (ByName {MySlice}), yüksek performans gerektiğinde dikkate alınmalıdır. "
Tutompita

Yanıtlar:


63

GÜNCELLEME: Bu yanıt, eski sürümleriyle ilgilidir go. Go 1.8 ve daha yeni sürümler için aşağıdaki AndreKR'nin cevabına bakın .


Standart kitaplık sortpaketinden biraz daha az ayrıntılı bir şey istiyorsanız , üçüncü taraf github.com/bradfitz/slicepaketini kullanabilirsiniz. Diliminizi sıralamak için gereken Lenve Swapyöntemlerini oluşturmak için bazı hileler kullanır , bu nedenle yalnızca bir Lessyöntem sağlamanız gerekir .

Bu paket ile sıralamayı şu şekilde gerçekleştirebilirsiniz:

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

planets[:]Bölüm dizinizi kapsayan bir dilim üretmek için gereklidir. Eğer yaparsanız planetsbir dizinin yerine bir dilim sen o kısmını atlamak olabilir.


28
Bir diziyi sıralamak için üçüncü taraf paketi kullanmalıyım (inanılmaz miktarda ayrıntılı kod yazmak istemiyorsam)? Bu dilin nesi var? Yani ... Bu sadece bir çeşit! Kara büyü yok.
Jendas

8
@jendas Go'nun amacı basittir, kolay değil. Ruby kolaydır. Bir şeyin nasıl çalıştığını tam olarak bilmeseniz bile, deneyebilirsiniz ve beklendiği gibi çalışacaktır. Ancak, Ruby'yi öğrenirken dilin özelliklerini anlamaya ve bir yorumlayıcı oluşturmaya veya rayların kodunu okumaya cesaret edemezsiniz. Gitmek basit. Turdan sonra dil özelliklerini okumanız tavsiye edilir - yeni başlayanlar bile okuyabilir. Ve en gelişmiş kodu okuyabilir ve alabilirler. Çünkü çok basit.
kik

4
@kik Bu hiç mantıklı değil. Basit, özelliksiz demek değildir. Sıralama, bir kitaplığın sahip olabileceği en önemli ve temel ancak basit özelliklerden biridir. Golang'ın html şablonları, crc32 karmaları, yazıcılar, tarayıcılar vb. İçin standart bir kitaplığı vardır. Standart kitaplığınızda sıralamanın olmaması basitlik meselesi değildir, tüm dillerin bir standart olarak kabul ettiği temel özelliklerin eksik olmasıdır. C bile bir Sıralama işlevine sahiptir. Golang ile bu kadar elitist olmayı bırakın ve Golang'ın bu konuda yanlış olabileceğini düşünmeye başlayın (eğer gerçekten yoksa).
Eksapsy

320

Go 1.8'den itibaren artık bir dilimi sıralamak için sort.Slice'ı kullanabilirsiniz :

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

Orada bir dilim yerine bir dizi kullanmak için bir sebep normalde, ancak örnekte sen edilir bir dilim (eklenti ile bindirmek zorunda, bir dizi kullanarak [:]) onunla çalışması için sort.Slice:

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

Sıralama diziyi değiştirir, böylece gerçekten istiyorsanız, sıralamanın ardından dilim yerine diziyi kullanmaya devam edebilirsiniz.


sort.Slicebiraz şaşırtıcı. less(Bu yanıtında) ayrı-yakalanan kullanmak zorunda böylece işlev yalnızca indeksleri alır planetsdizi. Sıralanan dilim ve lessişlevin aynı veriler üzerinde çalıştığını zorlayan hiçbir şey yok gibi görünüyor . Bunun çalışması için planetsüç kez (KURU) yazmanız gerekir .
Brent Bradburn

planets[:]çok önemlidir. Ama nedenini anlamıyorum. Yine de çalışıyor.
ÇELİK

@STEEL Genellikle ilk etapta bir dizi yerine bir dilim kullanmalısınız. O zaman ihtiyacın yok [:].
AndreKR

37

Go 1.8 itibariyle, @ AndreKR'nin cevabı daha iyi bir çözümdür.


Sıralama arayüzünü uygulayan bir koleksiyon türü uygulayabilirsiniz .

Eksen veya Ad'a göre sıralamanıza izin veren bu tür iki türe bir örnek :

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

Bu tam olarak bağladığım ayrıntılı çözüm, değil mi?
Martin Thoma

1
Ben bunu yazarken bağlamıştın. Özür dilerim. Ancak sadece standart araçları kullanarak bunu yapmanın daha kısa bir yolu yoktur.
jimt

5

Sen, yerine uygulanmasının edebilirsiniz Sort interfaceüzerinde []Planetsizin toplama ve karşılaştırma yapacak bir kapatma içeren bir türüne uygulamak. Her özellik için karşılaştırma kapanışı için uygulama sağlamanız gerekir.

Bence bu yöntem, yapının her özelliği için bir Sıralama türü uygulamaktan daha iyi.

Bu cevap neredeyse sıralama belgelerinden koparıldı, bu yüzden çok fazla itibar kazanamam

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

Nasıl adlandırılır.

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

İşte bir Demo


3

İşte kazan plakasının bir kısmını azaltmanın başka bir yolu. Sorumluluk reddi, yansıma ve kayıp tipi güvenliği kullanır.

İşte bir Demo

Tüm sihir Propişlevde gerçekleşir. Sıralamak için struct özelliğini ve sıralamak istediğiniz sırayı (artan, azalan) alır ve karşılaştırmaları gerçekleştirecek bir işlev döndürür.

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}
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.