Golang 切片.CompactFunc
最后修改于 2025 年 4 月 20 日
本教程将介绍如何在 Go 中使用 `slices.CompactFunc` 函数。我们将通过压缩切片的实际示例来讲解切片操作。
`slices.CompactFunc` 函数使用自定义比较函数,将连续的相等元素替换为单个副本。它是 Go 的 `slices` 包的一部分。
当需要自定义逻辑来判断相等性时,此函数对于从已排序的切片中删除重复项非常有用。它会就地修改切片并返回新的长度。
基本的 slices.CompactFunc 示例
`slices.CompactFunc` 最简单的用法是移除整数切片中连续的重复项。我们定义一个用于相等性比较的函数。
package main
import (
"fmt"
"slices"
)
func main() {
numbers := []int{1, 1, 2, 3, 3, 3, 4, 5, 5}
unique := slices.CompactFunc(numbers, func(a, b int) bool {
return a == b
})
fmt.Println("Compacted slice:", numbers[:unique])
}
我们创建一个包含连续重复项的切片并移除它们。比较函数会检查相邻元素之间的相等性。
不区分大小写的字符串压缩
`slices.CompactFunc` 可以不区分大小写地压缩字符串。此示例将连续的不同大小写视为相等。
package main
import (
"fmt"
"slices"
"strings"
)
func main() {
words := []string{"apple", "Apple", "banana", "BANANA", "cherry"}
unique := slices.CompactFunc(words, func(a, b string) bool {
return strings.EqualFold(a, b)
})
fmt.Println("Case-insensitive compact:", words[:unique])
}
比较使用 `strings.EqualFold` 进行不区分大小写的相等性判断。大小写不同的连续单词被视为重复项。
压缩结构体切片
我们可以将 `slices.CompactFunc` 与自定义结构体类型一起使用。此示例根据坐标压缩点切片。
package main
import (
"fmt"
"slices"
)
type Point struct {
X, Y int
}
func main() {
points := []Point{
{1, 2}, {1, 2}, {3, 4}, {3, 4}, {3, 4}, {5, 6},
}
unique := slices.CompactFunc(points, func(a, b Point) bool {
return a.X == b.X && a.Y == b.Y
})
fmt.Println("Unique points:", points[:unique])
}
该函数同时检查 X 和 Y 坐标的相等性。连续的重复点将从切片中移除。
使用自定义逻辑进行压缩
可以在函数中实现复杂的比较逻辑。此示例压缩数字,如果它们的差值小于 0.5,则认为它们相等。
package main
import (
"fmt"
"math"
"slices"
)
func main() {
numbers := []float64{1.0, 1.2, 1.6, 2.0, 2.1, 2.9, 3.0}
unique := slices.CompactFunc(numbers, func(a, b float64) bool {
return math.Abs(a-b) < 0.5
})
fmt.Println("Approximately unique:", numbers[:unique])
}
比较使用浮点数数学来确定近似相等性。接近的数字被视为重复项。
空切片行为
`slices.CompactFunc` 可以很好地处理空切片。此示例展示了它在空切片和 nil 切片上的行为。
package main
import (
"fmt"
"slices"
)
func main() {
var empty []int
nilSlice := []string(nil)
emptyResult := slices.CompactFunc(empty, func(a, b int) bool {
return a == b
})
nilResult := slices.CompactFunc(nilSlice, func(a, b string) bool {
return a == b
})
fmt.Println("Empty slice result:", emptyResult)
fmt.Println("Nil slice result:", nilResult)
}
空切片和 nil 切片都返回 0 作为新长度。由于没有元素可以压缩,原始切片保持不变。
性能注意事项
对于大型切片,比较函数的性能很重要。此示例对不同的比较方法进行了基准测试。
package main
import (
"fmt"
"slices"
"time"
)
func main() {
largeSlice := make([]int, 1_000_000)
for i := range largeSlice {
largeSlice[i] = i % 10 // Create many duplicates
}
// Simple comparison
start := time.Now()
_ = slices.CompactFunc(largeSlice, func(a, b int) bool {
return a == b
})
fmt.Println("Simple comparison:", time.Since(start))
// Complex comparison
start = time.Now()
_ = slices.CompactFunc(largeSlice, func(a, b int) bool {
return a%2 == b%2 // Group by even/odd
})
fmt.Println("Complex comparison:", time.Since(start))
}
执行时间取决于比较的复杂性。`slices.CompactFunc` 顺序处理元素,就地修改切片。
实际示例:去重日志条目
此实际示例使用 `slices.CompactFunc` 来移除连续重复的日志条目,同时保留原始顺序。
package main
import (
"fmt"
"slices"
"strings"
)
type LogEntry struct {
Timestamp string
Message string
}
func main() {
logs := []LogEntry{
{"2023-01-01T10:00:00", "System started"},
{"2023-01-01T10:00:05", "User logged in"},
{"2023-01-01T10:00:05", "User logged in"}, // Duplicate
{"2023-01-01T10:01:00", "File saved"},
{"2023-01-01T10:01:00", "File saved"}, // Duplicate
{"2023-01-01T10:02:00", "System shutdown"},
}
unique := slices.CompactFunc(logs, func(a, b LogEntry) bool {
return a.Timestamp == b.Timestamp &&
strings.EqualFold(a.Message, b.Message)
})
fmt.Println("Deduplicated logs:")
for _, log := range logs[:unique] {
fmt.Printf("%s: %s\n", log.Timestamp, log.Message)
}
}
我们同时比较时间戳和消息(不区分大小写)来识别重复项。结果保留了每个唯一日志条目的第一个出现。
来源
本教程通过在各种场景下使用自定义比较逻辑压缩切片的实际示例,涵盖了 Go 中的 `slices.CompactFunc` 函数。