[Go语言tips01]浅谈sort包
0. 引言
用C++时候就是用STL库里的sort函数,非常方便好用,看了一下Go语言的标准库,意料之内其中也是有相关的sort
包。
重新学习了一下sort包,把这篇博客又优化了一遍,让它更能为我以后查阅提供方便!——23/04/28
1. 常用类型切片排序
默认是升序(从小到大),想要降序的话可以使用Reverse函数,之后也会提到。
1.1 Ints - 整数切片排序
func main() {
s := []int{1, -1, 6, 4, 3, 8, 6, 234234, 8876, 234}
sort.Ints(s)
fmt.Println(s) // [-1 1 3 4 6 6 8 234 8876 234234]
}
可以通过func IntsAreSorted(a []int) bool
来判断是否是升序,注意降序返回的也是false
。
1.2 Float64s - 浮点数切片排序
func main() {
b := []float64{1.5, -1.6, 6.32326, 4.5545, 3.654654, 8.111, 6.2, 234234.9, 8876.0, 234.1}
sort.Float64s(b)
fmt.Println(b) // [-1.6 1.5 3.654654 4.5545 6.2 6.32326 8.111 234.1 8876 234234.9]
}
可以通过func Float64sAreSorted(a []float64) bool
来判断是否是升序,注意降序返回的也是false
。
1.3 Strings - 字符串切片排序
func main() {
b := []string{"a", "ab", "cc", "acd", "abbb"}
sort.Strings(b)
fmt.Println(b) // [a ab abbb acd cc]
}
可以通过func StringsAreSorted(a []string) bool
来判断是否是升序,注意降序返回的也是false
。
2. 常用类型切片查找
可以用来替代二分查找法找某一个元素或者是某个元素的范围。
函数规则如下:
- 找到了指定元素,则返回首个指定元素的索引
- 未找到指定元素,则返回应当插入的位置索引
2.1 SearchInts - 整数切片查找
func main() {
b := []int{1, 2, 3, 4, 4, 6, 7}
l := sort.SearchInts(b, 4)
r := sort.SearchInts(b, 5)
fmt.Println(l, r) // 3 5
}
如果找某个元素可以直接使用SearchInts
来查找获得索引l
,再判断b[l]
是否是指定元素即可。
如果找某个元素的范围可以先查找该元素获得左界限l
(闭区间),然后再查找该元素+1来获得右界限r
(开区间)。此时指定元素的范围是[l,r)
、个数是r-l
。
必须保证切片是升序排序的才能返回正确的结果。
2.2 SearchFloat64s - 浮点数切片查找
func main() {
b := []float64{1.1, 2.2, 3.4, 3.4, 4.6, 6.55, 7.89}
x := sort.SearchFloat64s(b, 3.4)
fmt.Println(x) // 2
}
必须保证切片是升序排序的才能返回正确的结果。
2.3 SearchStrings - 字符串切片查找
func main() {
b := []string{"a", "ab", "abbb", "ac"}
x := sort.SearchStrings(b, "abbb")
fmt.Println(x) // 2
}
必须保证切片是升序排序的才能返回正确的结果。
3. sort包接口的函数
这是sort包里定义的一个接口,也就说必须要实现了下面的三个方法才行,之后的结构体排序也时实现了这个接口才能进行自定义排序。
type Interface interface {
// Len方法返回集合中的元素个数
Len() int
// Less方法报告索引i的元素是否比索引j的元素小
Less(i, j int) bool
// Swap方法交换索引i和j的两个元素
Swap(i, j int)
}
sort包也提前给[]int
,[]float64
,[]string
准备了已经实现接口的type(T)
如果要使用后面提到的方法可以先把自己的切片做一个转换处理(或者直接声明指定类型):
a := []int{1, 2, 4, 3}
b := []float64{1.3, 2.4, 4.5, 3.6}
c := []string{"a", "aac", "ab", "dav"}
a = sort.IntSlice(a)
b = sort.Float64Slice(b)
c = sort.StringSlice(c)
或者:
a := sort.IntSlice{1, 2, 4, 3}
b := sort.Float64Slice{1.3, 2.4, 4.5, 3.6}
c := sort.StringSlice{"a", "aac", "ab", "dav"}
下面以整数数组为例来介绍常用的参数是Interface
类的函数。
3.1 Sort
调用1次data.Len确定长度,调用O(n*log(n))次data.Less和data.Swap。
本函数不能保证排序的稳定性(即不保证相等元素的相对次序不变)。
func main() {
n := sort.IntSlice{9, 8, 1, 6, 3, 0, -1, 99, 32, 7}
sort.Sort(n) // n.Sort() 实际上这个类有Sort方法调用sort.Sort
fmt.Println(n) // [-1 0 1 3 6 7 8 9 32 99]
}
这个方法其实对于简单的整数是很鸡肋的,毕竟可以直接sort.Ints(n)
排序。
3.2 Stable
调用1次data.Len,O(n * log(n))次data.Less和O(n * log(n) * log(n))次data.Swap。
Stable排序data,并保证排序的稳定性,相等元素的相对次序不变。
func main() {
n := sort.IntSlice{9, 8, 1, 6, 3, 0, -1, 99, 32, 7}
sort.Stable(n)
fmt.Println(n) // [-1 0 1 3 6 7 8 9 32 99]
}
Stable
和Sort
的区别就是Stable
可以保证相同元素的相对位置不变,但是耗时会多一点。
3.3 Reverse
Reverse包装一个Interface接口并返回一个新的Interface接口,对该接口排序可生成递减序列。
func main() {
n := sort.IntSlice{9, 8, 1, 6, 3, 0, -1, 99, 32, 7}
sort.Sort(sort.Reverse(n))
fmt.Println(n) // [99 32 9 8 7 6 3 1 0 -1]
}
需要注意的是Reverse
需要搭配Sort
使用,实际上就是返回了一个改写了Less
方法的Interface
接口。
4. 结构体排序
这一点是重中之重,也是这篇博客的核心,因为单独为某一个数组实现Interface
接口大多数情况是没有必要的……
4.1 实现Interface接口
核心就是我们要使用sort.Sort
进行排序,它的参数是Interface接口
因此我们的类需要实现这个接口,也就是同时有Len
、Swap
、Less
三个方法,前两个方法基本上都一样关键是第三个Less
方法决定了排序的方式。
type Person struct {
Name string
Age int
Star int
}
type Man []Person // Man是Person切片
func (a Man) Len() int { return len(a) }
func (a Man) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a Man) Less(i, j int) bool { return a[i].Age < a[j].Age }
上面的就是Less
就是按照年龄的升序排列的。
4.2 单条件排序
如:年龄小的排在前面。
func (p Person) String() string {
return fmt.Sprintf("%s: %d %d ,", p.Name, p.Age, p.Star)
}
type Man []Person
func (a Man) Len() int { return len(a) }
func (a Man) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a Man) Less(i, j int) bool { return a[i].Age < a[j].Age }
func main() {
people := Man{
{"Bob", 20, 99},
{"John", 21, 80},
{"Michael", 23, 70},
{"Jenny", 21, 81},
}
fmt.Println(people)
sort.Sort(people)
fmt.Println(people)
}
/*
[Bob: 20 99 , John: 21 80 , Michael: 23 70 , Jenny: 21 81 ,]
[Bob: 20 99 , John: 21 80 , Jenny: 21 81 , Michael: 23 70 ,]
*/
4.3 多条件排序
如:首先年龄小的在前面,年龄相同Star多的在前面。
func (p Person) String() string {
return fmt.Sprintf("%s: %d %d ,", p.Name, p.Age, p.Star)
}
type Man []Person
func (a Man) Len() int { return len(a) }
func (a Man) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a Man) Less(i, j int) bool {
if a[i].Age == a[j].Age {
return a[i].Star > a[j].Star
}
return a[i].Age < a[j].Age
}
func main() {
people := Man{
{"Bob", 20, 99},
{"John", 21, 80},
{"Michael", 23, 70},
{"Jenny", 21, 81},
}
fmt.Println(people)
sort.Sort(people)
fmt.Println(people)
}
/*
[Bob: 20 99 , John: 21 80 , Michael: 23 70 , Jenny: 21 81 ,]
[Bob: 20 99 , Jenny: 21 81 , John: 21 80 , Michael: 23 70 ,]
*/
4.4 补充
通过改写String方法来实现的输出结构体的内容,Get!
func (p Person) String() string {
return fmt.Sprintf("%s: %d %d ,", p.Name, p.Age, p.Star)
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· 单线程的Redis速度为什么快?
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码