Go 用 slices/maps 标准库泛型函数:告别手写 Contains、Sort、去重(Go 1.21)

Go 用 slices/maps 标准库泛型函数:告别手写 Contains、Sort、去重(Go 1.21)

你有没有在每个 Go 项目里都复制粘贴过一份 sliceContainssortByFielduniqueStrings 的工具函数?Go 泛型落地前,这些代码只能靠手写,一个项目一份,还容易写错。从 Go 1.21 开始,标准库正式收纳了 slicesmaps 两个泛型包,把这些高频操作一次性补齐。这篇就把最常用的函数过一遍,顺便讲清楚几个容易踩的坑。

先看看以前有多啰嗦

想判断一个切片里有没有某个元素,Go 1.21 之前得自己写循环:

go 复制代码
// 朴素写法:每个项目都要来一份
func contains(s []string, target string) bool {
    for _, v := range s {
        if v == target {
            return true
        }
    }
    return false
}

排序稍微复杂一点就得实现 sort.Interface,或者用 sort.Slice 传一个闭包,写法冗长且不类型安全。这些样板代码没有技术含量,却又不得不写。

slices 包:查找、排序、增删一步到位

引入 slices 后,上面那堆全都不用自己写了:

go 复制代码
package main

import (
    "fmt"
    "slices"
)

func main() {
    nums := []int{3, 1, 4, 1, 5, 9, 2, 6}

    // 排序:原地升序,不用再写 sort.Slice 闭包
    slices.Sort(nums)
    fmt.Println(nums) // [1 1 2 3 4 5 6 9]

    // 查找:是否包含
    fmt.Println(slices.Contains(nums, 5)) // true

    // 二分查找(切片必须已排序),返回下标和是否命中
    idx, found := slices.BinarySearch(nums, 4)
    fmt.Println(idx, found) // 4 true

    // 找第一个满足条件的下标,没有返回 -1
    i := slices.IndexFunc(nums, func(n int) bool { return n > 4 })
    fmt.Println(i) // 6(值为 5 的位置)
}

自定义排序用 slices.SortFunc,回调返回一个整数(负/零/正表示小于/等于/大于),语义和 C 的 qsort 一致:

go 复制代码
type User struct {
    Name string
    Age  int
}

users := []User{{"Bob", 30}, {"Alice", 25}, {"Carol", 30}}

// 先按年龄升序,年龄相同按名字字典序
slices.SortFunc(users, func(a, b User) int {
    if a.Age != b.Age {
        return a.Age - b.Age // 注意:字段是大整数时可能溢出,见下文
    }
    return strings.Compare(a.Name, b.Name)
})

用 slices 优雅去重

去重是另一个高频需求。slices.Compact 能删掉相邻的重复元素,配合先排序就是标准去重套路:

go 复制代码
func unique[T cmp.Ordered](s []T) []T {
    slices.Sort(s)              // 让重复元素相邻
    return slices.Compact(s)    // 删除相邻重复,返回新长度的切片
}

fmt.Println(unique([]int{3, 1, 1, 4, 3, 3})) // [1 3 4]

这里 cmp.Orderedcmp 包提供的约束,涵盖所有可比较大小的基础类型(整数、浮点、字符串)。如果不想改变原顺序,就别用 Compact,改用 map 记录见过的元素。

maps 包:提取 key、拷贝与比较

maps 包补齐了操作 map 的常用动作。要注意 Go 1.21 里 maps.Keys 返回的是迭代器 (Go 1.23 起是 iter.Seq),需要配合 slices.Collect 收集成切片:

go 复制代码
import (
    "maps"
    "slices"
)

m := map[string]int{"a": 1, "b": 2, "c": 3}

// 取出所有 key(顺序随机!),收集成切片再排序
keys := slices.Collect(maps.Keys(m))
slices.Sort(keys)
fmt.Println(keys) // [a b c]

// 浅拷贝一个 map,改副本不影响原 map
cp := maps.Clone(m)
cp["a"] = 99
fmt.Println(m["a"]) // 1,原 map 没变

// 比较两个 map 内容是否相等
fmt.Println(maps.Equal(m, cp)) // false

如果你的 Go 版本是 1.21/1.22,maps.Keys 的签名可能还是老的返回切片版本,升级到 1.23 后统一成迭代器。写代码前 go version 确认一下,避免编译报错找不到北。

三个别踩的坑

第一,slices.Clone 是浅拷贝。 它只复制切片本身的元素,如果元素是指针或含切片的结构体,拷贝后两者仍共享底层数据:

go 复制代码
type Box struct{ Items []int }
a := []Box{{Items: []int{1, 2}}}
b := slices.Clone(a)
b[0].Items[0] = 99
fmt.Println(a[0].Items[0]) // 99!内层切片是共享的

第二,SortFunc 的比较函数别用减法算大整数。 a.Age - b.Age 在数值接近 int 边界时会溢出,得到错误的正负号。安全写法是用 cmp.Compare(a.Age, b.Age),它内部做了溢出保护。

第三,slices.Compact 只删相邻重复。 没排序的切片直接 Compact 不会全局去重,[1,2,1] 出来还是 [1,2,1]。记住:先 Sort 再 Compact。

小结

  • Go 1.21 的 slices/maps 泛型包让 Contains、Sort、去重、Clone 这些操作不用再手写工具函数。
  • 自定义排序用 slices.SortFunc,比较函数优先用 cmp.Compare 而不是减法,避免溢出。
  • 去重标准套路:Sort 让重复相邻 + Compact 删除相邻重复。
  • Clone 是浅拷贝,元素含引用类型时要当心共享;maps.Keys 在新版本返回迭代器,用 slices.Collect 收集。

一句话记忆:能用标准库就别自己撸工具函数------slicesmaps 已经帮你写好、测好、优化好了。

相关推荐
地平线开发者1 小时前
【模型轻量化专题】深度学习模型为什么需要轻量化
算法
淼澄研学1 小时前
Python实战:基于LangChain与Chroma构建企业级RAG知识库问答系统
开发语言·python·langchain
4311媒体网1 小时前
帝国CMS网站自定义搭建技巧
服务器·开发语言·前端
_Narcissus_1 小时前
链表算法题和静态链表
数据结构·c++·笔记·算法·链表·ai·力扣
布莱克6051 小时前
C++拷贝构造与拷贝赋值运算符的区别详解
开发语言·c++
程序大爆炸1 小时前
gcsfuse与中断FUSE_INTERRUPT
后端
Lyyaoo.1 小时前
【普通数组】【中等】除了自身以外数组的乘积
数据结构·算法·leetcode
lhldsg2 小时前
全民健身解决方案:从共享球场到智能运营的技术实践
java·大数据·开发语言·需求分析
vipxieliang2 小时前
ValidX时间注解完全指南:10种时间验证注解详解
java·后端