sort package of Go language standard library

Five basic sorting algorithms are implemented in the sort package: insert sort, Hill sort, heap sort, quick sort and merge sort. Like other languages, these five algorithms are not public and are only used inside the sort package.

sort.Interface interface

The types that implement sort.interface (generally a collection) can be sorted using the functions in the sort package. The definition of sort.interface interface is as follows:

type Interface interface {
    // The Len method returns the number of elements in the collection
    Len() int
    // The Less method reports whether the element of index i is smaller than the element of index j
    Less(i, j int) bool
    // The Swap method swaps the two elements of indexes i and j
    Swap(i, j int)
}

Custom type sorting

Sort/Stable function

// Sorting data does not guarantee stability, that is, the relative order of equal elements may change
// Sort calls data.Len once to determine the length, and O(n*log(n)) calls data.Less and data.Swap
func Sort(data Interface)

// Sort data to ensure the stability of sorting, that is, the relative order of equal elements remains unchanged
// Sort calls data.Len once, O(n*log(n)) times data.Less and O(n*log(n)*log(n)) times data.Swap
func Stable(data Interface)

// Wrap a sort.Interface interface interface and return a new sort.Interface interface, which can be sorted to generate a decreasing sequence
func Reverse(data Interface) Interface

// Determine whether the data has been sorted
func IsSorted(data Interface) bool

The example code is as follows:

type Student struct {
    Name string
    Age  int
}

type StudentSlice []Student

// Implement the sort.Interface interface
func (ss StudentSlice) Len() int {
    return len(ss)
}

func (ss StudentSlice) Less(i, j int) bool {
    return ss[i].Age < ss[j].Age
}

func (ss StudentSlice) Swap(i, j int) {
    ss[i], ss[j] = ss[j], ss[i]
}

func main() {
    ss := StudentSlice{
        {"Alice", 18},
        {"Bob", 20},
        {"Allen", 16},
        {"Michelle", 18},
        {"James", 24},
    }
    // In ascending order of age
    sort.Sort(ss)
    fmt.Println(ss)                // [{Allen 16} {Alice 18} {Michelle 18} {Bob 20} {James 24}]
    fmt.Println(sort.IsSorted(ss)) // true

    // In descending order of age
    sort.Sort(sort.Reverse(ss))
    fmt.Println(ss)                // [{James 24} {Bob 20} {Alice 18} {Michelle 18} {Allen 16}]
    fmt.Println(sort.IsSorted(ss)) // false
}

Slice/SliceStable function

In the sorting of the previous Sort/Stable functions, slices of custom types need to implement the sort.Interface interface, which is not very concise in code. Go provides Slice/SliceStable functions. You only need to pass in a slice to be sorted and a callback function to sort. Note that except for the following three functions, the input parameter object x interface {} of all other functions in the sort package needs to implement the sort.Interface interface. The reason will be analyzed later.

// Using the rules defined by the less function to sort slice X does not guarantee the stability of sorting. If x is not a slice, it will report panic
func Slice(x interface{}, less func(i, j int) bool)

// Use the rules defined by the less function to sort slice x to ensure the stability of sorting. If x is not a slice, it will report panic
func SliceStable(x interface{}, less func(i, j int) bool)

// Judge whether the slice x is ordered according to the rules defined by the less function. If x is not a slice, panic will be reported
func SliceIsSorted(x interface{}, less func(i, j int) bool)

The example code is as follows:

type Student struct {
    Name string
    Age  int
}

func main() {
    ss := []Student{
        {"Alice", 18},
        {"Bob", 20},
        {"Allen", 16},
        {"Michelle", 18},
        {"James", 24},
    }

    less := func(i, j int) bool {
        return ss[i].Age < ss[j].Age
    }
    // In ascending order of age
    sort.Slice(ss, less)
    fmt.Println(ss)                           // [{Allen 16} {Alice 18} {Michelle 18} {Bob 20} {James 24}]
    fmt.Println(sort.SliceIsSorted(ss, less)) // true
}

Basic data type sorting

For slices of int, float64 and string types, Go language encapsulates them, and you can directly call the sort package function for sorting.

int type sort

// Under the sort package, the Go language defines the IntSlice type and implements the sort.Interface interface for [] int sorting
type IntSlice []int

// Implement Interface
func (x IntSlice) Len() int           { return len(x) }
func (x IntSlice) Less(i, j int) bool { return x[i] < x[j] }
func (x IntSlice) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }

