mirror of
https://github.com/apple/swift.git
synced 2025-12-14 20:36:38 +01:00
[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:
@@ -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()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user