排序与常用数据操作
本章目标
本章目标是把你手里的数据真正用起来:会排序、会查找、会去重、会反转。 看完后你至少能独立处理一组切片数据,不再只会"存进去"。
常见坑位
先看最常见的翻车点:
- 以为排序会返回新切片,结果原数据被改了。
- 自定义排序比较逻辑写反,升序写成降序。
BinarySearch直接用在无序切片上,查找结果不可信。- 去重时想保留原顺序,却用了"先排序再去重"的方案。
- 把
Contains当高性能查找用在超大数据上,性能不稳定。
先建立主线:这章到底在解决什么
上一章你学了切片和 map,已经能把数据放进容器。 但业务里更常见的问题是:
- 我要把列表按规则排好。
- 我要判断某个值在不在。
- 我要把重复数据清掉。
- 我要临时倒序输出。
这章就围绕这 4 个动作展开。
基础排序:slices.Sort
Go 1.21+ 推荐用标准库 slices 包做基础排序。 先跑最小可运行示例:
go
package main
import (
"fmt"
"slices"
)
func main() {
nums := []int{5, 2, 8, 1, 9, 3}
slices.Sort(nums) // 原地升序排序
fmt.Println(nums) // [1 2 3 5 8 9]
}
关键点:slices.Sort 是原地修改,不返回新切片。
自定义排序:slices.SortFunc
当你要按结构体字段排序时,用 slices.SortFunc。 这里先把结构体当成"把多个字段绑在一起的数据块",下一章再细讲结构体。
go
package main
import (
"cmp"
"fmt"
"slices"
)
type Student struct {
Name string
Score int
}
func main() {
students := []Student{
{Name: "小明", Score: 88},
{Name: "小红", Score: 95},
{Name: "小刚", Score: 72},
}
// 按分数降序
slices.SortFunc(students, func(a Student, b Student) int {
return cmp.Compare(b.Score, a.Score)
})
for _, student := range students {
fmt.Printf("%s: %d\n", student.Name, student.Score)
}
}
判断升降序的简单口诀:
- 升序:
cmp.Compare(a, b)。 - 降序:
cmp.Compare(b, a)。
判断是否有序:slices.IsSorted
如果你不确定一组数据是不是已经排好,可以先判断:
go
package main
import (
"fmt"
"slices"
)
func main() {
nums := []int{1, 2, 3, 5, 8}
fmt.Println(slices.IsSorted(nums)) // true
}
这个函数不常用,但在"避免重复排序"时很实用。
查找元素:Contains 和 BinarySearch
无序切片:slices.Contains
go
package main
import (
"fmt"
"slices"
)
func main() {
fruits := []string{"苹果", "香蕉", "橘子"}
fmt.Println(slices.Contains(fruits, "香蕉")) // true
fmt.Println(slices.Contains(fruits, "西瓜")) // false
}
Contains 是线性遍历,数据量小到中等完全够用。
有序切片:slices.BinarySearch
如果数据已经排好序,查找建议用二分:
go
package main
import (
"fmt"
"slices"
)
func main() {
fruits := []string{"苹果", "橘子", "香蕉"}
slices.Sort(fruits) // BinarySearch 前必须先排序
index, found := slices.BinarySearch(fruits, "香蕉")
fmt.Printf("found=%v, index=%d\n", found, index)
}
注意:找不到时返回的 index 是"可插入位置",不是 -1。
去重:保序和不保序两种思路
保留原始顺序:map 标记法
go
package main
import "fmt"
func uniqueKeepOrder(items []string) []string {
seen := make(map[string]bool)
result := make([]string, 0, len(items))
for _, item := range items {
if seen[item] {
continue
}
seen[item] = true
result = append(result, item)
}
return result
}
func main() {
tags := []string{"go", "web", "go", "api", "web"}
fmt.Println(uniqueKeepOrder(tags)) // [go web api]
}
不要求原顺序:排序 + Compact
go
package main
import (
"fmt"
"slices"
)
func main() {
tags := []string{"go", "web", "go", "api", "web"}
slices.Sort(tags)
tags = slices.Compact(tags)
fmt.Println(tags) // [api go web]
}
这一招只会去掉"相邻重复项",所以必须先排序。
反转切片:slices.Reverse
go
package main
import (
"fmt"
"slices"
)
func main() {
words := []string{"a", "b", "c", "d"}
slices.Reverse(words) // 原地反转
fmt.Println(words) // [d c b a]
}
动手练习
- 准备一个整数切片,先
Sort,再Reverse,观察结果。 - 写一个结构体切片,用
SortFunc按分数字段升序和降序各排一次。 - 用
Contains判断一个标签在不在。 - 用
BinarySearch在有序切片中查找元素,并打印found和index。 - 分别用"map 保序去重"和"Sort+Compact 去重"对同一组数据处理,对比结果。
过关标准
满足下面 6 条,这章就算过关:
- 你知道
slices.Sort会原地修改数据。 - 你能写出
SortFunc的升序和降序比较逻辑。 - 你知道
BinarySearch必须在有序切片上使用。 - 你能区分保序去重和排序去重的差异。
- 你会用
Contains、IsSorted、Reverse三个常用函数。 - 你能自己写出一个完整的小示例并跑通。
下一章衔接
下一章我们讲结构体 struct,把多个关联字段组织成真正可维护的数据模型。
本文章属于《Go语言大白话入门》零基础全套教程,持续更新、全程实操练手。 微信公众号【码栈研说】超前更新,目前比掘金领先 30 章内容,完整连载、无删减、持续日更,需要完整系统学习的读者可自行前往查看。
文中部分内容借助 AI 辅助梳理,全文人工校验、实操落地。