// Equivalent call sort.Sort(x)
func (x IntSlice) Sort() { Sort(x) }

// Equivalent call sort.SearchInts(x, p)
func (x IntSlice) Search(p int) int { return SearchInts(x, p) }
// Sort x in ascending order
func Ints(x []int)

// Determine whether x is in ascending order
func IntsAreSorted(x []int)

// Search for x in a in ascending order and return the index of x
// If it cannot be found, the return value is the position where x should be inserted into a (to ensure the increasing order of a), and the return value can be len(a)
func SearchInts(a []int, x int) int

The example code is as follows:

func main() {
    // Ascending mode 1:
    intList1 := []int{2, 0, 1, 8, 0, 9, 2, 4, 1, 2}
    sort.Ints(intList1)
    fmt.Println(intList1) // [0 0 1 1 2 2 2 4 8 9]
    // Ascending mode 2:
    intList2 := []int{2, 0, 1, 8, 0, 9, 2, 4, 1, 2}
    sort.Sort(sort.IntSlice(intList2))
    fmt.Println(intList2) // [0 0 1 1 2 2 2 4 8 9]
    // Ascending method 3:
    intList3 := []int{2, 0, 1, 8, 0, 9, 2, 4, 1, 2}
    (sort.IntSlice(intList3)).Sort()
    fmt.Println(intList3) // [0 0 1 1 2 2 2 4 8 9]

    // Descending order:
    intList4 := []int{2, 0, 1, 8, 0, 9, 2, 4, 1, 2}
    sort.Sort(sort.Reverse(sort.IntSlice(intList4)))
    fmt.Println(intList4) // [9 8 4 2 2 2 1 1 0 0]

    intList5 := []int{0, 0, 1, 1, 2, 2, 2, 4, 8, 9}
    // Search method 1:
    fmt.Println(sort.SearchInts(intList5, 2)) // 4
    // Search method 2:
    fmt.Println(sort.IntSlice(intList5).Search(2)) // 4
}

float64 type sorting

// Under the sort package, the Go language defines the Float64Slice type and implements the sort.Interface interface for [] float64 sorting
type Float64Slice []float64

// Implement Interface
func (x Float64Slice) Len() int { return len(x) }
func (x Float64Slice) Less(i, j int) bool { return x[i] < x[j] || (isNaN(x[i]) && !isNaN(x[j])) }
func (x Float64Slice) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }

// Equivalent call sort.Sort(x)
func (x Float64Slice) Sort() { Sort(x) }

// Equivalent call sort.SearchFloat64s(x, p)
func (x Float64Slice) Search(p float64) int { return SearchFloat64s(x, p) }
// Sort x in ascending order
func Float64s(x []float64)

// Determine whether x is in ascending order
func Float64sAreSorted(x []float64)

// Search for x in a in ascending order and return the index of x
// If it cannot be found, the return value is the position where x should be inserted into a (to ensure the increasing order of a), and the return value can be len(a)
func SearchFloat64s(a []float64, x float64) int

The example code is as follows:

func main() {
    // Ascending mode 1:
    floatList1 := []float64{4.2, 5.9, 12.3, 10.0, 50.4, 99.9, 31.4, 27.81828, 3.14}
    sort.Float64s(floatList1)
    fmt.Println(floatList1) // [3.14 4.2 5.9 10 12.3 27.81828 31.4 50.4 99.9]
    // Ascending mode 2:
    floatList2 := []float64{4.2, 5.9, 12.3, 10.0, 50.4, 99.9, 31.4, 27.81828, 3.14}
    sort.Sort(sort.Float64Slice(floatList2))
    fmt.Println(floatList2) // [3.14 4.2 5.9 10 12.3 27.81828 31.4 50.4 99.9]
    // Ascending method 3:
    floatList3 := []float64{4.2, 5.9, 12.3, 10.0, 50.4, 99.9, 31.4, 27.81828, 3.14}
    (sort.Float64Slice(floatList3)).Sort()
    fmt.Println(floatList3) // [3.14 4.2 5.9 10 12.3 27.81828 31.4 50.4 99.9]

    // Descending order:
    floatList4 := []float64{4.2, 5.9, 12.3, 10.0, 50.4, 99.9, 31.4, 27.81828, 3.14}
    sort.Sort(sort.Reverse(sort.Float64Slice(floatList4)))
    fmt.Println(floatList4) // [99.9 50.4 31.4 27.81828 12.3 10 5.9 4.2 3.14]

    floatList5 := []float64{3.14, 4.2, 5.9, 10, 12.3, 27.81828, 31.4, 50.4, 99.9}
    // Search method 1:
    fmt.Println(sort.SearchFloat64s(floatList5, 12.3)) // 4
    // Search method 2:
    fmt.Println(sort.Float64Slice(floatList5).Search(12.3)) // 4
}

