[stdlib] Switch to a stable sort algorithm (#19717)

This switches the standard library's sort algorithm from an in-place
introsort to use a modified timsort, a stable, adaptive sort that
merges runs using a temporary buffer. This implementation performs
straight merges instead of adopting timsort's galloping strategy.

In addition to maintaining the relative order of equal/non-comparable
elements, this algorithm outperforms the introsort on data with any
intrinsic structure, such as runs of ascending or descending elements
or a significant number of equality collisions.
This commit is contained in:
Nate Cook
2018-11-07 00:05:04 -06:00
committed by GitHub
parent 50e5a66bd5
commit e5c1567957
8 changed files with 953 additions and 400 deletions

View File

@@ -222,83 +222,5 @@ Algorithm.test("sorted/return type") {
let _: Array = ([5, 4, 3, 2, 1] as ArraySlice).sorted()
}
Algorithm.test("sort3/simple")
.forEach(in: [
[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]
]) {
var input = $0
input._sort3(0, 1, 2, by: <)
expectEqual([1, 2, 3], input)
}
func isSorted<T>(_ a: [T], by areInIncreasingOrder: (T, T) -> Bool) -> Bool {
return !a.dropFirst().enumerated().contains(where: { (offset, element) in
areInIncreasingOrder(element, a[offset])
})
}
Algorithm.test("sort3/stable")
.forEach(in: [
[1, 1, 2], [1, 2, 1], [2, 1, 1], [1, 2, 2], [2, 1, 2], [2, 2, 1], [1, 1, 1]
]) {
// decorate with offset, but sort by value
var input = Array($0.enumerated())
input._sort3(0, 1, 2) { $0.element < $1.element }
// offsets should still be ordered for equal values
expectTrue(isSorted(input) {
if $0.element == $1.element {
return $0.offset < $1.offset
}
return $0.element < $1.element
})
}
Algorithm.test("heapSort") {
// This function generate next permutation of 0-1 using long arithmetics
// approach.
func addOne(to num: inout [Int]) {
if num.isEmpty {
return
}
// Consider our num array reflects a binary integer.
// Here we are trying to add one to it.
var i = num.index(before: num.endIndex)
var carrier = 1
while carrier != 0 {
let b = num[i] + carrier
// Updating i's bit.
num[i] = b % 2
// Recalculate new carrier.
carrier = b / 2
// If the length of number was n, we don't want to create new number with
// length n + 1 in this test.
if i == num.startIndex {
break
} else {
num.formIndex(before: &i)
}
}
} // addOne end.
// Test binary number size.
let numberLength = 11
var binaryNumber = [Int](repeating: 0, count: numberLength)
// We are testing sort on all permutations off 0-1s of size `numberLength`
// except the all 1's case (Its equals to all 0's case).
while !binaryNumber.allSatisfy({ $0 == 1 }) {
var buffer = binaryNumber
buffer._heapSort(within: buffer.startIndex..<buffer.endIndex, by: <)
expectTrue(isSorted(buffer, by: <))
addOne(to: &binaryNumber)
}
}
runAllTests()