6.2 Go 切片自定义排序学习笔记

6.2 Go 切片自定义排序学习笔记

1. sort.Slice — 自定义排序函数

sort.Slice(slice, lessFunc) 用自定义的比较函数对切片排序,lessFunc 返回 true 表示 i 应排在 j 前面:

package main

import (
    "fmt"
    "sort"
)

type Person struct {
    Name   string
    Age    int
    Salary int
}

func printPeople(people []Person) {
    for _, p := range people {
        fmt.Printf("  %s (Age: %d, Salary: $%d)\n", p.Name, p.Age, p.Salary)
    }
}

func main() {
    people := []Person{
        {"Alice", 28, 75000},
        {"Bob", 32, 85000},
        {"Charlie", 25, 65000},
        {"Diana", 29, 90000},
        {"Eve", 25, 70000},
    }

    fmt.Println("Original people:")
    printPeople(people)

    // 按年龄升序:people[i].Age < people[j].Age → i 排前面
    sort.Slice(people, func(i, j int) bool {
        return people[i].Age < people[j].Age
    })
    fmt.Println("\nSorted by age (ascending):")
    printPeople(people)

    // 按薪资降序:people[i].Salary > people[j].Salary → i 排前面(大在前=降序)
    sort.Slice(people, func(i, j int) bool {
        return people[i].Salary > people[j].Salary
    })
    fmt.Println("\nSorted by salary (descending):")
    printPeople(people)

    // 按名字字典序:people[i].Name < people[j].Name → i 排前面
    sort.Slice(people, func(i, j int) bool {
        return people[i].Name < people[j].Name
    })
    fmt.Println("\nSorted by name (alphabetical):")
    printPeople(people)
}

执行结果:

Original people:
  Alice (Age: 28, Salary: $75000)
  Bob (Age: 32, Salary: $85000)
  Charlie (Age: 25, Salary: $65000)
  Diana (Age: 29, Salary: $90000)
  Eve (Age: 25, Salary: $70000)

Sorted by age (ascending):
  Charlie (Age: 25, Salary: $65000)
  Eve (Age: 25, Salary: $70000)
  Alice (Age: 28, Salary: $75000)
  Diana (Age: 29, Salary: $90000)
  Bob (Age: 32, Salary: $85000)

Sorted by salary (descending):
  Diana (Age: 29, Salary: $90000)
  Bob (Age: 32, Salary: $85000)
  Alice (Age: 28, Salary: $75000)
  Eve (Age: 25, Salary: $70000)
  Charlie (Age: 25, Salary: $65000)

Sorted by name (alphabetical):
  Alice (Age: 28, Salary: $75000)
  Bob (Age: 32, Salary: $85000)
  Charlie (Age: 25, Salary: $65000)
  Diana (Age: 29, Salary: $90000)
  Eve (Age: 25, Salary: $70000)

要点

  • sort.Slice(slice, func(i, j int) bool { ... }) — 两个参数:切片 + 比较函数
  • 比较函数接收两个索引 i, j,返回 true 表示 slice[i] 应排在 slice[j] 前面
  • 升序:<(小的排前面)— people[i].Age < people[j].Age
  • 降序:>(大的排前面)— people[i].Salary > people[j].Salary
  • 字典序:字符串的 < 比较按字母顺序 — people[i].Name < people[j].Name
  • sort.Slice不稳定排序:相等元素的相对顺序可能改变(见知识点 2)

2. sort.SliceStable — 稳定排序(保持相等元素的原始顺序)

sort.SliceStable 在排序时保持相等元素的原始相对顺序:

package main

import (
    "fmt"
    "sort"
)

type Person struct {
    Name   string
    Age    int
    Salary int
}

func printPeople(people []Person) {
    for _, p := range people {
        fmt.Printf("  %s (Age: %d, Salary: $%d)\n", p.Name, p.Age, p.Salary)
    }
}

