web-dev-qa-db-fra.com

Golang: Comment trier une structure avec plusieurs paramètres de tri?

[UDPATE] C'est ma faute. J'aurais dû lire les docs plus attentivement que de poser cette question tout de suite. Ma faute. 

J'ai un tableau/tranche de membres:

type Member struct {
    Id int
    LastName string
    FirstName string
}

var members []Member

Ma question est de savoir comment les trier par LastName et ensuite par FirstName.

Toute aide est appréciée merci.

19
Melvin

Utilisez la fonction sort.Slice (disponible depuis Go 1.8) ou la fonction sort.Sort pour trier une tranche de valeurs.

Avec les deux fonctions, l'application fournit une fonction qui teste si un élément de tranche est inférieur à un autre élément de tranche. Pour trier par nom de famille, puis par prénom, comparez le nom de famille et le prénom:

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

La fonction less est spécifiée à l'aide d'une fonction anonyme avec 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
})

La fonction less est spécifiée avec une interface - avec la fonction sort.Sort:

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

Sauf si l'analyse des performances montre que le tri est un point chaud, utilisez la fonction la plus pratique pour votre application.

31
ThunderCat

Utilisez la nouvelle fonction sort.Slice en tant que telle:

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

ou quelque chose comme ça.

14
abourget

Un autre motif, que je trouve légèrement plus propre:

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

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

Le code le plus court et le plus compréhensible que j'ai réussi à écrire est le suivant:

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

Le modèle utilisant l'instruction switch s'étend facilement à plus de deux critères de tri et reste suffisamment court pour être lu.

Voici le reste du programme:

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

Une idée complètement différente, mais même API

Si vous voulez éviter de mentionner les champs LastName et FirstName plusieurs fois et si vous voulez éviter de mélanger i et j (ce qui peut arriver tout le temps), j'ai joué un peu et l'idée de base est la suivante:

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

Puisque Go ne prend pas en charge l'opérateur -> pour la création de fonctions anonymes et qu'il ne possède pas non plus de médicaments génériques tels que Java, un peu de surcharge syntaxique est nécessaire:

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

L’implémentation, ainsi que l’API, est un peu moche en utilisant interface{} et réflexion, mais elle ne mentionne chaque champ qu’une fois, et le code de l’application n’a aucune chance de mélanger accidentellement les index i et j car il ne les traite pas .

J'ai conçu cette API dans l'esprit de Comparator.comparing de Java.

Le code d'infrastructure pour le trieur ci-dessus est:

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) }
1
Roland Illig

Cela a été très utile. Je devais trier une tranche de structs et j'ai trouvé ma réponse ici. En fait, je l'ai étendu au triplement. Bien que ce tri ne soit pas optimal pour l'exécution, il est utile dans certaines circonstances, notamment lorsque la solution de remplacement conduit à un code difficile à gérer ou à modifier et lorsque des exécutions plus rapides ne sont pas essentielles. 

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

Ce code trie d'abord par code, puis par groupe, puis par IDGroup.

0
Douglas Adolph