string type sorting

// Under the sort package, the Go language defines the StringSlice type and implements the sort.Interface interface for [] string sorting
type StringSlice []string

// Implement Interface
func (x StringSlice) Len() int           { return len(x) }
func (x StringSlice) Less(i, j int) bool { return x[i] < x[j] }
func (x StringSlice) Swap(i, j int)      { x[i], x[j] = x[j], x[i] }

// Equivalent call sort.Sort(x)
func (x StringSlice) Sort() { Sort(x) } 

// Equivalent call sort.SearchStrings(x, p)
func (x StringSlice) Search(p string) int { return SearchStrings(x, p) }
// Sort x in ascending order
func Strings(x []string)

// Determine whether x is in ascending order
func StringsAreSorted(x []string)

// Search for x in a in ascending order and return the index of x
// If it cannot be found, the return value is the position where x should be inserted into a (to ensure the increasing order of a), and the return value can be len(a)
func SearchStrings(a []string, x string) int

The example code is as follows:

func main() {
    // Ascending mode 1:
    stringList1 := []string{"o", "l", "d", "b", "o", "y", "g", "o", "l", "a", "n", "g"}
    sort.Strings(stringList1)
    fmt.Printf("%q\n", stringList1) // ["a" "b" "d" "g" "g" "l" "l" "n" "o" "o" "o" "y"]
    // Ascending mode 2:
    stringList2 := []string{"o", "l", "d", "b", "o", "y", "g", "o", "l", "a", "n", "g"}
    sort.Sort(sort.StringSlice(stringList2))
    fmt.Printf("%q\n", stringList2) // ["a" "b" "d" "g" "g" "l" "l" "n" "o" "o" "o" "y"]
    // Ascending mode 3:
    stringList3 := []string{"o", "l", "d", "b", "o", "y", "g", "o", "l", "a", "n", "g"}
    (sort.StringSlice(stringList3)).Sort()
    fmt.Printf("%q\n", stringList3) // ["a" "b" "d" "g" "g" "l" "l" "n" "o" "o" "o" "y"]

    // Descending order:
    stringList4 := []string{"o", "l", "d", "b", "o", "y", "g", "o", "l", "a", "n", "g"}
    sort.Sort(sort.Reverse(sort.StringSlice(stringList4)))
    fmt.Printf("%q\n", stringList4) // ["y" "o" "o" "o" "n" "l" "l" "g" "g" "d" "b" "a"]

    stringList5 := []string{"a", "b", "d", "g", "g", "l", "l", "n", "o", "o", "o", "y"}
    // Search method 1:
    fmt.Println(sort.SearchStrings(stringList5, "l")) // 5
    // Search method 2:
    fmt.Println((sort.StringSlice(stringList5)).Search("l")) // 5
}

Bottom implementation analysis

sort.Sort function

// Sort function is an unstable sort method, which uses one of three sorting methods: Hill quick sort, quick sort or heap sort
func Sort(data Interface) {
    n := data.Len()
    // maxDepth(n) returns 2*ceil(lg(n+1)). If the element depth reaches 2*ceil(lg(n+1)), heap sorting is selected
    quickSort(data, 0, n, maxDepth(n))
}
// quickSort will automatically select one of three sorting methods: quick, quick sort or heap sort according to the and element depth
func quickSort(data Interface, a, b, maxDepth int) {
    for b-a > 12 {
        if maxDepth == 0 { // Use ShellSort for slices <= 12 elements
            // Use heap sort
            heapSort(data, a, b)
            return
        }
        maxDepth--
        // Use three-dimensional segmentation quick sorting to partition the quick row through doPivot
        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 < b-mhi {
            quickSort(data, a, mlo, maxDepth)
            a = mhi // i.e., quickSort(data, mhi, b)
        } else {
            quickSort(data, mhi, b, maxDepth)
            b = mlo // i.e., quickSort(data, a, mlo)
        }
    }
    // Slice elements less than or equal to 12, using Hill sort (Hill sort is an efficient version of insert sort), with an interval of d=6
    if b-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)
    }
}