func main() {
    // 先按名字排序,建立初始顺序
    people := []Person{
        {"Alice", 28, 75000},
        {"Bob", 32, 85000},
        {"Charlie", 25, 65000},
        {"Diana", 29, 90000},
        {"Eve", 25, 70000},
    }

    // 多级排序:先按年龄,年龄相同则按薪资降序
    sort.SliceStable(people, func(i, j int) bool {
        if people[i].Age == people[j].Age {
            return people[i].Salary > people[j].Salary // 同年龄,薪资高排前面
        }
        return people[i].Age < people[j].Age // 不同年龄,年龄小排前面
    })

    fmt.Println("Sorted by age, then salary (stable sort):")
    printPeople(people)
}

执行结果:

Sorted by age, then salary (stable sort):
  Eve (Age: 25, Salary: $70000)
  Charlie (Age: 25, Salary: $65000)
  Alice (Age: 28, Salary: $75000)
  Diana (Age: 29, Salary: $90000)
  Bob (Age: 32, Salary: $85000)

要点

  • sort.SliceStablesort.Slice 语法相同,但保证相等元素的原始相对顺序不变
  • 多级排序模式:if 纀级1相等 { return 瀑级2比较 } else { return 瀑级1比较 }
  • 本例:Charlie(25, 65000) 和 Eve(25,70000) 年龄相同,按薪资降序 → Eve 排前面
  • 对比:如果用 sort.Slice(不稳定),Charlie 和 Eve 的相对顺序可能颠倒
  • 何时用 SliceStable:需要保持相等元素的相对顺序时(如先按A排序再按B排序的场景)

3. 复杂自定义排序 — 分类优先 + 二级排序

比较函数可以包含任意复杂的逻辑:

package main

import (
    "fmt"
    "sort"
)

type Person struct {
    Name   string
    Age    int
    Salary int
}

func printPeople(people []Person) {
    for _, p := range people {
        fmt.Printf("  %s (Age: %d, Salary: $%d)\n", p.Name, p.Age, p.Salary)
    }
}

func main() {
    people := []Person{
        {"Alice", 28, 75000},
        {"Bob", 32, 85000},
        {"Charlie", 25, 65000},
        {"Diana", 29, 90000},
        {"Eve", 25, 70000},
    }

    // 自定义排序:30岁以下优先,然后按薪资降序
    sort.Slice(people, func(i, j int) bool {
        under30i := people[i].Age < 30
        under30j := people[j].Age < 30

        if under30i && !under30j {
            return true  // i <30, j >=30 → i 排前面
        }
        if !under30i && under30j {
            return false // i >=30, j <30 → j 排前面
        }
        // 同类别(都<30或都>=30),按薪资降序
        return people[i].Salary > people[j].Salary
    })

    fmt.Println("Custom sort (under 30s first, then by salary desc):")
    printPeople(people)
}

执行结果:

Custom sort (under 30s first, then by salary desc):
  Diana (Age: 29, Salary: $90000)
  Alice (Age: 28, Salary: $75000)
  Eve (Age: 25, Salary: $70000)
  Charlie (Age: 25, Salary: $65000)
  Bob (Age: 32, Salary: $85000)

要点

  • 分类优先:先判断是否<30(布尔分组),<30的人排在前面
  • 同组内再排序:同为<30的人按薪资降序,同为>=30的人也按薪资降序
  • 4个30岁以下的人(Diana, Alice, Eve, Charlie)排在前面,按薪资降序
  • Bob(32岁,>=30)排在最后,即使薪资 85000 > Charlie 的65000
  • 这是"分类排序"模式:先分大类,大类内再细排,常见于分组展示场景

4. 字符串多级排序 — 先按长度再按字典序

package main

import (
    "fmt"
    "sort"
    "strings"
)

