How to sort struct with multiple sort parameters?

前端 未结 8 555
囚心锁ツ
囚心锁ツ 2020-12-24 11:24

I have an array/slice of members:

type Member struct {
    Id int
    LastName string
    FirstName string
}

var members []Member

My quest

相关标签:
8条回答
  • 2020-12-24 12:12

    The shortest and still comprehensible code I managed to write for this is:

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

    The pattern using the switch statement easily extends to more than two sorting criteria and is still short enough to be read.

    Here's the rest of the program:

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

    A completely different idea, but same API

    If you want to avoid mentioning the fields LastName and FirstName multiple times and if you want to avoid mixing i and j (which can happen all the way), I played around a bit and the basic idea is:

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

    Since Go doesn't support the -> operator for creating anonymous functions and also doesn't have generics like Java, a bit of syntactical overhead is needed:

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

    The implementation as well as the API is a bit ugly using interface{} and reflection, but it only mentions each field once, and the application code does not have a single chance to accidentally mix the indexes i and j since it doesn't deal with them.

    I designed this API in the spirit of Java's Comparator.comparing.

    The infrastructure code for the above sorter is:

    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) }
    
    0 讨论(0)
  • 2020-12-24 12:12

    Just created go project to implement it: https://github.com/itroot/keysort

    You can just return a list of fields to sort:

    package main
    
    import (
        "fmt"
    
        "github.com/itroot/keysort"
    )
    
    type Data struct {
        A int
        B string
        C bool
    }
    
    func main() {
        slice := []Data{{1, "2", false}, {2, "2", false}, {2, "1", true}, {2, "1", false}}
        keysort.Sort(slice, func(i int) keysort.Sortable {
            e := slice[i]
            return keysort.Sequence{e.A, e.B, e.C}
        })
        fmt.Println(slice)
    }
    

    Playground link: https://play.golang.org/p/reEDcoXNiwh

    0 讨论(0)
提交回复
热议问题