go的sort包真心强大,但你真的会用吗

人工智能与算法数据库算法

有一个来自小村庄的老婆婆,

那一天去医院检查,

得知自己得了不治之症之后,

选择打电话把

这个消息告诉那个村里面与她最熟悉的人,说

自己得了不治之症。

出院后,她说自己要回去,

回到家里,

因为她还要去喂家里养的猫儿。

那个老婆婆是我的婆婆。

人生是一次有限的时间排序


读书的时候,最重要的是成绩的排序;工作的时候是能力,金钱的排序;恋爱的时候,也有对爱的排序。这些都是走过的路,是已经确定的排序。

picture.image

对于做后端的同学,对数据的排序一定不会陌生。比如在一个分页查询商品信息的页面,需要根据商品的创建时间倒序排列,也就是最新创建的商品排在最前面。这个需求实现起来很简单,只需要使用sql的排序语句就可以实现

  
select * from product order by create\_time desc

但是只会sql这样的排序是不够的。比如从其他地方获取到一些初始数据,并且需要你先对这些数据排序,然后进行下一步处理,sql就帮不上忙了。所以还需要学会使用go来对数据进行排序。

排序在编码中是一个基本的操作。所以go提供的 sort 包来实现对不同类型的切片进行排序的功能。其中我们对常见的排序对象是int类型和string类型的切片。

picture.image

对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)  
}
0
0
0
0
关于作者
关于作者

文章

0

获赞

0

收藏

0

相关资源
字节跳动 NoSQL 的实践与探索
随着 NoSQL 的蓬勃发展越来越多的数据存储在了 NoSQL 系统中,并且 NoSQL 和 RDBMS 的界限越来越模糊,各种不同的专用 NoSQL 系统不停涌现,各具特色,形态不一。本次主要分享字节跳动内部和火山引擎 NoSQL 的实践,希望能够给大家一定的启发。
相关产品
评论
未登录
看完啦,登录分享一下感受吧~
暂无评论