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

时间:2016-03-21 03:26:57

标签: sorting go

我有一个数组/片段成员:

type Member struct {
    Id int
    LastName string
    FirstName string
}

var members []Member

我的问题是如何按LastName然后按FirstName对其进行排序。

11 个答案:

答案 0 :(得分:36)

使用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

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

使用sort.Sort函数{/ 3>通过interface指定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))

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

答案 1 :(得分:14)

使用较新的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
})

或类似的东西。

答案 2 :(得分:3)

另一种模式,我发现它稍微干净一些:

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

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

答案 3 :(得分:1)

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

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语句的模式很容易扩展到两个以上的排序标准,并且仍然足够短以至于无法读取。

这是程序的其余部分:

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相同

如果您要避免多次提及字段LastNameFirstName,并且要避免混淆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)
}

使用interface{}和反射时,实现和API有点难看,但是它仅提及每个字段一次,并且应用程序代码没有偶然混合索引{{1}的机会。 }和i,因为它不处理它们。

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

上述分类器的基础结构代码为:

j

答案 4 :(得分:1)

只需创建go项目来实现它:https://github.com/itroot/keysort

您可以仅返回要排序的字段列表:

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

游乐场链接:https://play.golang.org/p/reEDcoXNiwh

答案 5 :(得分:1)

比当前接受的答案更简洁的解决方案是做更多这样的事情:

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

这样做的好处在于,如果您添加一个新字段 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
})

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

答案 6 :(得分:1)

您可以创建一个函数切片:

package main

type (
   mFunc func(a, b member) bool
   member struct { lastName, firstName string }
)

var members = []member{
   {"Mullen", "Larry"},
   {"Howard", "James"},
   {"Clayton", "Adam"},
   {"Howard", "Ben"},
}

var mFuncs = []mFunc{
   func(a, b member) bool { return a.lastName < b.lastName },
   func(a, b member) bool { return a.firstName < b.firstName },
}

然后遍历函数,直到其中一个函数找到一个 区别:

package main

import (
   "fmt"
   "sort"
)

func main() {
   sort.Slice(members, func(a, b int) bool {
      ma, mb := members[a], members[b]
      for _, mf := range mFuncs {
         if mf(ma, mb) {
            return true
         }
         if mf(mb, ma) {
            break
         }
      }
      return false
   })
   fmt.Println(members) // [{Clayton Adam} {Howard Ben} {Howard James} {Mullen Larry}]
}

https://golang.org/pkg/sort#example__sortMultiKeys

答案 7 :(得分:0)

这非常有帮助。我需要对结构进行分类,然后在这里找到答案。实际上,我将其扩展为三重排序。尽管对于运行时而言,排序不是最佳选择,但在某些情况下很有用,尤其是当替代方法导致难以维护或修改的代码以及更快的运行时并不重要时。

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排序。

答案 8 :(得分:0)

以下是已接受答案的更简洁的实现方式:

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

这是预期的:成员按姓氏的升序打印,如果是平局,则按姓氏的顺序打印。

答案 9 :(得分:0)

所有好的答案, 如果您不想编写任何配置。您可以使用外部程序包执行排序。

go get -d github.com/raunakjodhawat/multisort

并像这样调用函数:

sortedSlice, err := multisort.MultiSorted(inputSlice, inputKeys, SortOrders)

要查看具体示例,请转到: https://github.com/raunakjodhawat/multisort

答案 10 :(得分:0)

只需阅读位于 https://pkg.go.dev/sort 的 Go 官方文档中 SortMultiKeys 示例的代码,并使其适应您的 Member 结构。

为了使这个答案保持最新,我不会在此处复制和粘贴整个示例,但最终您可以按如下方式编写:

OrderedBy(lastName, firstName).Sort(members)

如果要求也改为按年龄排序,您只需添加 age 闭包:

OrderBy(lastName, firstName, age).Sort(members)