// Heap sort
func heapSort(data Interface, a, b int) {
    first := a
    lo := 0
    hi := b - a

    // Build heap with greatest element at top.
    for i := (hi - 1) / 2; i >= 0; i-- {
        siftDown(data, i, hi, first)
    }

    // Pop elements, largest first, into end of data.
    for i := hi - 1; i >= 0; i-- {
        data.Swap(first, first+i)
        siftDown(data, lo, i, first)
    }
}

// siftDown implements the heap property on data[lo:hi].
// first is an offset into the array where the root of the heap lies.
func siftDown(data Interface, lo, hi, first int) {
    root := lo
    for {
        child := 2*root + 1
        if child >= hi {
            break
        }
        if child+1 < hi && data.Less(first+child, first+child+1) {
            child++
        }
        if !data.Less(first+root, first+child) {
            return
        }
        data.Swap(first+root, first+child)
        root = child
    }
}

// Insert sort
// insertionSort sorts data[a:b] using insertion sort.
func insertionSort(data Interface, a, b int) {
    for i := a + 1; i < b; i++ {
        for j := i; j > a && data.Less(j, j-1); j-- {
            data.Swap(j, j-1)
        }
    }
}

sort.Stable function

// Stable is a stable sort, using merge sort
func Stable(data Interface) {
    stable(data, data.Len())
}
// The merge sort algorithm used here is an original address sort algorithm:
// First, it sorts slices by inserting each blockSize=20 elements into a slice
// Loop merge two adjacent blocks, and double the blockSize each time until blockSize > n
func stable(data Interface, n int) {
    // The initial blockSize is set to 20
    blockSize := 20 // must be > 0
    a, b := 0, blockSize
    // Insert sort each block (and one block with less than blockSize remaining)
    for b <= n {
        insertionSort(data, a, b)
        a = b
        b += blockSize
    }
    insertionSort(data, a, n)
    
    for blockSize < n {
        a, b = 0, 2*blockSize
        for b <= n {
            // Merge every two blocksizes
            symMerge(data, a, a+blockSize, b)
            a = b
            b += 2 * blockSize
        }
        // The remaining one or more blocksizes are merged
        if m := a + blockSize; m < n {
            symMerge(data, a, m, n)
        }
        blockSize *= 2
    }
}

func symMerge(data Interface, a, m, b int) {
    // Avoid unnecessary recursions of symMerge
    // by direct insertion of data[a] into data[m:b]
    // if data[a:m] only contains one element.
    if m-a == 1 {
        // Use binary search to find the lowest index i
        // such that data[i] >= data[a] for m <= i < b.
        // Exit the search loop with i == b in case no such index exists.
        i := m
        j := b
        for i < j {
            h := int(uint(i+j) >> 1)
            if data.Less(h, a) {
                i = h + 1
            } else {
                j = h
            }
        }
        // Swap values until data[a] reaches the position before i.
        for k := a; k < i-1; k++ {
            data.Swap(k, k+1)
        }
        return
    }

    // Avoid unnecessary recursions of symMerge
    // by direct insertion of data[m] into data[a:m]
    // if data[m:b] only contains one element.
    if b-m == 1 {
        // Use binary search to find the lowest index i
        // such that data[i] > data[m] for a <= i < m.
        // Exit the search loop with i == m in case no such index exists.
        i := a
        j := m
        for i < j {
            h := int(uint(i+j) >> 1)
            if !data.Less(m, h) {
                i = h + 1
            } else {
                j = h
            }
        }
        // Swap values until data[m] reaches the position i.
        for k := m; k > i; k-- {
            data.Swap(k, k-1)
        }
        return
    }

    mid := int(uint(a+b) >> 1)
    n := mid + m
    var start, r int
    if m > mid {
        start = n - b
        r = mid
    } else {
        start = a
        r = m
    }
    p := n - 1

    for start < r {
        c := int(uint(start+r) >> 1)
        if !data.Less(p-c, c) {
            start = c + 1
        } else {
            r = c
        }
    }

    end := n - start
    if start < m && m < end {
        rotate(data, start, m, end)
    }
    if a < start && start < mid {
        symMerge(data, a, start, mid)
    }
    if mid < end && end < b {
        symMerge(data, mid, end, b)
    }
}

sort.Slice function

