Go 言語における sort パッケージの Ints メソッド実装について深堀り
気になったので調べた
Go 言語における sort パッケージの Ints メソッド
sort package - sort - pkg.go.dev
https://pkg.go.dev/sort#Ints
func Ints(x []int)
Ints sorts a slice of ints in increasing order.
int 配列を引数として受け付けて、昇順に並び替えてくれるメソッド
package main
import (
"fmt"
"sort"
)
func main() {
s := []int{5, 2, 6, 3, 1, 4} // unsorted
sort.Ints(s)
fmt.Println(s) // [1 2 3 4 5 6]
}
Go Playground
https://go.dev/play/p/mFjGgkoyKce
Ints の定義
go/sort.go at go1.16.5 · golang/go
https://github.com/golang/go/blob/go1.16.5/src/sort/sort.go#L328
// Ints sorts a slice of ints in increasing order.
func Ints(x []int) { Sort(IntSlice(x)) }
Sort
というメソッドに対して IntSlice(x)
というメソッドを渡している
Sort の定義
go/sort.go at go1.16.5 · golang/go
https://github.com/golang/go/blob/go1.16.5/src/sort/sort.go#L229
// Sort sorts data.
// It makes one call to data.Len to determine n and O(n*log(n)) calls to
// data.Less and data.Swap. The sort is not guaranteed to be stable.
func Sort(data Interface) {
n := data.Len()
quickSort(data, 0, n, maxDepth(n))
}
data Interface
を受け取って data の長さを n として取得quickSort
というメソッドを呼び出している- 名前からしてクイックソートアルゴリズムで実装してそう
- クイックソート - Wikipedia
quickSort の定義
go/sort.go at go1.16.5 · golang/go
https://github.com/golang/go/blob/go1.16.5/src/sort/sort.go#L196
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 < 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)
}
}
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)
}
}
それっぽい実装がされている
- 引数
- data Interface: ソート対象のデータ
- a: 初期値?
- b: data の最大長
- maxDepth int: 最大深度
- b と a の差が 12 以上である間、ループを行う // スライスの要素数が 12 以下ならシェルソートを使用する
- maxDepth が 0 であれば heapSort を行う
- ヒープソートアルゴリズムで実装してそう
- ヒープソート - Wikipedia
- maxDepth をデクリメント
- mlo, mhi に doPivot でピボットを境界値として選択
- LO: 配列の先頭から順に、値がピボット以上の要素を探索した位置
- HI: 配列の終端から逆順に、値がピボット未満の要素を探索した位置
- mlo - a < b - mhi なら
quickSort(data, a ,mlo, maxDepth)
- mhi を a に代入
- mlo - a < b - mhi 以外なら
quickSort(data, mhi, b, maxDepth)
- mlo を b に代入
- maxDepth が 0 であれば heapSort を行う
- b と a の差が 1 より大きければ
- a + 6 が b より小さい間
- ギャップ 6 でシェルソートパスを実行する
- シェルソート - Wikipedia
- そうでなければ挿入ソートを実行する
- a + 6 が b より小さい間
doPivot の定義
go/sort.go at go1.16.5 · golang/go
https://github.com/golang/go/blob/go1.16.5/src/sort/sort.go#L109
func doPivot(data Interface, lo, hi int) (midlo, midhi int) {
m := int(uint(lo+hi) >> 1) // Written like this to avoid integer overflow.
if hi-lo > 40 {
// Tukey's ``Ninther,'' median of three medians of three.
s := (hi - lo) / 8
medianOfThree(data, lo, lo+s, lo+2*s)
medianOfThree(data, m, m-s, m+s)
medianOfThree(data, hi-1, hi-1-s, hi-1-2*s)
}
medianOfThree(data, lo, m, hi-1)
// Invariants are:
// data[lo] = pivot (set up by ChoosePivot)
// data[lo < i < a] < pivot
// data[a <= i < b] <= pivot
// data[b <= i < c] unexamined
// data[c <= i < hi-1] > pivot
// data[hi-1] >= pivot
pivot := lo
a, c := lo+1, hi-1
for ; a < c && data.Less(a, pivot); a++ {
}
b := a
for {
for ; b < c && !data.Less(pivot, b); b++ { // data[b] <= pivot
}
for ; b < c && data.Less(pivot, c-1); c-- { // data[c-1] > pivot
}
if b >= c {
break
}
// data[b] > pivot; data[c-1] <= pivot
data.Swap(b, c-1)
b++
c--
}
// If hi-c<3 then there are duplicates (by property of median of nine).
// Let's be a bit more conservative, and set border to 5.
protect := hi-c < 5
if !protect && hi-c < (hi-lo)/4 {
// Lets test some points for equality to pivot
dups := 0
if !data.Less(pivot, hi-1) { // data[hi-1] = pivot
data.Swap(c, hi-1)
c++
dups++
}
if !data.Less(b-1, pivot) { // data[b-1] = pivot
b--
dups++
}
// m-lo = (hi-lo)/2 > 6
// b-lo > (hi-lo)*3/4-1 > 8
// ==> m < b ==> data[m] <= pivot
if !data.Less(m, pivot) { // data[m] = pivot
data.Swap(m, b-1)
b--
dups++
}
// if at least 2 points are equal to pivot, assume skewed distribution
protect = dups > 1
}
if protect {
// Protect against a lot of duplicates
// Add invariant:
// data[a <= i < b] unexamined
// data[b <= i < c] = pivot
for {
for ; a < b && !data.Less(b-1, pivot); b-- { // data[b] == pivot
}
for ; a < b && data.Less(a, pivot); a++ { // data[a] < pivot
}
if a >= b {
break
}
// data[a] == pivot; data[b-1] < pivot
data.Swap(a, b-1)
a++
b--
}
}
// Swap pivot into middle
data.Swap(pivot, b-1)
return b - 1, c
}
heapSort の実装
go/sort.go at go1.16.5 · golang/go
https://github.com/golang/go/blob/go1.16.5/src/sort/sort.go#L66
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)
}
}
insertionSort の定義
go/sort.go at go1.16.5 · golang/go
https://github.com/golang/go/blob/go1.16.5/src/sort/sort.go#L38
// 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)
}
}
}
- シンプルに挿入ソート - Wikipedia
IntSlices の定義
go/sort.go at go1.16.5 · golang/go
https://github.com/golang/go/blob/go1.16.5/src/sort/sort.go#L282
// IntSlice attaches the methods of Interface to []int, sorting in increasing order.
type IntSlice []int
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] }
// Sort is a convenience method: x.Sort() calls Sort(x).
func (x IntSlice) Sort() { Sort(x) }
感想
- sort パッケージの Ints メソッドで使用されるソートは、クイックソートをベースに実装されている
- よって安定ソートではない
- 一方で、状況に応じてヒープソート・シェルソート・挿入ソートも使用するハイブリッドな実装になっている事が分かった
- 細かい実装は理解できてないが、コード読むのは面白いので今後も気になったものは調べていきたい
- そして既に自分より詳しく読み込んでいる方がいらっしゃった
Go 標準のソート・アルゴリズム
https://zenn.dev/spiegel/articles/20200926-standard-sort-package-by-golang
配列とスライスとソート | slide.Baldanders.info
https://slide.baldanders.info/shimane-go-2020-02-13/
ソートを使う | text.Baldanders.info
https://text.baldanders.info/golang/sort/
- また sort パッケージには昇順にソート済みのデータを降順にする Reverse や、挿入ソートのみを使って安定ソートを実現する SliceStable など、他にも便利なメソッドが用意されていた
my env
% go version
go version go1.16.5 darwin/amd64