func main() {
    words := []string{"apple", "pie", "banana", "cat", "elephant", "dog", "a"}
    fmt.Printf("Original words: %v\n", words)

    // 先按长度排序,长度相同则按字典序
    sort.Slice(words, func(i, j int) bool {
        if len(words[i]) == len(words[j]) {
            return strings.ToLower(words[i]) < strings.ToLower(words[j])
        }
        return len(words[i]) < len(words[j])
    })

    fmt.Printf("Sorted by length, then alphabetically: %v\n", words)
}

执行结果:

Original words: [apple pie banana cat elephant dog a]
Sorted by length, then alphabetically: [a cat dog pie apple banana elephant]

要点

  • 多级排序模式:if 瀑级1相等 { return 瀑级2比较 } else { return 瀑级1比较 }
  • 先按长度:len(words[i]) < len(words[j]) — 短的排前面
  • 长度相同按字典序:strings.ToLower(words[i]) < strings.ToLower(words[j]) — 大小写不敏感
  • 结果:a(1) → cat/dog(3,字典序) → pie(3) → apple(5) → banana(6) → elephant(8)
  • 注意 cat 和 dog 长度都是3,按字典序 cat < dog

5. 按绝对值排序 — 自定义数值排序

package main

import (
    "fmt"
    "sort"
)

func abs(x int) int {
    if x < 0 {
        return -x
    }
    return x
}

func main() {
    numbers := []int{-5, 3, -1, 8, -10, 2, -3}
    fmt.Printf("Original numbers: %v\n", numbers)

    sort.Slice(numbers, func(i, j int) bool {
        return abs(numbers[i]) < abs(numbers[j])
    })

    fmt.Printf("Sorted by absolute value: %v\n", numbers)
}

执行结果:

Original numbers: [-5 3 -1 8 -10 2 -3]
Sorted by absolute value: [-1 2 3 -3 -5 8 -10]

要点

  • abs() 辅助函数计算绝对值,在比较函数中调用
  • abs(numbers[i]) < abs(numbers[j]) — 按绝对值升序排
  • 结果:|-1|=1 → |2|=2 → |3|=3 → |-3|=3 → |-5|=5 → |8|=8 → |-10|=10
  • 3 和 -3 绝对值都是3,相对顺序可能不同(sort.Slice 不稳定)
  • 自定义排序不限于简单比较,可以在 less 函数中做任意计算

6. sort.SliceIsSorted — 检查是否已按自定义规则排序

package main

import (
    "fmt"
    "sort"
)

func main() {
    ages := []int{25, 30, 35, 40, 45}

    isSorted := sort.SliceIsSorted(ages, func(i, j int) bool {
        return ages[i] <= ages[j] // 升序规则(注意用 <= 而不是 <)
    })
    fmt.Printf("Ages %v is sorted: %t\n", ages, isSorted)
}

执行结果:

Ages [25 30 35 40 45] is sorted: true

要点

  • sort.SliceIsSorted(slice, lessFunc) — 检查切片是否已按自定义规则排序
  • 比较函数与 sort.Slice 使用相同的逻辑:ages[i] <= ages[j] 表示升序
  • 注意用 <= 而不是 <<= 允许相等元素,< 会认为相等元素是"未排序"
  • %t — 格式化布尔值为 "true" 或 "false"
  • 返回 true 表示切片已按该规则排序,false 表示未排序

知识点总结

知识点 关键概念
sort.Slice sort.Slice(slice, func(i,j) bool) — 自定义比较函数排序
升序/降序 升序 <,降序 >,字典序用字符串 <
sort.SliceStable 稳定排序,相等元素保持原始相对顺序
多级排序 if 瀑级1相等 { 瀑级2比较 } else { 瀑级1比较 }
分类排序 先分组(布尔条件),组内再排序
自定义计算排序 less 函数中可调用辅助函数(如 abs)
SliceIsSorted sort.SliceIsSorted(slice, lessFunc) 检查是否已排序,用 <=
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容