(任意の)フィールド名で構造体の配列を単純にソートする最も簡単な方法は何ですか?


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行を追加する必要があります。Pythonの背景があり、と同じくらい単純sorted(planets, key=lambda n: n.Axis)です-Goにも同様の単純なものはありますか?)


ここに別のサードパーティのgithub.com/patrickmn/sortutilパッケージがあります。通常のソートとネストされたソートを行うことができます。ここでは、パフォーマンスに関するドキュメントから引用します。「sortutilは便利ですが、専用のソートに勝るものはありません。パフォーマンスの面でのインターフェースです。ソートの実装。たとえば、[] MyStructを埋め込んでsort.Sortを実行するタイプByNameのインターフェース。 (ByName {MySlice})は、高いパフォーマンスが必要な場合に検討する必要があります。」
Tutompita 2016年

回答:


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は単純ではなく、簡単であることが意図されています。Rubyは簡単です。何かが正確に機能していない場合でも、試すことができ、期待どおりに機能します。しかし、あえて言語の仕様を理解してインタープリターを作成したり、Rubyの学習中にRailsのコードを読んだりしないでください。囲碁は簡単です。ツアーの後は、言語仕様を読むことをお勧めします-初心者でも読むことができます。そして、彼らは最も高度なコードを読んでそれを手に入れることができます。シンプルだから。
kik 2016

4
@kik意味がありません。シンプルは、機能がないという意味ではありません。ソートは、ライブラリが持つことができる最も重要で基本的な、しかもシンプルな機能の1つです。Golangには、htmlテンプレート、crc32ハッシュ、プリンター、スキャナーなどのための標準ライブラリーがあります。標準ライブラリで並べ替えを行わないことは単純さの問題ではなく、すべての言語が標準と見なしている基本機能が欠落していることの問題です。Cにもソート機能があります。Golangでそれほどエリート主義的であるのをやめ、Golangがこの1つでは間違っていた可能性があると考え始めます(実際にそれがなかった場合)。
Eksapsy

317

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関数が同じデータで動作していることを強制するものは何もないようです。これを機能させるには、planets3回入力する必要があります(DRY)。
ブレントブラッドバーン、

planets[:]重要です。しかし、その理由はわかりません。でも動作します。
STEEL

@STEEL通常、最初に配列の代わりにスライスを使用する必要があります。その後、あなたは必要ありません[:]
AndreKR

37

Go 1.8以降、@ AndreKRの答えがより良いソリューションです。


ソートインターフェイスを実装するコレクション型を実装できます

以下は、AxisまたはNameのいずれかでソートできる2つのタイプのです。

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

これはまさに私がリンクした冗長なソリューションですよね。
Martin Thoma

1
私がこれを書いている間にあなたはそれをリンクしました。謝罪いたします。しかし、標準のツールだけを使用して、これを行うためのより短い方法はありません。
jimt 2015年

5

することができ、代わりに実装するためのあなたSort interface[]Planet、あなたのコレクションとの比較を行いますクロージャを含む型に実装します。各プロパティの比較クロージャーの実装を提供する必要があります。

この方法は、構造体の各プロパティにSortタイプを実装するよりも優れていると感じています。

この答えはソートドキュメントからほとんど引き裂かれているので、あまり信用できません

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関数で発生します。これは、並べ替える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'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}
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.