ZetCode

Golang 切片.CompactFunc

最后修改于 2025 年 4 月 20 日

本教程将介绍如何在 Go 中使用 `slices.CompactFunc` 函数。我们将通过压缩切片的实际示例来讲解切片操作。

`slices.CompactFunc` 函数使用自定义比较函数,将连续的相等元素替换为单个副本。它是 Go 的 `slices` 包的一部分。

当需要自定义逻辑来判断相等性时,此函数对于从已排序的切片中删除重复项非常有用。它会就地修改切片并返回新的长度。

基本的 slices.CompactFunc 示例

`slices.CompactFunc` 最简单的用法是移除整数切片中连续的重复项。我们定义一个用于相等性比较的函数。

basic_compact.go
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` 可以不区分大小写地压缩字符串。此示例将连续的不同大小写视为相等。

case_insensitive.go
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` 与自定义结构体类型一起使用。此示例根据坐标压缩点切片。

struct_compact.go
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,则认为它们相等。

custom_logic.go
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 切片上的行为。

empty_slice.go
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 作为新长度。由于没有元素可以压缩,原始切片保持不变。

性能注意事项

对于大型切片,比较函数的性能很重要。此示例对不同的比较方法进行了基准测试。

performance.go
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` 来移除连续重复的日志条目,同时保留原始顺序。

log_deduplication.go
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 实验性切片包文档

本教程通过在各种场景下使用自定义比较逻辑压缩切片的实际示例,涵盖了 Go 中的 `slices.CompactFunc` 函数。

作者

我叫 Jan Bodnar,是一名充满激情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在教授编程方面拥有十多年的经验。

列出所有 Go 教程