web-dev-qa-db-ja.com

複数の並べ替えパラメーターで構造体を並べ替える方法は?

メンバーの配列/スライスがあります:

type Member struct {
    Id int
    LastName string
    FirstName string
}

var members []Member

私の質問は、LastNameで並べ替えてからFirstNameで並べ替える方法です。

23
Melvin

sort.Slice (Go 1.8以降で使用可能)または sort.Sort 関数を使用して、値のスライスをソートします。

両方の機能により、アプリケーションは、1つのスライス要素が別のスライス要素よりも小さいかどうかをテストする機能を提供します。姓、次に名で並べ替えるには、姓と名を比較します。

if members[i].LastName < members[j].LastName {
    return true
}
if members[i].LastName > members[j].LastName {
    return false
}
return members[i].FirstName < members[j].FirstName

Less関数は、sort.Sliceで匿名関数を使用して指定されます。

var members []Member
sort.Slice(members, func(i, j int) bool {
    if members[i].LastName < members[j].LastName {
        return true
    }
    if members[i].LastName > members[j].LastName {
        return false
    }
    return members[i].FirstName < members[j].FirstName
})

Less関数は、sort.Sort関数で interface を使用して指定されます。

type byLastFirst []Member

func (members byLastFirst) Len() int           { return len(members) }
func (members byLastFirst) Swap(i, j int)      { members[i], members[j] = members[j], members[i] }
func (members byLastFirst) Less(i, j int) bool { 
    if members[i].LastName < members[j].LastName {
       return true
    }
    if members[i].LastName > members[j].LastName {
       return false
    }
    return members[i].FirstName < members[j].FirstName
}

sort.Sort(byLastFirst(members))

パフォーマンス分析でソートがホットスポットであることが示されていない限り、アプリケーションに最も便利な機能を使用してください。

43
Cerise Limón

新しいsort.Slice関数を次のように使用します。

sort.Slice(members, func(i, j int) bool {
    switch strings.Compare(members[i].FirstName, members[j].FirstName) {
    case -1:
        return true
    case 1:
        return false
    }
    return members[i].LastName > members[j].LastName
})

またはそのようなもの。

16
abourget

別のパターン、私はわずかにきれいだと思う:

if members[i].LastName != members[j].LastName {
    return members[i].LastName < members[j].LastName
}

return members[i].FirstName < members[j].FirstName
5
LVB

このために私がなんとか書いた、最短でまだ理解可能なコードは次のとおりです。

package main

import (
    "fmt"
    "sort"
)

type Member struct {
    Id        int
    LastName  string
    FirstName string
}

func sortByLastNameAndFirstName(members []Member) {
    sort.SliceStable(members, func(i, j int) bool {
        mi, mj := members[i], members[j]
        switch {
        case mi.LastName != mj.LastName:
            return mi.LastName < mj.LastName
        default:
            return mi.FirstName < mj.FirstName
        }
    })
}

switchステートメントを使用したパターンは、3つ以上のソート基準に簡単に拡張できますが、読み取り可能なほど短いものです。

プログラムの残りの部分は次のとおりです。

func main() {
    members := []Member{
        {0, "The", "quick"},
        {1, "brown", "fox"},
        {2, "jumps", "over"},
        {3, "brown", "grass"},
        {4, "brown", "grass"},
        {5, "brown", "grass"},
        {6, "brown", "grass"},
        {7, "brown", "grass"},
        {8, "brown", "grass"},
        {9, "brown", "grass"},
        {10, "brown", "grass"},
        {11, "brown", "grass"},
    }

    sortByLastNameAndFirstNameFunctional(members)

    for _, member := range members {
        fmt.Println(member)
    }
}

まったく異なるアイデアですが、同じAPI

LastNameフィールドとFirstNameフィールドに複数回言及するのを避けたい場合、およびijを混在させたくない場合(これは常に発生する可能性があります)、私は少し遊んで、基本的な考え方は次のとおりです:

func sortByLastNameAndFirstNameFunctional(members []Member) {
    NewSorter().
        AddStr(member -> member.LastName).
        AddStr(member -> member.FirstName).
        AddInt(member -> member.Id).
        SortStable(members)
}

Goは、匿名関数を作成するための->演算子をサポートせず、Javaなどのジェネリックも持たないため、構文上のオーバーヘッドが少し必要です。

func sortByLastNameAndFirstNameFunctional(members []Member) {
    NewSorter().
        AddStr(func(i interface{}) string { return i.(Member).LastName }).
        AddStr(func(i interface{}) string { return i.(Member).FirstName }).
        AddInt(func(i interface{}) int { return i.(Member).Id}).
        SortStable(members)
}

実装とAPIはinterface{}とリフレクションを使用すると少しいですが、各フィールドに1回しか言及しておらず、アプリケーションコードはインデックスijを誤って混在させることはありません。対処しません。

このAPIは、Javaの Comparator.comparing の精神で設計しました。

上記のソーターのインフラストラクチャコードは次のとおりです。

type Sorter struct{ keys []Key }

func NewSorter() *Sorter { return new(Sorter) }

func (l *Sorter) AddStr(key StringKey) *Sorter { l.keys = append(l.keys, key); return l }
func (l *Sorter) AddInt(key IntKey) *Sorter    { l.keys = append(l.keys, key); return l }

func (l *Sorter) SortStable(slice interface{}) {
    value := reflect.ValueOf(slice)
    sort.SliceStable(slice, func(i, j int) bool {
        si := value.Index(i).Interface()
        sj := value.Index(j).Interface()
        for _, key := range l.keys {
            if key.Less(si, sj) {
                return true
            }
            if key.Less(sj, si) {
                return false
            }
        }
        return false
    })
}

type Key interface {
    Less(a, b interface{}) bool
}

type StringKey func(interface{}) string

func (k StringKey) Less(a, b interface{}) bool  { return k(a) < k(b) }

type IntKey func(interface{}) int

func (k IntKey) Less(a, b interface{}) bool  { return k(a) < k(b) }
2
Roland Illig

受け入れられた答えのもう少し簡潔な実装を次に示します。

package main

import (
    "fmt"
    "sort"
)

type Member struct {
    FirstName string
    LastName  string
}

func main() {
    members := []Member{
        {"John", "Doe"},
        {"Jane", "Doe"},
        {"Mary", "Contrary"},
    }

    fmt.Println(members)

    sort.Slice(members, func(i, j int) bool {
        return members[i].LastName < members[j].LastName || members[i].FirstName < members[j].FirstName
    })

    fmt.Println(members)
}

実行すると、次の出力が印刷されます。

[{John Doe} {Jane Doe} {Mary Contrary}]
[{Mary Contrary} {Jane Doe} {John Doe}]

これは予想どおりです。メンバーは姓の昇順で印刷され、同点の場合は名の順に印刷されます。

0
Kurt Peek

これは非常に役に立ちました。構造体のスライスを並べ替える必要があり、ここで答えを見つけました。実際に、3回ソートに拡張しました。これだけのソートはランタイムの目的には最適ではありませんが、状況によっては特に役立ちます。代替手段が、メンテナンスや変更が困難なコードにつながり、高速なランタイムが重要でない場合に特に役立ちます。

sort.Slice(servers, func(i, j int) bool {
        if servers[i].code < servers[j].code {
            return true
        } else if servers[i].code > servers[j].code {
            return false
        } else {
            if servers[i].group < servers[j].group {
                return true
            } else {
                if servers[i].group > servers[j].group {
                    return false
                }
            }
        }
        return servers[i].IDGroup < servers[j]. IDGroup
    })

このコードは、最初にコードで、次にグループで、次にIDGroupでソートされます。

0
Douglas Adolph