diff --git a/src/sort/gen_sort_variants.go b/src/sort/gen_sort_variants.go new file mode 100644 index 0000000000000000000000000000000000000000..5f817221e119b8eeab60fc26c1d62bf96906b6ef --- /dev/null +++ b/src/sort/gen_sort_variants.go @@ -0,0 +1,526 @@ +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build ignore +// +build ignore + +// This program is run via "go generate" (via a directive in sort.go) +// to generate implementation variants of the underlying sorting algorithm. +// When passed the -generic flag it generates generic variants of sorting; +// otherwise it generates the non-generic variants used by the sort package. + +package main + +import ( + "bytes" + "flag" + "fmt" + "go/format" + "log" + "os" + "text/template" +) + +type Variant struct { + // Name is the variant name: should be unique among variants. + Name string + + // Path is the file path into which the generator will emit the code for this + // variant. + Path string + + // Package is the package this code will be emitted into. + Package string + + // Imports is the imports needed for this package. + Imports string + + // FuncSuffix is appended to all function names in this variant's code. All + // suffixes should be unique within a package. + FuncSuffix string + + // DataType is the type of the data parameter of functions in this variant's + // code. + DataType string + + // TypeParam is the optional type parameter for the function. + TypeParam string + + // ExtraParam is an extra parameter to pass to the function. Should begin with + // ", " to separate from other params. + ExtraParam string + + // ExtraArg is an extra argument to pass to calls between functions; typically + // it invokes ExtraParam. Should begin with ", " to separate from other args. + ExtraArg string + + // Funcs is a map of functions used from within the template. The following + // functions are expected to exist: + // + // Less (name, i, j): + // emits a comparison expression that checks if the value `name` at + // index `i` is smaller than at index `j`. + // + // Swap (name, i, j): + // emits a statement that performs a data swap between elements `i` and + // `j` of the value `name`. + Funcs template.FuncMap +} + +func main() { + genGeneric := flag.Bool("generic", false, "generate generic versions") + flag.Parse() + + if *genGeneric { + generate(&Variant{ + Name: "generic_ordered", + Path: "zsortordered.go", + Package: "slices", + Imports: "import \"constraints\"\n", + FuncSuffix: "Ordered", + TypeParam: "[Elem constraints.Ordered]", + ExtraParam: "", + ExtraArg: "", + DataType: "[]Elem", + Funcs: template.FuncMap{ + "Less": func(name, i, j string) string { + return fmt.Sprintf("(%s[%s] < %s[%s])", name, i, name, j) + }, + "Swap": func(name, i, j string) string { + return fmt.Sprintf("%s[%s], %s[%s] = %s[%s], %s[%s]", name, i, name, j, name, j, name, i) + }, + }, + }) + + generate(&Variant{ + Name: "generic_func", + Path: "zsortanyfunc.go", + Package: "slices", + FuncSuffix: "LessFunc", + TypeParam: "[Elem any]", + ExtraParam: ", less func(a, b Elem) bool", + ExtraArg: ", less", + DataType: "[]Elem", + Funcs: template.FuncMap{ + "Less": func(name, i, j string) string { + return fmt.Sprintf("less(%s[%s], %s[%s])", name, i, name, j) + }, + "Swap": func(name, i, j string) string { + return fmt.Sprintf("%s[%s], %s[%s] = %s[%s], %s[%s]", name, i, name, j, name, j, name, i) + }, + }, + }) + } else { + generate(&Variant{ + Name: "interface", + Path: "zsortinterface.go", + Package: "sort", + Imports: "", + FuncSuffix: "", + TypeParam: "", + ExtraParam: "", + ExtraArg: "", + DataType: "Interface", + Funcs: template.FuncMap{ + "Less": func(name, i, j string) string { + return fmt.Sprintf("%s.Less(%s, %s)", name, i, j) + }, + "Swap": func(name, i, j string) string { + return fmt.Sprintf("%s.Swap(%s, %s)", name, i, j) + }, + }, + }) + + generate(&Variant{ + Name: "func", + Path: "zsortfunc.go", + Package: "sort", + Imports: "", + FuncSuffix: "_func", + TypeParam: "", + ExtraParam: "", + ExtraArg: "", + DataType: "lessSwap", + Funcs: template.FuncMap{ + "Less": func(name, i, j string) string { + return fmt.Sprintf("%s.Less(%s, %s)", name, i, j) + }, + "Swap": func(name, i, j string) string { + return fmt.Sprintf("%s.Swap(%s, %s)", name, i, j) + }, + }, + }) + } +} + +// generate generates the code for variant `v` into a file named by `v.Path`. +func generate(v *Variant) { + // Parse templateCode anew for each variant because Parse requires Funcs to be + // registered, and it helps type-check the funcs. + tmpl, err := template.New("gen").Funcs(v.Funcs).Parse(templateCode) + if err != nil { + log.Fatal("template Parse:", err) + } + + var out bytes.Buffer + err = tmpl.Execute(&out, v) + if err != nil { + log.Fatal("template Execute:", err) + } + + formatted, err := format.Source(out.Bytes()) + if err != nil { + log.Fatal("format:", err) + } + + if err := os.WriteFile(v.Path, formatted, 0644); err != nil { + log.Fatal("WriteFile:", err) + } +} + +var templateCode = `// Code generated by gen_sort_variants.go; DO NOT EDIT. + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package {{.Package}} + +{{.Imports}} + +// insertionSort{{.FuncSuffix}} sorts data[a:b] using insertion sort. +func insertionSort{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b int {{.ExtraParam}}) { + for i := a + 1; i < b; i++ { + for j := i; j > a && {{Less "data" "j" "j-1"}}; j-- { + {{Swap "data" "j" "j-1"}} + } + } +} + +// siftDown{{.FuncSuffix}} implements the heap property on data[lo:hi]. +// first is an offset into the array where the root of the heap lies. +func siftDown{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, lo, hi, first int {{.ExtraParam}}) { + root := lo + for { + child := 2*root + 1 + if child >= hi { + break + } + if child+1 < hi && {{Less "data" "first+child" "first+child+1"}} { + child++ + } + if !{{Less "data" "first+root" "first+child"}} { + return + } + {{Swap "data" "first+root" "first+child"}} + root = child + } +} + +func heapSort{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b int {{.ExtraParam}}) { + first := a + lo := 0 + hi := b - a + + // Build heap with greatest element at top. + for i := (hi - 1) / 2; i >= 0; i-- { + siftDown{{.FuncSuffix}}(data, i, hi, first {{.ExtraArg}}) + } + + // Pop elements, largest first, into end of data. + for i := hi - 1; i >= 0; i-- { + {{Swap "data" "first" "first+i"}} + siftDown{{.FuncSuffix}}(data, lo, i, first {{.ExtraArg}}) + } +} + +// Quicksort, loosely following Bentley and McIlroy, +// "Engineering a Sort Function" SP&E November 1993. + +// medianOfThree{{.FuncSuffix}} moves the median of the three values data[m0], data[m1], data[m2] into data[m1]. +func medianOfThree{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, m1, m0, m2 int {{.ExtraParam}}) { + // sort 3 elements + if {{Less "data" "m1" "m0"}} { + {{Swap "data" "m1" "m0"}} + } + // data[m0] <= data[m1] + if {{Less "data" "m2" "m1"}} { + {{Swap "data" "m2" "m1"}} + // data[m0] <= data[m2] && data[m1] < data[m2] + if {{Less "data" "m1" "m0"}} { + {{Swap "data" "m1" "m0"}} + } + } + // now data[m0] <= data[m1] <= data[m2] +} + +func swapRange{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b, n int {{.ExtraParam}}) { + for i := 0; i < n; i++ { + {{Swap "data" "a+i" "b+i"}} + } +} + +func doPivot{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, lo, hi int {{.ExtraParam}}) (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{{.FuncSuffix}}(data, lo, lo+s, lo+2*s {{.ExtraArg}}) + medianOfThree{{.FuncSuffix}}(data, m, m-s, m+s {{.ExtraArg}}) + medianOfThree{{.FuncSuffix}}(data, hi-1, hi-1-s, hi-1-2*s {{.ExtraArg}}) + } + medianOfThree{{.FuncSuffix}}(data, lo, m, hi-1 {{.ExtraArg}}) + + // 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 && {{Less "data" "a" "pivot"}}; a++ { + } + b := a + for { + for ; b < c && !{{Less "data" "pivot" "b"}}; b++ { // data[b] <= pivot + } + for ; b < c && {{Less "data" "pivot" "c-1"}}; c-- { // data[c-1] > pivot + } + if b >= c { + break + } + // data[b] > pivot; data[c-1] <= pivot + {{Swap "data" "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 !{{Less "data" "pivot" "hi-1"}} { // data[hi-1] = pivot + {{Swap "data" "c" "hi-1"}} + c++ + dups++ + } + if !{{Less "data" "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 !{{Less "data" "m" "pivot"}} { // data[m] = pivot + {{Swap "data" "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 && !{{Less "data" "b-1" "pivot"}}; b-- { // data[b] == pivot + } + for ; a < b && {{Less "data" "a" "pivot"}}; a++ { // data[a] < pivot + } + if a >= b { + break + } + // data[a] == pivot; data[b-1] < pivot + {{Swap "data" "a" "b-1"}} + a++ + b-- + } + } + // Swap pivot into middle + {{Swap "data" "pivot" "b-1"}} + return b - 1, c +} + +func quickSort{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, b, maxDepth int {{.ExtraParam}}) { + for b-a > 12 { // Use ShellSort for slices <= 12 elements + if maxDepth == 0 { + heapSort{{.FuncSuffix}}(data, a, b {{.ExtraArg}}) + return + } + maxDepth-- + mlo, mhi := doPivot{{.FuncSuffix}}(data, a, b {{.ExtraArg}}) + // Avoiding recursion on the larger subproblem guarantees + // a stack depth of at most lg(b-a). + if mlo-a < b-mhi { + quickSort{{.FuncSuffix}}(data, a, mlo, maxDepth {{.ExtraArg}}) + a = mhi // i.e., quickSort{{.FuncSuffix}}(data, mhi, b) + } else { + quickSort{{.FuncSuffix}}(data, mhi, b, maxDepth {{.ExtraArg}}) + b = mlo // i.e., quickSort{{.FuncSuffix}}(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 {{Less "data" "i" "i-6"}} { + {{Swap "data" "i" "i-6"}} + } + } + insertionSort{{.FuncSuffix}}(data, a, b {{.ExtraArg}}) + } +} + +func stable{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, n int {{.ExtraParam}}) { + blockSize := 20 // must be > 0 + a, b := 0, blockSize + for b <= n { + insertionSort{{.FuncSuffix}}(data, a, b {{.ExtraArg}}) + a = b + b += blockSize + } + insertionSort{{.FuncSuffix}}(data, a, n {{.ExtraArg}}) + + for blockSize < n { + a, b = 0, 2*blockSize + for b <= n { + symMerge{{.FuncSuffix}}(data, a, a+blockSize, b {{.ExtraArg}}) + a = b + b += 2 * blockSize + } + if m := a + blockSize; m < n { + symMerge{{.FuncSuffix}}(data, a, m, n {{.ExtraArg}}) + } + blockSize *= 2 + } +} + +// symMerge{{.FuncSuffix}} merges the two sorted subsequences data[a:m] and data[m:b] using +// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum +// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz +// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in +// Computer Science, pages 714-723. Springer, 2004. +// +// Let M = m-a and N = b-n. Wolog M < N. +// The recursion depth is bound by ceil(log(N+M)). +// The algorithm needs O(M*log(N/M + 1)) calls to data.Less. +// The algorithm needs O((M+N)*log(M)) calls to data.Swap. +// +// The paper gives O((M+N)*log(M)) as the number of assignments assuming a +// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation +// in the paper carries through for Swap operations, especially as the block +// swapping rotate uses only O(M+N) Swaps. +// +// symMerge assumes non-degenerate arguments: a < m && m < b. +// Having the caller check this condition eliminates many leaf recursion calls, +// which improves performance. +func symMerge{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, m, b int {{.ExtraParam}}) { + // 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 {{Less "data" "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++ { + {{Swap "data" "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 !{{Less "data" "m" "h"}} { + i = h + 1 + } else { + j = h + } + } + // Swap values until data[m] reaches the position i. + for k := m; k > i; k-- { + {{Swap "data" "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 !{{Less "data" "p-c" "c"}} { + start = c + 1 + } else { + r = c + } + } + + end := n - start + if start < m && m < end { + rotate{{.FuncSuffix}}(data, start, m, end {{.ExtraArg}}) + } + if a < start && start < mid { + symMerge{{.FuncSuffix}}(data, a, start, mid {{.ExtraArg}}) + } + if mid < end && end < b { + symMerge{{.FuncSuffix}}(data, mid, end, b {{.ExtraArg}}) + } +} + +// rotate{{.FuncSuffix}} rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: +// Data of the form 'x u v y' is changed to 'x v u y'. +// rotate performs at most b-a many calls to data.Swap, +// and it assumes non-degenerate arguments: a < m && m < b. +func rotate{{.FuncSuffix}}{{.TypeParam}}(data {{.DataType}}, a, m, b int {{.ExtraParam}}) { + i := m - a + j := b - m + + for i != j { + if i > j { + swapRange{{.FuncSuffix}}(data, m-i, m, j {{.ExtraArg}}) + i -= j + } else { + swapRange{{.FuncSuffix}}(data, m-i, m+j-i, i {{.ExtraArg}}) + j -= i + } + } + // i == j + swapRange{{.FuncSuffix}}(data, m-i, m, i {{.ExtraArg}}) +} +` diff --git a/src/sort/genzfunc.go b/src/sort/genzfunc.go deleted file mode 100644 index ed04e33568f4c6f6b5a7fa04790c76f7da29b10a..0000000000000000000000000000000000000000 --- a/src/sort/genzfunc.go +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -//go:build ignore -// +build ignore - -// This program is run via "go generate" (via a directive in sort.go) -// to generate zfuncversion.go. -// -// It copies sort.go to zfuncversion.go, only retaining funcs which -// take a "data Interface" parameter, and renaming each to have a -// "_func" suffix and taking a "data lessSwap" instead. It then rewrites -// each internal function call to the appropriate _func variants. - -package main - -import ( - "bytes" - "go/ast" - "go/format" - "go/parser" - "go/token" - "log" - "os" - "regexp" -) - -var fset = token.NewFileSet() - -func main() { - af, err := parser.ParseFile(fset, "sort.go", nil, 0) - if err != nil { - log.Fatal(err) - } - af.Doc = nil - af.Imports = nil - af.Comments = nil - - var newDecl []ast.Decl - for _, d := range af.Decls { - fd, ok := d.(*ast.FuncDecl) - if !ok { - continue - } - if fd.Recv != nil || fd.Name.IsExported() { - continue - } - typ := fd.Type - if len(typ.Params.List) < 1 { - continue - } - arg0 := typ.Params.List[0] - arg0Name := arg0.Names[0].Name - arg0Type := arg0.Type.(*ast.Ident) - if arg0Name != "data" || arg0Type.Name != "Interface" { - continue - } - arg0Type.Name = "lessSwap" - - newDecl = append(newDecl, fd) - } - af.Decls = newDecl - ast.Walk(visitFunc(rewriteCalls), af) - - var out bytes.Buffer - if err := format.Node(&out, fset, af); err != nil { - log.Fatalf("format.Node: %v", err) - } - - // Get rid of blank lines after removal of comments. - src := regexp.MustCompile(`\n{2,}`).ReplaceAll(out.Bytes(), []byte("\n")) - - // Add comments to each func, for the lost reader. - // This is so much easier than adding comments via the AST - // and trying to get position info correct. - src = regexp.MustCompile(`(?m)^func (\w+)`).ReplaceAll(src, []byte("\n// Auto-generated variant of sort.go:$1\nfunc ${1}_func")) - - // Final gofmt. - src, err = format.Source(src) - if err != nil { - log.Fatalf("format.Source: %v on\n%s", err, src) - } - - out.Reset() - out.WriteString(`// Code generated from sort.go using genzfunc.go; DO NOT EDIT. - -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -`) - out.Write(src) - - const target = "zfuncversion.go" - if err := os.WriteFile(target, out.Bytes(), 0644); err != nil { - log.Fatal(err) - } -} - -type visitFunc func(ast.Node) ast.Visitor - -func (f visitFunc) Visit(n ast.Node) ast.Visitor { return f(n) } - -func rewriteCalls(n ast.Node) ast.Visitor { - ce, ok := n.(*ast.CallExpr) - if ok { - rewriteCall(ce) - } - return visitFunc(rewriteCalls) -} - -func rewriteCall(ce *ast.CallExpr) { - ident, ok := ce.Fun.(*ast.Ident) - if !ok { - // e.g. skip SelectorExpr (data.Less(..) calls) - return - } - // skip casts - if ident.Name == "int" || ident.Name == "uint" { - return - } - if len(ce.Args) < 1 { - return - } - ident.Name += "_func" -} diff --git a/src/sort/sort.go b/src/sort/sort.go index 749310764aff2b7e00f0e6c89781f2d7d306c753..2c197afc033e7bfe19cd88e2d2616f3371d2926b 100644 --- a/src/sort/sort.go +++ b/src/sort/sort.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -//go:generate go run genzfunc.go +//go:generate go run gen_sort_variants.go // Package sort provides primitives for sorting slices and user-defined collections. package sort @@ -34,195 +34,6 @@ type Interface interface { Swap(i, j int) } -// 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) - } - } -} - -// 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 - } -} - -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) - } -} - -// Quicksort, loosely following Bentley and McIlroy, -// ``Engineering a Sort Function,'' SP&E November 1993. - -// medianOfThree moves the median of the three values data[m0], data[m1], data[m2] into data[m1]. -func medianOfThree(data Interface, m1, m0, m2 int) { - // sort 3 elements - if data.Less(m1, m0) { - data.Swap(m1, m0) - } - // data[m0] <= data[m1] - if data.Less(m2, m1) { - data.Swap(m2, m1) - // data[m0] <= data[m2] && data[m1] < data[m2] - if data.Less(m1, m0) { - data.Swap(m1, m0) - } - } - // now data[m0] <= data[m1] <= data[m2] -} - -func swapRange(data Interface, a, b, n int) { - for i := 0; i < n; i++ { - data.Swap(a+i, b+i) - } -} - -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 -} - -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) - } -} - // Sort sorts data in ascending order as determined by the Less method. // 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. @@ -379,152 +190,6 @@ func Stable(data Interface) { stable(data, data.Len()) } -func stable(data Interface, n int) { - blockSize := 20 // must be > 0 - a, b := 0, blockSize - 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 { - symMerge(data, a, a+blockSize, b) - a = b - b += 2 * blockSize - } - if m := a + blockSize; m < n { - symMerge(data, a, m, n) - } - blockSize *= 2 - } -} - -// symMerge merges the two sorted subsequences data[a:m] and data[m:b] using -// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum -// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz -// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in -// Computer Science, pages 714-723. Springer, 2004. -// -// Let M = m-a and N = b-n. Wolog M < N. -// The recursion depth is bound by ceil(log(N+M)). -// The algorithm needs O(M*log(N/M + 1)) calls to data.Less. -// The algorithm needs O((M+N)*log(M)) calls to data.Swap. -// -// The paper gives O((M+N)*log(M)) as the number of assignments assuming a -// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation -// in the paper carries through for Swap operations, especially as the block -// swapping rotate uses only O(M+N) Swaps. -// -// symMerge assumes non-degenerate arguments: a < m && m < b. -// Having the caller check this condition eliminates many leaf recursion calls, -// which improves performance. -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) - } -} - -// rotate rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: -// Data of the form 'x u v y' is changed to 'x v u y'. -// rotate performs at most b-a many calls to data.Swap, -// and it assumes non-degenerate arguments: a < m && m < b. -func rotate(data Interface, a, m, b int) { - i := m - a - j := b - m - - for i != j { - if i > j { - swapRange(data, m-i, m, j) - i -= j - } else { - swapRange(data, m-i, m+j-i, i) - j -= i - } - } - // i == j - swapRange(data, m-i, m, i) -} - /* Complexity of Stable Sorting diff --git a/src/sort/zfuncversion.go b/src/sort/zfuncversion.go deleted file mode 100644 index 30067cbe077bd0e1c8b570cc5c71450fae3ce16c..0000000000000000000000000000000000000000 --- a/src/sort/zfuncversion.go +++ /dev/null @@ -1,265 +0,0 @@ -// Code generated from sort.go using genzfunc.go; DO NOT EDIT. - -// Copyright 2016 The Go Authors. All rights reserved. -// Use of this source code is governed by a BSD-style -// license that can be found in the LICENSE file. - -package sort - -// Auto-generated variant of sort.go:insertionSort -func insertionSort_func(data lessSwap, 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) - } - } -} - -// Auto-generated variant of sort.go:siftDown -func siftDown_func(data lessSwap, 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 - } -} - -// Auto-generated variant of sort.go:heapSort -func heapSort_func(data lessSwap, a, b int) { - first := a - lo := 0 - hi := b - a - for i := (hi - 1) / 2; i >= 0; i-- { - siftDown_func(data, i, hi, first) - } - for i := hi - 1; i >= 0; i-- { - data.Swap(first, first+i) - siftDown_func(data, lo, i, first) - } -} - -// Auto-generated variant of sort.go:medianOfThree -func medianOfThree_func(data lessSwap, m1, m0, m2 int) { - if data.Less(m1, m0) { - data.Swap(m1, m0) - } - if data.Less(m2, m1) { - data.Swap(m2, m1) - if data.Less(m1, m0) { - data.Swap(m1, m0) - } - } -} - -// Auto-generated variant of sort.go:swapRange -func swapRange_func(data lessSwap, a, b, n int) { - for i := 0; i < n; i++ { - data.Swap(a+i, b+i) - } -} - -// Auto-generated variant of sort.go:doPivot -func doPivot_func(data lessSwap, lo, hi int) (midlo, midhi int) { - m := int(uint(lo+hi) >> 1) - if hi-lo > 40 { - s := (hi - lo) / 8 - medianOfThree_func(data, lo, lo+s, lo+2*s) - medianOfThree_func(data, m, m-s, m+s) - medianOfThree_func(data, hi-1, hi-1-s, hi-1-2*s) - } - medianOfThree_func(data, lo, m, hi-1) - 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++ { - } - for ; b < c && data.Less(pivot, c-1); c-- { - } - if b >= c { - break - } - data.Swap(b, c-1) - b++ - c-- - } - protect := hi-c < 5 - if !protect && hi-c < (hi-lo)/4 { - dups := 0 - if !data.Less(pivot, hi-1) { - data.Swap(c, hi-1) - c++ - dups++ - } - if !data.Less(b-1, pivot) { - b-- - dups++ - } - if !data.Less(m, pivot) { - data.Swap(m, b-1) - b-- - dups++ - } - protect = dups > 1 - } - if protect { - for { - for ; a < b && !data.Less(b-1, pivot); b-- { - } - for ; a < b && data.Less(a, pivot); a++ { - } - if a >= b { - break - } - data.Swap(a, b-1) - a++ - b-- - } - } - data.Swap(pivot, b-1) - return b - 1, c -} - -// Auto-generated variant of sort.go:quickSort -func quickSort_func(data lessSwap, a, b, maxDepth int) { - for b-a > 12 { - if maxDepth == 0 { - heapSort_func(data, a, b) - return - } - maxDepth-- - 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 - } - } - 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) - } -} - -// Auto-generated variant of sort.go:stable -func stable_func(data lessSwap, n int) { - blockSize := 20 - a, b := 0, blockSize - 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 { - symMerge_func(data, a, a+blockSize, b) - a = b - b += 2 * blockSize - } - if m := a + blockSize; m < n { - symMerge_func(data, a, m, n) - } - blockSize *= 2 - } -} - -// Auto-generated variant of sort.go:symMerge -func symMerge_func(data lessSwap, a, m, b int) { - if m-a == 1 { - i := m - j := b - for i < j { - h := int(uint(i+j) >> 1) - if data.Less(h, a) { - i = h + 1 - } else { - j = h - } - } - for k := a; k < i-1; k++ { - data.Swap(k, k+1) - } - return - } - if b-m == 1 { - i := a - j := m - for i < j { - h := int(uint(i+j) >> 1) - if !data.Less(m, h) { - i = h + 1 - } else { - j = h - } - } - 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_func(data, start, m, end) - } - if a < start && start < mid { - symMerge_func(data, a, start, mid) - } - if mid < end && end < b { - symMerge_func(data, mid, end, b) - } -} - -// Auto-generated variant of sort.go:rotate -func rotate_func(data lessSwap, a, m, b int) { - i := m - a - j := b - m - for i != j { - if i > j { - swapRange_func(data, m-i, m, j) - i -= j - } else { - swapRange_func(data, m-i, m+j-i, i) - j -= i - } - } - swapRange_func(data, m-i, m, i) -} diff --git a/src/sort/zsortfunc.go b/src/sort/zsortfunc.go new file mode 100644 index 0000000000000000000000000000000000000000..80c8a77995987d2d32ccc3aed72db8d402b708a7 --- /dev/null +++ b/src/sort/zsortfunc.go @@ -0,0 +1,342 @@ +// Code generated by gen_sort_variants.go; DO NOT EDIT. + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package sort + +// insertionSort_func sorts data[a:b] using insertion sort. +func insertionSort_func(data lessSwap, 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) + } + } +} + +// siftDown_func implements the heap property on data[lo:hi]. +// first is an offset into the array where the root of the heap lies. +func siftDown_func(data lessSwap, 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 + } +} + +func heapSort_func(data lessSwap, 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_func(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_func(data, lo, i, first) + } +} + +// Quicksort, loosely following Bentley and McIlroy, +// "Engineering a Sort Function" SP&E November 1993. + +// medianOfThree_func moves the median of the three values data[m0], data[m1], data[m2] into data[m1]. +func medianOfThree_func(data lessSwap, m1, m0, m2 int) { + // sort 3 elements + if data.Less(m1, m0) { + data.Swap(m1, m0) + } + // data[m0] <= data[m1] + if data.Less(m2, m1) { + data.Swap(m2, m1) + // data[m0] <= data[m2] && data[m1] < data[m2] + if data.Less(m1, m0) { + data.Swap(m1, m0) + } + } + // now data[m0] <= data[m1] <= data[m2] +} + +func swapRange_func(data lessSwap, a, b, n int) { + for i := 0; i < n; i++ { + data.Swap(a+i, b+i) + } +} + +func doPivot_func(data lessSwap, 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_func(data, lo, lo+s, lo+2*s) + medianOfThree_func(data, m, m-s, m+s) + medianOfThree_func(data, hi-1, hi-1-s, hi-1-2*s) + } + medianOfThree_func(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 +} + +func quickSort_func(data lessSwap, a, b, maxDepth int) { + for b-a > 12 { // Use ShellSort for slices <= 12 elements + if maxDepth == 0 { + heapSort_func(data, a, b) + return + } + maxDepth-- + mlo, mhi := doPivot_func(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_func(data, a, mlo, maxDepth) + a = mhi // i.e., quickSort_func(data, mhi, b) + } else { + quickSort_func(data, mhi, b, maxDepth) + b = mlo // i.e., quickSort_func(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_func(data, a, b) + } +} + +func stable_func(data lessSwap, n int) { + blockSize := 20 // must be > 0 + a, b := 0, blockSize + 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 { + symMerge_func(data, a, a+blockSize, b) + a = b + b += 2 * blockSize + } + if m := a + blockSize; m < n { + symMerge_func(data, a, m, n) + } + blockSize *= 2 + } +} + +// symMerge_func merges the two sorted subsequences data[a:m] and data[m:b] using +// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum +// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz +// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in +// Computer Science, pages 714-723. Springer, 2004. +// +// Let M = m-a and N = b-n. Wolog M < N. +// The recursion depth is bound by ceil(log(N+M)). +// The algorithm needs O(M*log(N/M + 1)) calls to data.Less. +// The algorithm needs O((M+N)*log(M)) calls to data.Swap. +// +// The paper gives O((M+N)*log(M)) as the number of assignments assuming a +// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation +// in the paper carries through for Swap operations, especially as the block +// swapping rotate uses only O(M+N) Swaps. +// +// symMerge assumes non-degenerate arguments: a < m && m < b. +// Having the caller check this condition eliminates many leaf recursion calls, +// which improves performance. +func symMerge_func(data lessSwap, 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_func(data, start, m, end) + } + if a < start && start < mid { + symMerge_func(data, a, start, mid) + } + if mid < end && end < b { + symMerge_func(data, mid, end, b) + } +} + +// rotate_func rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: +// Data of the form 'x u v y' is changed to 'x v u y'. +// rotate performs at most b-a many calls to data.Swap, +// and it assumes non-degenerate arguments: a < m && m < b. +func rotate_func(data lessSwap, a, m, b int) { + i := m - a + j := b - m + + for i != j { + if i > j { + swapRange_func(data, m-i, m, j) + i -= j + } else { + swapRange_func(data, m-i, m+j-i, i) + j -= i + } + } + // i == j + swapRange_func(data, m-i, m, i) +} diff --git a/src/sort/zsortinterface.go b/src/sort/zsortinterface.go new file mode 100644 index 0000000000000000000000000000000000000000..e0d70936785493e4a3500411b5978def8c7f79b8 --- /dev/null +++ b/src/sort/zsortinterface.go @@ -0,0 +1,342 @@ +// Code generated by gen_sort_variants.go; DO NOT EDIT. + +// Copyright 2022 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package 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) + } + } +} + +// 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 + } +} + +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) + } +} + +// Quicksort, loosely following Bentley and McIlroy, +// "Engineering a Sort Function" SP&E November 1993. + +// medianOfThree moves the median of the three values data[m0], data[m1], data[m2] into data[m1]. +func medianOfThree(data Interface, m1, m0, m2 int) { + // sort 3 elements + if data.Less(m1, m0) { + data.Swap(m1, m0) + } + // data[m0] <= data[m1] + if data.Less(m2, m1) { + data.Swap(m2, m1) + // data[m0] <= data[m2] && data[m1] < data[m2] + if data.Less(m1, m0) { + data.Swap(m1, m0) + } + } + // now data[m0] <= data[m1] <= data[m2] +} + +func swapRange(data Interface, a, b, n int) { + for i := 0; i < n; i++ { + data.Swap(a+i, b+i) + } +} + +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 +} + +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) + } +} + +func stable(data Interface, n int) { + blockSize := 20 // must be > 0 + a, b := 0, blockSize + 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 { + symMerge(data, a, a+blockSize, b) + a = b + b += 2 * blockSize + } + if m := a + blockSize; m < n { + symMerge(data, a, m, n) + } + blockSize *= 2 + } +} + +// symMerge merges the two sorted subsequences data[a:m] and data[m:b] using +// the SymMerge algorithm from Pok-Son Kim and Arne Kutzner, "Stable Minimum +// Storage Merging by Symmetric Comparisons", in Susanne Albers and Tomasz +// Radzik, editors, Algorithms - ESA 2004, volume 3221 of Lecture Notes in +// Computer Science, pages 714-723. Springer, 2004. +// +// Let M = m-a and N = b-n. Wolog M < N. +// The recursion depth is bound by ceil(log(N+M)). +// The algorithm needs O(M*log(N/M + 1)) calls to data.Less. +// The algorithm needs O((M+N)*log(M)) calls to data.Swap. +// +// The paper gives O((M+N)*log(M)) as the number of assignments assuming a +// rotation algorithm which uses O(M+N+gcd(M+N)) assignments. The argumentation +// in the paper carries through for Swap operations, especially as the block +// swapping rotate uses only O(M+N) Swaps. +// +// symMerge assumes non-degenerate arguments: a < m && m < b. +// Having the caller check this condition eliminates many leaf recursion calls, +// which improves performance. +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) + } +} + +// rotate rotates two consecutive blocks u = data[a:m] and v = data[m:b] in data: +// Data of the form 'x u v y' is changed to 'x v u y'. +// rotate performs at most b-a many calls to data.Swap, +// and it assumes non-degenerate arguments: a < m && m < b. +func rotate(data Interface, a, m, b int) { + i := m - a + j := b - m + + for i != j { + if i > j { + swapRange(data, m-i, m, j) + i -= j + } else { + swapRange(data, m-i, m+j-i, i) + j -= i + } + } + // i == j + swapRange(data, m-i, m, i) +}