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` 函数。