登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  Golang >  Go教程

Go slices.Compact 怎样判断输入是否满足边界

来源:17golang原创

时间:2026-11-03 17:57:16 123浏览 收藏

如果你想判断一个输入是否适合交给 slices.Compact,先看“重复是否相邻”,再看结果长度是否变化。这个函数只会把连续相等的一段压成一个元素,不会排序,也不会把分散在切片不同位置的相同值自动合并。

官方文档:https://pkg.go.dev/slices#Compact

从 Go 1.21 开始,标准库提供了 slices 包。使用 Compact 前,可以把边界分成五类:nil 或空切片、只有一个元素、已经没有相邻重复、存在相邻重复、重复值不相邻。前四类的判断方式不同,最后一类则说明数据还不满足“连续压缩”的业务前提。

先确认输入是不是连续重复

Compact 的比较范围是相邻元素。下面的两组输入看起来都有重复值,但只有第一组会被压缩:

// 这段示例用相邻比较区分 Compact 能处理的重复和分散重复。
package main

import (
    "fmt"
    "slices"
)

func main() {
    adjacent := []string{"go", "go", "rust", "rust", "go"}
    separated := []string{"go", "rust", "go", "rust"}

    adjacent = slices.Compact(adjacent)
    separated = slices.Compact(separated)

    fmt.Println(adjacent)  // 结果只保留每段连续值的第一个元素。
    fmt.Println(separated) // 分散重复没有相邻关系,仍会保留。
}

因此,标题里的“满足边界”不是判断切片里有没有重复值,而是判断重复是否以连续分组的形式出现。如果业务要求全局去重,需要先明确顺序、哈希表或排序策略,不能直接把 Compact 当成全局去重函数。

输入切片中相邻重复与非相邻重复的范围说明图
图1:slices.Compact 只处理相邻连续重复项的范围说明图,不是运行截图。

用相邻比较预判输入会不会变化

如果调用方需要在压缩前记录“输入是否已经满足边界”,可以只检查每一对相邻元素。泛型约束与 Compact 一样使用可比较元素:

// hasAdjacentEqual 只回答一个问题:切片里是否存在相邻相等元素。
// 它不排序、不修改输入,也不负责判断所有重复值。
func hasAdjacentEqual[S ~[]E, E comparable](s S) bool {
    for i := 1; i 

这个判断天然覆盖几个边界:

  • nil 和 []T{} 的长度都不超过 1,不存在相邻元素。
  • 单元素切片没有可比较的相邻位置,调用后长度不会变化。
  • 每一对相邻值都不相等时,输入已经满足“没有连续重复”的边界。
  • 只要找到一对相邻相等的值,结果就可能变短,但变短多少要由连续段长度决定。

要注意“可能变短”而不是“必然得到某个固定长度”。例如连续段可以出现在开头、中间或结尾,多个连续段也会同时被压缩。

调用后以返回长度作为结果边界

Compact 会返回处理后的切片,所以应当接住返回值。原来的切片变量不会因为函数在内部修改了底层数组,就自动拥有新的长度:

// 以返回值覆盖原变量,后续遍历才会使用压缩后的长度。
func compactNames(names []string) []string {
    before := len(names)
    names = slices.Compact(names)
    after := len(names)

    // before 和 after 用来记录业务上是否发生了压缩。
    if after  %d\n", before, after)
    }
    return names
}

如果只是想知道输入是否会变化,调用前的 hasAdjacentEqual 更直接;如果最终一定要得到压缩后的数据,则以 slices.Compact 返回值的长度为准。两者不要混成一个“检查函数”,否则容易把预判结果误当成处理结果。

别忽略 nil、空切片和元素类型

文档明确说明,Compact 会保留输入的 nil 性质。也就是说,nil 切片经过处理后仍然是 nil;非 nil 的空切片也不会凭空变成 nil。这一点在接口序列化、缓存命中和“未提供”与“提供但为空”的业务语义中很重要。

// 这个例子只展示 nil 性质的判断,不把 nil 和空切片混为一谈。
func sameNilness() {
    var nilItems []int
    emptyItems := make([]int, 0)

    nilItems = slices.Compact(nilItems)
    emptyItems = slices.Compact(emptyItems)

    fmt.Println(nilItems == nil)   // nil 输入仍保持 nil。
    fmt.Println(emptyItems == nil) // 非 nil 空切片仍不是 nil。
}

此外,Compact 要求元素类型满足 comparable,因为它需要用相等运算判断相邻值。包含切片、映射或函数字段的复杂对象不能直接作为元素类型调用;这时应先定义业务等价关系,再考虑 slices.CompactFunc 是否适合。

理解底层数组复用带来的尾部边界

压缩通常会复用原切片的底层数组。返回值的 len 变短,cap 可能仍然保留;新长度之后的元素会被清零。调用方应该只通过返回切片访问有效数据,不要把原容量当成结果长度。

Compact 返回长度与底层数组复用和尾部清零的关系说明图
图2:Compact 返回切片与底层数组复用关系的说明图,不是运行截图。
// 只把 returned 当作有效范围,避免误读底层数组尾部。
func compactTags(tags []string) []string {
    tags = slices.Compact(tags)
    // tags[:len(tags)] 才是业务有效数据;不要按 cap(tags) 遍历。
    return tags
}

如果尾部元素曾经持有指针或较大的对象引用,清零可以帮助释放这些不再属于结果的引用。但这不代表压缩后就一定分配了新数组,也不代表原切片的所有别名都会同步缩短;多个切片视图仍可能指向同一个底层数组。

一张表判断是否满足使用边界

输入情况是否属于 Compact 的处理范围处理建议
nil 或空切片可以调用,结果长度不变保留 nil 与空值语义
只有一个元素可以调用,结果长度不变无需为压缩额外分支
存在相邻相等元素满足,结果可能变短接住返回值并使用新长度
重复值不相邻不满足全局去重目标先重新组织数据或选择其他算法
元素不可比较不能直接调用 Compact定义等价函数后评估 CompactFunc

实际工程里,可以把判断顺序固定为:先确认目标是“压缩连续重复”,再检查元素是否可比较,最后接住返回值并只按新长度读取。这样既不会误删分散重复,也不会因为原切片容量还在就把尾部当成有效记录。

常见问题

slices.Compact 会自动排序吗?

不会。它只比较相邻元素,输入顺序不会被重新排列。

为什么调用后原切片看起来还有旧元素?

原变量的长度不会自动更新,或者你观察的是同一底层数组的其他切片视图。业务代码应使用函数返回值,并按新长度访问。

不相邻的重复值怎样删除?

先确定是否需要保序,再选择哈希集合、排序后压缩或其他专门算法;不要把 Compact 的相邻语义误当成全局去重。

什么时候考虑 CompactFunc?

当元素本身不可直接用 == 表达业务等价关系时,可以先设计比较函数,再确认连续相等的语义仍然符合需求。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>