开发过程中,我们经常需要对元素进行排序,使用 Go 我们可以轻松实现。
Go 内置 sort 包中提供了根据一些排序函数可对任何序列进行排序,并提供自定义排序规则的能力。
sort 包实现了四种基本排序算法:插入排序(Shell 排序)、归并排序、堆排序和快速排序。 但是这四种排序方法是不公开的,它们只被用于 sort 包内部使用,sort 包会根据实际数据自动选择高效的排序算法。
Go sort 包主要提供了三种排序能力: (1)基本类型切片排序; (2)自定义比较器; (3)排序任意数据结构。
三者易用程度逐渐降低。
2.基本类型切片排序[]int[]float64[]string
func Ints(x []int)
func Float64s(x []float64)
func Strings(x []string)
示例:
package main
import (
"fmt"
"sort"
)
func main() {
// []int 排序
slInt := []int{5, 2, 6, 3, 1, 4} // unsorted
sort.Ints(slInt)
fmt.Println(slInt) // 输出 [1 2 3 4 5 6]
// []float64 排序
slF64 := []float64{5.2, -1.3, 0.7, -3.8, 2.6} // unsorted
sort.Float64s(slF64)
fmt.Println(slF64) // 输出 [-3.8 -1.3 0.7 2.6 5.2]
// []string 字典序
slStr := []string{"Go", "Bravo", "Gopher", "Alpha", "Grin", "Delta"}
sort.Strings(slStr)
fmt.Println(slStr) // 输出 [Alpha Bravo Delta Go Gopher Grin]
}
3.自定义比较器func(i, j int) bool
func Slice(x interface{}, less func(i, j int) bool)
这个用来比较大小的函数就是比较器,返回值 true 表示下标 i 的元素小于下表 j 的元素。如果是降序排列的话,可以理解为返回 true 表示下标 i 的元素排在下标 j 的元素前面。
如果想稳定排序的话,使用 sort.SliceStable(),在排序切片时会保留相等元素的原始顺序。
按照年龄升序排序的示例:
func main() {
slStdnt := []struct {
Name string
Age int
Height int
}{
{"Alice", 23, 175},
{"David", 18, 185},
{"Eve", 18, 165},
{"Bob", 25, 170},
}
// 用 age 排序,年龄相等的元素保持原始顺序
sort.SliceStable(slStdnt, func(i, j int) bool {
return slStdnt[i].Age < slStdnt[j].Age
})
fmt.Println(slStdnt)
}
运行输出:
[{David 18 185} {Eve 18 165} {Alice 23 175} {Bob 25 170}]
如果想要在年龄相等的情况下再按照身高排序,我们修改一下比较器即可。
// 优先按照年龄排,年龄相等的话再按照身高排
sort.SliceStable(slStdnt, func(i, j int) bool {
if slStdnt[i].Age < slStdnt[j].Age {
return true
}
if slStdnt[i].Age > slStdnt[j].Age {
return false
}
return slStdnt[i].Height < slStdnt[j].Height
})
运行输出:
[{Eve 18 165} {David 18 185} {Alice 23 175} {Bob 25 170}]
4.排序任意数据结构使用 sort.Sort() 或者 sort.Stable() 函数可完成对任意类型元素的排序。
一个内置的排序算法需要知道三个东西:序列的长度,表示两个元素比较的结果,一种交换两个元素的方式;这就是 sort.Interface 的三个方法:
type Interface interface {
Len() int
Less(i, j int) bool // i, j 是元素的索引
Swap(i, j int)
}
这种方法相较于前面介绍的两种排序,用起来稍微麻烦了点,因为需要用户自定义的东西更多了,不过带来的好处也是显而易见的,更加普适。
还是以学生排序为例,在自定义的结构体上实现 srot.Interface 接口。
type Student struct {
Name string
Age int
Height int
}
// ByAgeHeight 实现 sort.Interface 接口
type ByAgeHeight []Student
func (a ByAgeHeight) Len() int { return len(a) }
// Less 先用年龄排序,年龄相等再用身高排
func (a ByAgeHeight) Less(i, j int) bool {
if a[i].Age < a[j].Age {
return true
}
if a[i].Age > a[j].Age {
return false
}
return a[i].Height < a[j].Height
}
func (a ByAgeHeight) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func main() {
slStdnt := []Student{
{"Alice", 23, 175},
{"David", 18, 185},
{"Eve", 18, 165},
{"Bob", 25, 170},
}
sort.Stable(ByAgeHeight(slStdnt))
fmt.Println(slStdnt)
}
运行输出:
[{Eve 18 165} {David 18 185} {Alice 23 175} {Bob 25 170}]
5.小结Go sort 包提供的排序能力丰富易用,掌握上面三种排序的用法,便可轻松应对大部分排序场景。
sort 包除了提供以上三种排序能力,还为我们提供了其他功能,比如判断序列是否有序、在有序序列中通过二分查找指定元素下标,反转有序序列获得逆序等,具体用法参见官方文档 Package sort。
参考文献