有一个来自小村庄的老婆婆,
那一天去医院检查,
得知自己得了不治之症之后,
选择打电话把
这个消息告诉那个村里面与她最熟悉的人,说
自己得了不治之症。
出院后,她说自己要回去,
回到家里,
因为她还要去喂家里养的猫儿。
那个老婆婆是我的婆婆。
人生是一次有限的时间排序
读书的时候,最重要的是成绩的排序;工作的时候是能力,金钱的排序;恋爱的时候,也有对爱的排序。这些都是走过的路,是已经确定的排序。
对于做后端的同学,对数据的排序一定不会陌生。比如在一个分页查询商品信息的页面,需要根据商品的创建时间倒序排列,也就是最新创建的商品排在最前面。这个需求实现起来很简单,只需要使用sql的排序语句就可以实现
select * from product order by create\_time desc
但是只会sql这样的排序是不够的。比如从其他地方获取到一些初始数据,并且需要你先对这些数据排序,然后进行下一步处理,sql就帮不上忙了。所以还需要学会使用go来对数据进行排序。
排序在编码中是一个基本的操作。所以go提供的 sort 包来实现对不同类型的切片进行排序的功能。其中我们对常见的排序对象是int类型和string类型的切片。
对int类型的切片排序
import "sort"
numbers := []int{5, 3, 8, 1}
//print:[1, 3, 5, 8] 默认是升序
sort.Ints(numbers)
sort.Ints方法没有产生一个新的slice,而是在原有的slice进行排序(sort slice in place)
对字符串切片排序
import "sort"
names := []string{"God", "Alice", "Bob"}
sort.Strings(names)
与int的排序一样,也是就地排序,升序排列。
那么如何降序排序呢?
numbers := []int{5, 3, 8, 1}
//sort.Slice 的第二个参数,定义是升序还是降序
sort.Slice(numbers, func(i, j int) bool {
//表示前面的元素要比后面的元素大
return numbers[i] > numbers[j]
})
为了实现更多定制化的排序规则,sort包提供了一个Interface 接口
type Interface interface {
// 返回集合的元素个数
Len() int
// 表达在索引i的元素是否排在索引j的元素前面
Less(i, j int) bool
// 交换索引i,j对于的元素
Swap(i, j int)
}
实现 Interface 接口
import "sort"
type Person struct {
Name string
Age int
}
//根据年龄排序
type ByAge []Person
func (a ByAge) Len() int {
return len(a)
}
// < 对应着升序
func (a ByAge) Less(i, j int) bool {
return a[i].Age < a[j].Age
}
使用ByAge排序
people := ByAge{
{"Bob", 31},
{"Alice", 25},
{"Eve", 40},
}
sort.Sort(people)
需要降序只需要把Less方法里面的<改成>
func (a ByAge) Less(i, j int) bool {
return a[i].Age > a[j].Age
}
这里我们再次看到面向接口编程的好处了。
高级排序
根据多个字段排序
type People struct {
Name string
Age int
CreateTime time.Time
}
//先根据年龄升序排,然后根据姓名升序排
func (a ByPeople) Less(i, j int) bool {
if a[i].Age == a[j].Age {
return a[i].Name < a[j].Name
}
return a[i].Age < a[j].Age
}
稳定排序
稳定排序的意义在于, 无论进行多少次排序,排序结果的相同元素的相对顺序是保持不变的。始终保持一样的顺序在有些时候是很有用的。
people := ByPeople{
{"Bok", 15},
{"Ace", 15},
{"Eve", 40},
}
//Bok和Ace都是15 排序后的结果Bok仍然在Ace前面
sort.Stable(people)
使用来自外部定义的顺序
// 外部的顺序 用map保存每个人的顺序
var rank = map[string]int{
"哈二": 3,
"Alice": 1,
"Ai": 2,
}
func (a ByName) Less(i, j int) bool {
//根据姓名获取map里面的顺序来排序
return rank[a[i].Name] < rank[a[j].Name]
}
外部排序的方式也给我们一个启发: 如果我们已有的字段的排序无法满足要求,可以把这种排序规则外部化。
有一个亮灯的需求,一共有红灯(red),蓝灯(blue),绿灯(green),黄灯(yellow)4种,
type Plan struct {
Name string
//亮灯 值是red等英文
Light string
//...
}
按照英文字典的顺序是:blue,green,red,yellow。但是这里我们需要按照red,blue,green,yellow的顺序排列,下面来自定义这种排序规则:
var lightSort = map[string]int{
"red": 1,
"blue": 2,
"green": 3,
"yellow": 4,
}
给你的排序提提速
在排序前先判断是否已经排序了
if !sort.IntsAreSorted(nums) {
sort.Ints(nums)
}
减少排序时的数据移动
type Person struct {
Name string
Age int
}
// 使用指针类型的slice可以减少排序时的数据移动
people := []*Person{
{"Bob", 30},
{"Alice", 25},
}
sort.Slice(people, func(i, j int) bool {
return people[i].Age < people[j].Age
})
批量排序
对于有相同排序规则的多个slice排序,可以批量排序,提高缓存利用率
slices := [][]int{
{5, 3, 2},
{8, 7, 6},
{1, 3, 2},
}
for _, s := range slices {
sort.Ints(s)
}
频繁访问的小的数据集,比如这里的s,会被放在缓存里面,缓存里面的数据的访问会比计算机的主内存的访问快很多。
快速查找
sort包还提供了快速查找一个元素是否在排好序的slice。排序是为了更好的查看。
使用Search方法查找元素
package main
import (
"fmt"
"sort"
)
func main() {
a := []int{1, 3, 6, 10, 15, 21, 28, 36, 45, 55}
x := 6
//a[i] >= x 是按照元素升序的方式去查找的,
// 返回匹配到的元素的最小索引
i := sort.Search(len(a), func(i int) bool {
return a[i] >= x
})
if i < len(a) && a[i] == x {
fmt.Printf("found %d at index %d in %v\n", x, i, a)
} else {
fmt.Printf("%d not found in %v\n", x, a)
}
}
注意:如果元素没在slice里面,方法会返回slice的长度,而不是-1.
go1.19新增了Find方法,和Search类似
// 比Search多返回了一个found表示是否找到了指定的元素
// 方便后续代码判断
i, found := sort.Find(x.Len(), func(i int) int {
return strings.Compare(target, x.At(i))
})
if found {
fmt.Printf("found %s at entry %d\n", target, i)
} else {
fmt.Printf("%s not found, would insert at %d", target, i)
}