Golang:如何使用多个排序参数对struct进行排序?

Mel*_*vin 26 sorting go

[UDPATE]我的错.我应该更彻底地阅读文档,而不是立即提出这个问题.我的错.

我有一个数组/片成员:

type Member struct {
    Id int
    LastName string
    FirstName string
}

var members []Member
Run Code Online (Sandbox Code Playgroud)

我的问题是如何将它们排序LastName,然后按FirstName.

任何帮助表示赞赏谢谢.

Cer*_*món 46

使用sort.Slice(自Go 1.8起可用)或sort.Sort函数对一片值进行排序.

使用这两个函数,应用程序提供了一个函数,用于测试一个slice元素是否小于另一个slice元素.要按姓氏和名字排序,请比较姓氏和名字:

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
Run Code Online (Sandbox Code Playgroud)

使用sort.Slice的匿名函数指定less函数:

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
})
Run Code Online (Sandbox Code Playgroud)

通过具有sort.Sort函数的接口指定less函数:

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))
Run Code Online (Sandbox Code Playgroud)

除非性能分析显示排序是一个热点,否则请使用对您的应用程序最方便的功能.


abo*_*get 17

使用更新的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
})
Run Code Online (Sandbox Code Playgroud)

或类似的东西.

  • 你应该提一下,这只是从Go 1.8开始 (5认同)
  • 不要使用strings.Compare.https://golang.org/pkg/strings/#Compare上的文档说"仅包含比较包含字节的对称性.使用内置的字符串比较运算符==,<,通常更清晰,速度更快, >,依此类推." (4认同)
  • 如果两个成员的姓氏和名字都可能相同,并且您希望保留切片的原始顺序,则应使用`sort.Stable`或`sort.SliceStable`. (3认同)

Rol*_*lig 6

我为此编写的最短但仍然易于理解的代码是:

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
        }
    })
}
Run Code Online (Sandbox Code Playgroud)

使用该switch语句的模式很容易扩展到两个以上的排序标准,并且仍然足够短以供阅读。

这是程序的其余部分:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

一个完全不同的想法,但相同的 API

如果你想避免多次提到字段LastNameFirstName,如果你想避免混合ij(这可能一直发生),我玩了一下,基本思想是:

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

由于 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)
}
Run Code Online (Sandbox Code Playgroud)

实现以及 API 的使用interface{}和反射有点丑陋,但它只提到每个字段一次,并且应用程序代码没有机会意外混合索引ij因为它不处理它们。

我本着 Java 的Comparator.comparing的精神设计了这个 API 。

上述分拣机的基础设施代码是:

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) }
Run Code Online (Sandbox Code Playgroud)


LVB*_*LVB 5

我发现另一种更干净的模式:

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

return members[i].FirstName < members[j].FirstName
Run Code Online (Sandbox Code Playgroud)


cyp*_*har 5

比当前接受的答案稍微干净的解决方案是做类似这样的事情:

sort.Slice(members, func(i, j int) bool {
    if members[i].FirstName != members[j].FirstName {
        return members[i].FirstName < members[j].FirstName
    }
    return members[i].LastName < members[j].LastName
})
Run Code Online (Sandbox Code Playgroud)

这样做的好处是,如果添加新字段Age(或其他内容),您将更清楚如何扩展它:

sort.Slice(members, func(i, j int) bool {
    if members[i].FirstName != members[j].FirstName {
        return members[i].FirstName < members[j].FirstName
    }
    if members[i].Age != members[j].Age {
        return members[i].Age < members[j].Age
    }
    return members[i].LastName < members[j].LastName
})
Run Code Online (Sandbox Code Playgroud)

因此,模式是if X[i] != X[j]为所有属性添加一条语句,直到最后一个属性,这只是正常比较。