// Slice function is an unstable sort method, which uses one of three sorting methods: Hill quick, quick sort or heap sort
// Slice sorts the slice x given the provided less function.
// It panics if x is not a slice.
//
// The sort is not guaranteed to be stable: equal elements
// may be reversed from their original order.
// For a stable sort, use SliceStable.
//
// The less function must satisfy the same requirements as
// the Interface type's Less method.
func Slice(x interface{}, less func(i, j int) bool) {
    rv := reflectValueOf(x)
    swap := reflectSwapper(x)
    length := rv.Len()
    // maxDepth(n) returns 2*ceil(lg(n+1)). If the element depth reaches 2*ceil(lg(n+1)), heap sorting is selected
    quickSort_func(lessSwap{less, swap}, 0, length, maxDepth(length))
}

// lessSwap is a pair of Less and Swap function for use with the
// auto-generated func-optimized variant of sort.go in
// zfuncversion.go.
type lessSwap struct {
    Less func(i, j int) bool
    Swap func(i, j int)
}
// quickSort_func will automatically select one of three sorting methods: quick sort, quick sort or heap sort according to the and element depth
// Auto-generated variant of sort.go:quickSort
func quickSort_func(data lessSwap, a, b, maxDepth int) {
    for b-a > 12 {
        if maxDepth == 0 {
            // Use heap sort
            heapSort_func(data, a, b)
            return
        }
        maxDepth--
        // Use three-way segmentation to quickly sort through doPivot_func partition for fast scheduling
        mlo, mhi := doPivot_func(data, a, b)
        if mlo-a < b-mhi {
            quickSort_func(data, a, mlo, maxDepth)
            a = mhi
        } else {
            quickSort_func(data, mhi, b, maxDepth)
            b = mlo
        }
    }
    // Slice elements less than or equal to 12, using Hill sort (Hill sort is an efficient version of insert sort), with an interval of d=6
    if b-a > 1 {
        for i := a + 6; i < b; i++ {
            if data.Less(i, i-6) {
                data.Swap(i, i-6)
            }
        }
        insertionSort_func(data, a, b)
    }
}

As you can see from the above, the sort function quicksort called by sort. Slice_ The input parameter type of func is data lesswap instead of data Interface. The insertion sort, heap sort and quick sort functions called are also different from sort.Sort. The implementation principles of sort.Slice and sort.Sort are the same. The difference is that sort.Sort obtains Len()/Swap()/Less() by passing in the object implementing the sort.Interface interface, while sort.Slice obtains Len() and Swap() / Less() by reflection, and Less() by passing parameters. Therefore, the object passed in by sort. Slice does not need to implement the sort. Interface interface interface.


sort.SliceStable function

// SliceStable is a stable sort that uses merge sort
// SliceStable sorts the slice x using the provided less
// function, keeping equal elements in their original order.
// It panics if x is not a slice.
//
// The less function must satisfy the same requirements as
// the Interface type's Less method.
func SliceStable(x interface{}, less func(i, j int) bool) {
    rv := reflectValueOf(x)
    swap := reflectSwapper(x)
    stable_func(lessSwap{less, swap}, rv.Len())
}

// lessSwap is a pair of Less and Swap function for use with the
// auto-generated func-optimized variant of sort.go in
// zfuncversion.go.
type lessSwap struct {
    Less func(i, j int) bool
    Swap func(i, j int)
}
// The merge sort algorithm used here is an original address sort algorithm:
// First, it sorts slices by inserting each blockSize=20 elements into a slice
// Loop merge two adjacent blocks, and double the blockSize each time until blockSize > n
// Auto-generated variant of sort.go:stable
func stable_func(data lessSwap, n int) {
    blockSize := 20  // The initial blockSize is set to 20
    a, b := 0, blockSize
    // Insert sort each block (and one block with less than blockSize remaining)
    for b <= n {
        insertionSort_func(data, a, b)
        a = b
        b += blockSize
    }
    insertionSort_func(data, a, n)
    for blockSize < n {
        a, b = 0, 2*blockSize
        for b <= n {
            // Merge every two blocksizes
            symMerge_func(data, a, a+blockSize, b)
            a = b
            b += 2 * blockSize
        }
        // The remaining one or more blocksizes are merged
        if m := a + blockSize; m < n {
            symMerge_func(data, a, m, n)
        }
        blockSize *= 2
    }
}

Like sort.Slice, sort.SliceStable calls the sort function stable_ The input parameter type of func is data lesswap, not data Interface. Len() and Swap() are obtained by reflection, and Less() is obtained by passing parameters.


reference resources

  1. Summary of common sorting algorithms and analysis of sorting source code of Go standard library
  2. Implementation method and application of sort package in go language

Added by 3dron on Fri, 03 Dec 2021 19:34:51 +0200