golang自定义sort及延展知识?

golang自定义sort及延展知识?

背景介绍

初来乍到

刚入门golang的时候,总是不知道怎么才能实现自定义类型的排序。

这几天看leader面试别人,时不时也会问到排序的问题,看来还是很重要的。

这篇小文章,一起小结下自定义类型的排序问题。

本文摘要

  • 自定义排序的实现;
  • 先按A规则排序再按B规则排序的实现技巧;
  • 三剑客原理回顾;

问题引入

如何对下面的personArr实现先按Height排序、再按Age排序呢?

type Person struct {
    Name string
    Age int
    Height int
}

personArr := make([]Person, 0)

三剑客

初识三剑客

最通用的是下面这个函数:

sort.Sort(data)

跳转到该函数的实现:

func Sort(data Interface) {
    n := data.Len()
    quickSort(data, 0, n, maxDepth(n))
}

在看下Interface的定义:

type Interface interface {
    // Len is the number of elements in the collection.
    Len() int
    // Less reports whether the element with
    // index i should sort before the element with index j.
    Less(i, j int) bool
    // Swap swaps the elements with indexes i and j.
    Swap(i, j int)
}

从定义可知,只要传入的data实现了 Len、Less、Swap 三个函数就可以直接调用sort方法了!

show me code

回到最上面自定义排序的问题,实现如下:

type []Person PersonArr

func (p PersonArr) Len() int {
    return len(p)
}

// 下面算是一个小技巧
// 先按Height排序、再按Age排序
func (p PersonArr) Less(i, j int) bool {
    if p[i].Height != p[j].Height {
        return p[i].Height < p[j].Height
    }

    return p[i].Age < p[j].Age
}

func (p PersonArr) Swap(i, j int) {
    tmp := p[i]
    p[i] = p[j]
    p[j] = tmp
}

调用示例

简单使用

personArr := PersonArr{                      
    Person{                                  
        Name: "h180_a30",                    
        Age: 30,                             
        Height: 180,                         
    },                                       
    Person{                                  
        Name: "h170_a35",                    
        Age: 35,                             
        Height: 170,                         
    },                                       
    Person{                                  
        Name: "h180_a25",                    
        Age: 25,                             
        Height: 180,                         
    },                                       
    }                                        
                                             
                                             
fmt.Printf("%+v", personArr)

运行结果

[{Name:h180_a30 Age:30 Height:180} {Name:h170_a35 Age:35 Height:170} {Name:h180_a25 Age:25 Height:180}]

举一反三

其他类型的排序,也和上面的大同小异了。

再挖一层

源码跟读

为什么是这三个函数,这个就要看golang中sort的实现了,

func Sort(data Interface) {
    n := data.Len()
    quickSort(data, 0, n, maxDepth(n))
}

func quickSort(data Interface, a, b, maxDepth int) {
    for b-a > 12 { // Use ShellSort for slices <= 12 elements
        if maxDepth == 0 {
            heapSort(data, a, b)
            return
        }
        maxDepth--
        mlo, mhi := doPivot(data, a, b)
        // Avoiding recursion on the larger subproblem guarantees
        // a stack depth of at most lg(b-a).
        if mlo-a  1 {
        // Do ShellSort pass with gap 6
        // It could be written in this simplified form cause b-a <= 12
        for i := a + 6; i < b; i++ {
            if data.Less(i, i-6) {
                data.Swap(i, i-6)
            }
        }
        insertionSort(data, a, b)
    }
}

从上面的代码可以看出,quickSort并不是只实现了快速排序,

而是根据实际数据灵活的选择 快速排序、堆排序、插入排序 的;

why三剑客

上面代码中最主要的篇幅是和快速排序相关的了,所以我们也简单回顾下快速排序的原理:

  • 首先设定一个分界值;
  • 将大于或等于分界值的数据集中到数组右边,小于分界值的数据集中到数组的左边。
  • 左右两边的子数组也同样通过上面两个步骤排好序;(递归的思想)
  • 将左边排序数组、分界值、右边排序数组三者连接起来,排序完成。

上面的步骤,必然会用到 比较、交换、数组长度 这三个基本要素,所以就必须实现三剑客了。

注:详细以及拓展阅读请参阅维基百科的介绍。

开箱即用

golang默认实现的排序有哪些?如下几个可以开箱即用:

sort.Slice()
sort.Float64s()
sort.Ints()
sort.Strings()

喜欢的话,关注我的公众号哦

image.png

本公众号关注golang、程序员出路、开心也是一种能力一种需要等。