1.前言

开发过程中,我们经常需要对元素进行排序,使用 Go 我们可以轻松实现。

Go 内置 sort 包中提供了根据一些排序函数可对任何序列进行排序,并提供自定义排序规则的能力。

sort 包实现了四种基本排序算法:插入排序(Shell 排序)、归并排序、堆排序和快速排序。 但是这四种排序方法是不公开的,它们只被用于 sort 包内部使用,sort 包会根据实际数据自动选择高效的排序算法。

Go sort 包主要提供了三种排序能力:
(1)基本类型切片排序;
(2)自定义比较器;
(3)排序任意数据结构。

三者易用程度逐渐降低。

2.基本类型切片排序

[]int切片[]float64切片[]string
func Ints(x []int)
func Float64s(x []float64)
func Strings(x []string)

示例:

package main

import (
	"fmt"
	"sort"
)

func main() {
	// []int 排序
	slInt := []int{5, 2, 6, 3, 1, 4} // unsorted
	sort.Ints(slInt)
	fmt.Println(slInt) // 输出 [1 2 3 4 5 6]
	
	// []float64 排序
	slF64 := []float64{5.2, -1.3, 0.7, -3.8, 2.6} // unsorted
	sort.Float64s(slF64)
	fmt.Println(slF64)	// 输出 [-3.8 -1.3 0.7 2.6 5.2]
	
	// []string 字典序
	slStr := []string{"Go", "Bravo", "Gopher", "Alpha", "Grin", "Delta"}
	sort.Strings(slStr)
	fmt.Println(slStr) // 输出 [Alpha Bravo Delta Go Gopher Grin]
}

3.自定义比较器

使用 sort.Slice() 函数可以排序任意类型的切片,但是需要用户提供函数来判断元素大小关系,函数类型为func(i, j int) bool,其中参数 i, j 是序列中的索引。

func Slice(x interface{}, less func(i, j int) bool)

这个用来比较大小的函数就是比较器,返回值 true 表示下标 i 的元素小于下表 j 的元素。如果是降序排列的话,可以理解为返回 true 表示下标 i 的元素排在下标 j 的元素前面。

如果想稳定排序的话,使用 sort.SliceStable(),在排序切片时会保留相等元素的原始顺序。

按照年龄升序排序的示例

func main() {
	slStdnt := []struct {
		Name   string
		Age    int
		Height int
	}{
		{"Alice", 23, 175},
		{"David", 18, 185},
		{"Eve", 18, 165},
		{"Bob", 25, 170},
	}

	// 用 age 排序,年龄相等的元素保持原始顺序
	sort.SliceStable(slStdnt, func(i, j int) bool {
		return slStdnt[i].Age < slStdnt[j].Age
	})
	fmt.Println(slStdnt)
}

运行输出:

[{David 18 185} {Eve 18 165} {Alice 23 175} {Bob 25 170}]

如果想要在年龄相等的情况下再按照身高排序,我们修改一下比较器即可。

// 优先按照年龄排,年龄相等的话再按照身高排
sort.SliceStable(slStdnt, func(i, j int) bool {
	if slStdnt[i].Age < slStdnt[j].Age {
		return true
	} 
	if slStdnt[i].Age > slStdnt[j].Age {
		return false
	}
	return slStdnt[i].Height < slStdnt[j].Height
})

运行输出:

[{Eve 18 165} {David 18 185} {Alice 23 175} {Bob 25 170}]

4.排序任意数据结构

使用 sort.Sort() 或者 sort.Stable() 函数可完成对任意类型元素的排序。

一个内置的排序算法需要知道三个东西:序列的长度,表示两个元素比较的结果,一种交换两个元素的方式;这就是 sort.Interface 的三个方法:

type Interface interface {
    Len() int
    Less(i, j int) bool // i, j 是元素的索引
    Swap(i, j int)
}

这种方法相较于前面介绍的两种排序,用起来稍微麻烦了点,因为需要用户自定义的东西更多了,不过带来的好处也是显而易见的,更加普适。

还是以学生排序为例,在自定义的结构体上实现 srot.Interface 接口。

type Student struct {
	Name   string
	Age    int
	Height int
}

// ByAgeHeight 实现 sort.Interface 接口
type ByAgeHeight []Student

func (a ByAgeHeight) Len() int { return len(a) }

// Less 先用年龄排序,年龄相等再用身高排
func (a ByAgeHeight) Less(i, j int) bool {
	if a[i].Age < a[j].Age {
		return true
	}
	if a[i].Age > a[j].Age {
		return false
	}
	return a[i].Height < a[j].Height
}
func (a ByAgeHeight) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

func main() {
	slStdnt := []Student{
		{"Alice", 23, 175},
		{"David", 18, 185},
		{"Eve", 18, 165},
		{"Bob", 25, 170},
	}
	sort.Stable(ByAgeHeight(slStdnt))
	fmt.Println(slStdnt)
}

运行输出:

[{Eve 18 165} {David 18 185} {Alice 23 175} {Bob 25 170}]

5.小结

Go sort 包提供的排序能力丰富易用,掌握上面三种排序的用法,便可轻松应对大部分排序场景。

sort 包除了提供以上三种排序能力,还为我们提供了其他功能,比如判断序列是否有序、在有序序列中通过二分查找指定元素下标,反转有序序列获得逆序等,具体用法参见官方文档 Package sort。