「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战」。
基础概念
自定义排序要求待排序的对象实现sort.Interface接口,只要实现一下三个方法即可
Len() int // 需要排序的集合的长度
Less(i, j int) bool // 比较i位置和j位置元素顺序
Swap(i, j int) // 替换i位置和j位置元素
最简单排序示例就是定义一个struct,分别实现以上三个方法,即可使用sort.Sort排序,其中Less函数是定义排序规则的方法,这里可以决定两个比较元素的顺序,返回true代表不用交换,返回false则需要交换位置。
import (
"fmt"
"sort"
)
// Student 学生struct
type Student struct {
Name string
Age int
EnglishScore int
MathScore int
}
// OrderByAge Student数组的别名,用于实现sort.Interface接口
// OrderByAge 可以直接使用sort.Sort来排序
type OrderByAge []Student
func (a OrderByAge) Len() int { return len(a) }
func (a OrderByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a OrderByAge) Less(i, j int) bool { return a[i].Age < a[j].Age }
students := []Student{
{ Name: "Lilei", Age: 18, EnglishScore: 78, MathScore: 79 },
{ Name: "Hanmeimei", Age: 17, EnglishScore: 79, MathScore: 99 },
{ Name: "Hetingting", Age: 19, EnglishScore: 71, MathScore: 68 },
}
println "students:", students
// 对students进行Age字段排序
sort.Sort(OrderByAge(students))
println "by age :", students
复制代码
程序会有以下输出:
students: [{Lilei 18 78 79} {Hanmeimei 17 79 99} {Hetingting 19 71 68}]
by age : [{Hanmeimei 17 79 99} {Lilei 18 78 79} {Hetingting 19 71 68}]
自定义排序
如果想灵活的排序字段可以使用排序函数的别名,然后声明多种不同规则的排序函数,首先有两个主要struct,一个是OrderBy,它是排序函数的别名,用于转换不同类型的排序,另一个是排序器StudentSorter,它是内部有待排序数据和对应的排序规则函数OrderBy,同时StudentSorter实现了sort.Interface接口,可以使用sort.Sort排序。
import (
"fmt"
"sort"
)
type Student struct {
Name string
Age int
EnglishScore int
MathScore int
}
// OrderBy 定义排序函数别名
// 用于定义StudentSorter排序器的排序规则
type OrderBy func(s1, s2 *Student) bool
func (by OrderBy) Sort(students []Student) {
ss := &StudentSorter{
Students: students,
OrderBy: by, // 它的Sort函数定义的排序的具体规则,它是可变的,所以可以有不同排序规则传递
}
sort.Sort(ss)
}
// StudentSorter student的排序器,实现了sort.Interface接口
// 包括student集合数据和排序的规则定义
type StudentSorter struct {
Students []Student
OrderBy OrderBy
}
func (s StudentSorter) Len() int { return len(s.Students) }
func (s StudentSorter) Swap(i, j int) { s.Students[i], s.Students[j] = s.Students[j], s.Students[i] }
func (s StudentSorter) Less(i, j int) bool { return s.OrderBy(&s.Students[i], &s.Students[j]) }
students := []Student{
{ Name: "Lilei", Age: 18, EnglishScore: 78, MathScore: 79 },
{ Name: "Hanmeimei", Age: 17, EnglishScore: 79, MathScore: 66 },
{ Name: "Hetingting", Age: 19, EnglishScore: 71, MathScore: 68 },
}
println "students:", students
// 定义按照EnglishScore排序的规则
englishScore := func(s1, s2 *Student) bool {
return s1.EnglishScore < s2.EnglishScore
}
OrderBy(englishScore).Sort(students)
println "By EnglishScore :", students
// 定义按照MathScore排序的规则
mathScore := func(s1, s2 *Student) bool {
return s1.MathScore < s2.MathScore
}
OrderBy(mathScore).Sort(students)
println "By MathScore :", students
复制代码
程序会有以下输出:
students: [{Lilei 18 78 79} {Hanmeimei 17 79 66} {Hetingting 19 71 68}]
By EnglishScore : [{Hetingting 19 71 68} {Lilei 18 78 79} {Hanmeimei 17 79 66}]
By MathScore : [{Hanmeimei 17 79 66} {Hetingting 19 71 68} {Lilei 18 78 79}]
如果想多个字段按照优先级排序呢,比如先按照EnglishScore排序,如果相等则接着按照MathScore排序呢?我们可以重新设计排序器,其中排序器同样带有带排序数据,不相同的是排序规则为多个,执行排序时候会遍历这些排序规则,如果第一个排序器可以决定顺序就返回,否则就计算下一个排序规则,知道排出顺序。
import (
"fmt"
"sort"
)
type Student struct {
Name string
Age int
EnglishScore int
MathScore int
}
// OrderBy 定义排序函数别名
// 用于定义StudentSorter排序器的排序规则
type OrderBy func(s1, s2 *Student) bool
// MultiSorter student的排序器,实现了sort.Interface接口
// 包括student集合数据和排序的规则定义,其中排序规则是多个带有优先级的排序
type MultiSorter struct {
Students []Student
OrderBys []OrderBy
}
func (s *MultiSorter) Sort(students []Student) {
s.Students = students
sort.Sort(s)
}
func (s *MultiSorter) Len() int { return len(s.Students) }
func (s *MultiSorter) Swap(i, j int) { s.Students[i], s.Students[j] = s.Students[j], s.Students[i] }
func (s *MultiSorter) Less(i, j int) bool {
p, q := &s.Students[i], &s.Students[j]
for index, orderBy := range s.OrderBys {
// 因为这里需要考虑相等情况下,进行下一轮比较,所以不能直接返回比较结果
switch {
case orderBy(p, q):
// 明确不需要调换位置
return true
case orderBy(q, p):
// 明确需要调换位置,也要返回
return false
}
if index == len(s.Students)-2 {
return orderBy(p, q) // 之前都没出结果,不论如何最后一个返回结果就行了
}
}
return true
}
// OrderedBy multiSorter的排序器构造函数
// 使用多个排序规则初始化,返回一个排序器
func OrderedBy(orderBys ...OrderBy) *MultiSorter {
return &MultiSorter{
OrderBys: orderBys,
}
}
students := []Student{
{Name: "Lilei", Age: 18, EnglishScore: 78, MathScore: 79},
{Name: "Hanmeimei", Age: 17, EnglishScore: 78, MathScore: 66},
{Name: "Hetingting", Age: 19, EnglishScore: 71, MathScore: 68},
}
fmt.Println("students:", students)
// 定义按照EnglishScore排序的规则
englishScore := func(s1, s2 *Student) bool {
return s1.EnglishScore < s2.EnglishScore
}
// 定义按照MathScore排序的规则
mathScore := func(s1, s2 *Student) bool {
return s1.MathScore < s2.MathScore
}
OrderedBy(englishScore).Sort(students)
fmt.Println("By EnglishScore:", students)
OrderedBy(englishScore, mathScore).Sort(students)
fmt.Println("By EnglishScore and MathScore: ", students)
复制代码
程序会有以下输出:
students: [{Lilei 18 78 79} {Hanmeimei 17 78 66} {Hetingting 19 71 68}]
By EnglishScore: [{Hetingting 19 71 68} {Hanmeimei 17 78 66} {Lilei 18 78 79}]
By EnglishScore and MathScore: [{Hetingting 19 71 68} {Hanmeimei 17 78 66} {Lilei 18 78 79}]
可以看到当EnglishScore都是78的时候使用了MathScore作为排序的结果。
因为本质上,排序是给出一个固定的规则下,有一个固定的顺序,所以我们可以做一些有意思的排序,比如奇数在前,偶数在后,这个例子和第一个例子只有Less函数的实现不同,其它都是相同的,Less函数逻辑也很简单,如果都是奇数或者都是偶数返回true,如果i是奇数,返回true,否则返回false。
import (
"fmt"
"sort"
)
// Student 学生struct
type Student struct {
Name string
Age int
EnglishScore int
MathScore int
}
// OrderByAge Student数组的别名,用于实现sort.Interface接口
// OrderByAge 可以直接使用sort.Sort来排序
type OrderByAge []Student
func (a OrderByAge) Len() int { return len(a) }
func (a OrderByAge) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a OrderByAge) Less(i, j int) bool {
// 请注意,和第一个例子,只有这里不同
// 奇数在前,偶数在后
if (a[i].Age + a[j].Age) % 2 == 0 {
// 说明要么都是奇数,要么都是偶数,返回true
return true
}
if a[i].Age % 2 == 0 {
// 说明i是偶数,j是奇数,需要调换位置
return false
}
// 否则就是不用调换
return true
}
students := []Student{
{ Name: "Lilei", Age: 18, EnglishScore: 78, MathScore: 79 },
{ Name: "Hanmeimei", Age: 17, EnglishScore: 79, MathScore: 99 },
{ Name: "Hetingting", Age: 19, EnglishScore: 71, MathScore: 68 },
}
println "students:", students
// 对students进行Age字段排序
sort.Sort(OrderByAge(students))
println "by age :", students
复制代码
程序会有以下输出:
students: [{Lilei 18 78 79} {Hanmeimei 17 79 99} {Hetingting 19 71 68}]
by age : [{Hetingting 19 71 68} {Hanmeimei 17 79 99} {Lilei 18 78 79}]
可以看到顺序已经变了,并且是奇数在前偶数在后。
在做一个有意思的排序,我们假设考试分数百分制,忽略具体分数,而是按照等级排序,等级A:90-100 B:80-90 C:70-80 D: 60-70 E:小于60,同级排名不分先后,这里和第一个例子也是只修改了Less的实现,因为它是管排序规则的,或者对应级别后,因为字符串可以直接比较大小,所以直接返回对比大小。
程序会有以下输出:
students: [{Hanmeimei 17 79 99} {Lilei 18 78 79} {Hetingting 19 66 68}]
by EnglishScore: [{Hetingting 19 66 68} {Hanmeimei 17 79 99} {Lilei 18 78 79}]
可以看到66排序在前,79和78顺序没有动,因为它俩级别都是C。