[stdlib] Modernize sort code (#18824)
* Replace bodies of Comparable versions with calls to sort(by:)
* Make _insertionSort a method
* Make _sort3 a method
* Make _partition a method
* Make _introSort a method
* Make _siftDown, _heapify, _heapsort methods
* Other minor cleanup
diff --git a/stdlib/public/core/Sort.swift b/stdlib/public/core/Sort.swift
index 5d8ddfe..f3d3fe1 100644
--- a/stdlib/public/core/Sort.swift
+++ b/stdlib/public/core/Sort.swift
@@ -45,9 +45,7 @@
/// - Complexity: O(*n* log *n*), where *n* is the length of the sequence.
@inlinable
public func sorted() -> [Element] {
- var result = ContiguousArray(self)
- result.sort()
- return Array(result)
+ return sorted(by: <)
}
}
@@ -142,7 +140,6 @@
extension MutableCollection
where Self: RandomAccessCollection, Element: Comparable {
-
/// Sorts the collection in place.
///
/// You can sort any mutable collection of elements that conform to the
@@ -171,13 +168,7 @@
/// - Complexity: O(*n* log *n*), where *n* is the length of the collection.
@inlinable
public mutating func sort() {
- let didSortUnsafeBuffer = _withUnsafeMutableBufferPointerIfSupported {
- buffer -> Void? in
- buffer.sort()
- }
- if didSortUnsafeBuffer == nil {
- _introSort(&self, subRange: startIndex..<endIndex, by: <)
- }
+ sort(by: <)
}
}
@@ -261,333 +252,306 @@
try buffer.sort(by: areInIncreasingOrder)
}
if didSortUnsafeBuffer == nil {
- try _introSort(
- &self,
- subRange: startIndex..<endIndex,
- by: areInIncreasingOrder)
+ try _introSort(within: startIndex..<endIndex, by: areInIncreasingOrder)
}
}
}
-@inlinable
-internal func _insertionSort<C: MutableCollection & BidirectionalCollection>(
- _ elements: inout C,
- subRange range: Range<C.Index>,
- by areInIncreasingOrder: (C.Element, C.Element) throws -> Bool
-) rethrows {
+extension MutableCollection where Self: BidirectionalCollection {
+ @inlinable
+ internal mutating func _insertionSort(
+ within range: Range<Index>,
+ by areInIncreasingOrder: (Element, Element) throws -> Bool
+ ) rethrows {
- if !range.isEmpty {
+ guard !range.isEmpty else { return }
+
let start = range.lowerBound
- // Keep track of the end of the initial sequence of sorted
- // elements.
- var sortedEnd = start
+ // Keep track of the end of the initial sequence of sorted elements. One
+ // element is trivially already-sorted, thus pre-increment Continue until
+ // the sorted elements cover the whole sequence
+ var sortedEnd = index(after: start)
- // One element is trivially already-sorted, thus pre-increment
- // Continue until the sorted elements cover the whole sequence
- elements.formIndex(after: &sortedEnd)
while sortedEnd != range.upperBound {
// get the first unsorted element
- let x: C.Element = elements[sortedEnd]
+ // FIXME: by stashing the element, instead of using indexing and swapAt,
+ // this method won't work for collections of move-only types.
+ let x = self[sortedEnd]
// Look backwards for x's position in the sorted sequence,
// moving elements forward to make room.
var i = sortedEnd
repeat {
- let predecessor: C.Element = elements[elements.index(before: i)]
+ let j = index(before: i)
+ let predecessor = self[j]
- // If clouser throws the error, We catch the error put the element at right
- // place and rethrow the error.
+ // If closure throws, put the element at right place and rethrow.
do {
// if x doesn't belong before y, we've found its position
- if !(try areInIncreasingOrder(x, predecessor)) {
+ if try !areInIncreasingOrder(x, predecessor) {
break
}
} catch {
- elements[i] = x
+ self[i] = x
throw error
}
// Move y forward
- elements[i] = predecessor
- elements.formIndex(before: &i)
+ self[i] = predecessor
+ i = j
} while i != start
if i != sortedEnd {
// Plop x into position
- elements[i] = x
+ self[i] = x
}
- elements.formIndex(after: &sortedEnd)
+ formIndex(after: &sortedEnd)
}
}
}
-/// Sorts the elements at `elements[a]`, `elements[b]`, and `elements[c]`.
-/// Stable.
-///
-/// The indices passed as `a`, `b`, and `c` do not need to be consecutive, but
-/// must be in strict increasing order.
-///
-/// - Precondition: `a < b && b < c`
-/// - Postcondition: `elements[a] <= elements[b] && elements[b] <= elements[c]`
-@inlinable
-public // @testable
-func _sort3<C: MutableCollection & RandomAccessCollection>(
- _ elements: inout C,
- _ a: C.Index, _ b: C.Index, _ c: C.Index,
- by areInIncreasingOrder: (C.Element, C.Element) throws -> Bool
-) rethrows {
- // There are thirteen possible permutations for the original ordering of
- // the elements at indices `a`, `b`, and `c`. The comments in the code below
- // show the relative ordering of the three elements using a three-digit
- // number as shorthand for the position and comparative relationship of
- // each element. For example, "312" indicates that the element at `a` is the
- // largest of the three, the element at `b` is the smallest, and the element
- // at `c` is the median. This hypothetical input array has a 312 ordering for
- // `a`, `b`, and `c`:
- //
- // [ 7, 4, 3, 9, 2, 0, 3, 7, 6, 5 ]
- // ^ ^ ^
- // a b c
- //
- // - If each of the three elements is distinct, they could be ordered as any
- // of the permutations of 1, 2, and 3: 123, 132, 213, 231, 312, or 321.
- // - If two elements are equivalent and one is distinct, they could be
- // ordered as any permutation of 1, 1, and 2 or 1, 2, and 2: 112, 121, 211,
- // 122, 212, or 221.
- // - If all three elements are equivalent, they are already in order: 111.
+extension MutableCollection {
+ /// Sorts the elements at `elements[a]`, `elements[b]`, and `elements[c]`.
+ /// Stable.
+ ///
+ /// The indices passed as `a`, `b`, and `c` do not need to be consecutive, but
+ /// must be in strict increasing order.
+ ///
+ /// - Precondition: `a < b && b < c`
+ /// - Postcondition: `self[a] <= self[b] && self[b] <= self[c]`
+ @inlinable
+ public // @testable
+ mutating func _sort3(
+ _ a: Index, _ b: Index, _ c: Index,
+ by areInIncreasingOrder: (Element, Element) throws -> Bool
+ ) rethrows {
+ // There are thirteen possible permutations for the original ordering of
+ // the elements at indices `a`, `b`, and `c`. The comments in the code below
+ // show the relative ordering of the three elements using a three-digit
+ // number as shorthand for the position and comparative relationship of
+ // each element. For example, "312" indicates that the element at `a` is the
+ // largest of the three, the element at `b` is the smallest, and the element
+ // at `c` is the median. This hypothetical input array has a 312 ordering for
+ // `a`, `b`, and `c`:
+ //
+ // [ 7, 4, 3, 9, 2, 0, 3, 7, 6, 5 ]
+ // ^ ^ ^
+ // a b c
+ //
+ // - If each of the three elements is distinct, they could be ordered as any
+ // of the permutations of 1, 2, and 3: 123, 132, 213, 231, 312, or 321.
+ // - If two elements are equivalent and one is distinct, they could be
+ // ordered as any permutation of 1, 1, and 2 or 1, 2, and 2: 112, 121, 211,
+ // 122, 212, or 221.
+ // - If all three elements are equivalent, they are already in order: 111.
- switch ((try areInIncreasingOrder(elements[b], elements[a])),
- (try areInIncreasingOrder(elements[c], elements[b]))) {
- case (false, false):
- // 0 swaps: 123, 112, 122, 111
- break
-
- case (true, true):
- // 1 swap: 321
- // swap(a, c): 312->123
- elements.swapAt(a, c)
-
- case (true, false):
- // 1 swap: 213, 212 --- 2 swaps: 312, 211
- // swap(a, b): 213->123, 212->122, 312->132, 211->121
- elements.swapAt(a, b)
-
- if (try areInIncreasingOrder(elements[c], elements[b])) {
- // 132 (started as 312), 121 (started as 211)
- // swap(b, c): 132->123, 121->112
- elements.swapAt(b, c)
- }
-
- case (false, true):
- // 1 swap: 132, 121 --- 2 swaps: 231, 221
- // swap(b, c): 132->123, 121->112, 231->213, 221->212
- elements.swapAt(b, c)
-
- if (try areInIncreasingOrder(elements[b], elements[a])) {
- // 213 (started as 231), 212 (started as 221)
- // swap(a, b): 213->123, 212->122
- elements.swapAt(a, b)
- }
- }
-}
-
-/// Reorders `elements` and returns an index `p` such that every element in
-/// `elements[range.lowerBound..<p]` is less than every element in
-/// `elements[p..<range.upperBound]`.
-///
-/// - Precondition: The count of `range` must be >= 3:
-/// `elements.distance(from: range.lowerBound, to: range.upperBound) >= 3`
-@inlinable
-internal func _partition<C: MutableCollection & RandomAccessCollection>(
- _ elements: inout C,
- subRange range: Range<C.Index>,
- by areInIncreasingOrder: (C.Element, C.Element) throws -> Bool
-) rethrows -> C.Index {
- var lo = range.lowerBound
- var hi = elements.index(before: range.upperBound)
-
- // Sort the first, middle, and last elements, then use the middle value
- // as the pivot for the partition.
- let half = numericCast(elements.distance(from: lo, to: hi)) as UInt / 2
- let mid = elements.index(lo, offsetBy: numericCast(half))
- try _sort3(&elements, lo, mid, hi
- , by: areInIncreasingOrder)
- let pivot = elements[mid]
-
- // Loop invariants:
- // * lo < hi
- // * elements[i] < pivot, for i in range.lowerBound..<lo
- // * pivot <= elements[i] for i in hi..<range.upperBound
- Loop: while true {
- FindLo: do {
- elements.formIndex(after: &lo)
- while lo != hi {
- if !(try areInIncreasingOrder(elements[lo], pivot)) { break FindLo }
- elements.formIndex(after: &lo)
- }
- break Loop
- }
-
- FindHi: do {
- elements.formIndex(before: &hi)
- while hi != lo {
- if (try areInIncreasingOrder(elements[hi], pivot)) { break FindHi }
- elements.formIndex(before: &hi)
- }
- break Loop
- }
-
- elements.swapAt(lo, hi)
- }
-
- return lo
-}
-
-@inlinable
-public // @testable
-func _introSort<C: MutableCollection & RandomAccessCollection>(
- _ elements: inout C,
- subRange range: Range<C.Index>
- , by areInIncreasingOrder: (C.Element, C.Element) throws -> Bool
-) rethrows {
-
- let count = elements.distance(from: range.lowerBound, to: range.upperBound)
- if count < 2 {
- return
- }
- // Set max recursion depth to 2*floor(log(N)), as suggested in the introsort
- // paper: http://www.cs.rpi.edu/~musser/gp/introsort.ps
- let depthLimit = 2 * count._binaryLogarithm()
- try _introSortImpl(
- &elements,
- subRange: range,
- by: areInIncreasingOrder,
- depthLimit: depthLimit)
-}
-
-@inlinable
-internal func _introSortImpl<C: MutableCollection & RandomAccessCollection>(
- _ elements: inout C,
- subRange range: Range<C.Index>
- , by areInIncreasingOrder: (C.Element, C.Element) throws -> Bool,
- depthLimit: Int
-) rethrows {
-
- // Insertion sort is better at handling smaller regions.
- if elements.distance(from: range.lowerBound, to: range.upperBound) < 20 {
- try _insertionSort(
- &elements,
- subRange: range
- , by: areInIncreasingOrder)
- return
- }
- if depthLimit == 0 {
- try _heapSort(
- &elements,
- subRange: range
- , by: areInIncreasingOrder)
- return
- }
-
- // Partition and sort.
- // We don't check the depthLimit variable for underflow because this variable
- // is always greater than zero (see check above).
- let partIdx: C.Index = try _partition(
- &elements,
- subRange: range
- , by: areInIncreasingOrder)
- try _introSortImpl(
- &elements,
- subRange: range.lowerBound..<partIdx,
- by: areInIncreasingOrder,
- depthLimit: depthLimit &- 1)
- try _introSortImpl(
- &elements,
- subRange: partIdx..<range.upperBound,
- by: areInIncreasingOrder,
- depthLimit: depthLimit &- 1)
-}
-
-@inlinable
-internal func _siftDown<C: MutableCollection & RandomAccessCollection>(
- _ elements: inout C,
- index: C.Index,
- subRange range: Range<C.Index>,
- by areInIncreasingOrder: (C.Element, C.Element) throws -> Bool
-) rethrows {
- var i = index
- var countToIndex = elements.distance(from: range.lowerBound, to: i)
- var countFromIndex = elements.distance(from: i, to: range.upperBound)
- // Check if left child is within bounds. If not, stop iterating, because there are
- // no children of the given node in the heap.
- while countToIndex + 1 < countFromIndex {
- let left = elements.index(i, offsetBy: countToIndex + 1)
- var largest = i
- if try areInIncreasingOrder(elements[largest], elements[left]) {
- largest = left
- }
- // Check if right child is also within bounds before trying to examine it.
- if countToIndex + 2 < countFromIndex {
- let right = elements.index(after: left)
- if try areInIncreasingOrder(elements[largest], elements[right]) {
- largest = right
- }
- }
- // If a child is bigger than the current node, swap them and continue sifting
- // down.
- if largest != i {
- elements.swapAt(index, largest)
- i = largest
- countToIndex = elements.distance(from: range.lowerBound, to: i)
- countFromIndex = elements.distance(from: i, to: range.upperBound)
- } else {
+ switch try (areInIncreasingOrder(self[b], self[a]),
+ areInIncreasingOrder(self[c], self[b])) {
+ case (false, false):
+ // 0 swaps: 123, 112, 122, 111
break
+
+ case (true, true):
+ // 1 swap: 321
+ // swap(a, c): 312->123
+ swapAt(a, c)
+
+ case (true, false):
+ // 1 swap: 213, 212 --- 2 swaps: 312, 211
+ // swap(a, b): 213->123, 212->122, 312->132, 211->121
+ swapAt(a, b)
+
+ if try areInIncreasingOrder(self[c], self[b]) {
+ // 132 (started as 312), 121 (started as 211)
+ // swap(b, c): 132->123, 121->112
+ swapAt(b, c)
+ }
+
+ case (false, true):
+ // 1 swap: 132, 121 --- 2 swaps: 231, 221
+ // swap(b, c): 132->123, 121->112, 231->213, 221->212
+ swapAt(b, c)
+
+ if try areInIncreasingOrder(self[b], self[a]) {
+ // 213 (started as 231), 212 (started as 221)
+ // swap(a, b): 213->123, 212->122
+ swapAt(a, b)
+ }
}
}
}
-@inlinable
-internal func _heapify<C: MutableCollection & RandomAccessCollection>(
- _ elements: inout C,
- subRange range: Range<C.Index>,
- by areInIncreasingOrder: (C.Element, C.Element) throws -> Bool
-) rethrows {
- // Here we build a heap starting from the lowest nodes and moving to the root.
- // On every step we sift down the current node to obey the max-heap property:
- // parent >= max(leftChild, rightChild)
- //
- // We skip the rightmost half of the array, because these nodes don't have
- // any children.
- let root = range.lowerBound
- var node = elements.index(
- root,
- offsetBy: elements.distance(
- from: range.lowerBound, to: range.upperBound) / 2)
+extension MutableCollection where Self: RandomAccessCollection {
+ /// Reorders the collection and returns an index `p` such that every element
+ /// in `range.lowerBound..<p` is less than every element in
+ /// `p..<range.upperBound`.
+ ///
+ /// - Precondition: The count of `range` must be >= 3 i.e.
+ /// `distance(from: range.lowerBound, to: range.upperBound) >= 3`
+ @inlinable
+ internal mutating func _partition(
+ within range: Range<Index>,
+ by areInIncreasingOrder: (Element, Element) throws -> Bool
+ ) rethrows -> Index {
+ var lo = range.lowerBound
+ var hi = index(before: range.upperBound)
- while node != root {
- elements.formIndex(before: &node)
- try _siftDown(
- &elements,
- index: node,
- subRange: range
- , by: areInIncreasingOrder)
+ // Sort the first, middle, and last elements, then use the middle value
+ // as the pivot for the partition.
+ let half = distance(from: lo, to: hi) / 2
+ let mid = index(lo, offsetBy: half)
+ try _sort3(lo, mid, hi, by: areInIncreasingOrder)
+ let pivot = self[mid]
+
+ // Loop invariants:
+ // * lo < hi
+ // * self[i] < pivot, for i in range.lowerBound..<lo
+ // * pivot <= self[i] for i in hi..<range.upperBound
+ Loop: while true {
+ FindLo: do {
+ formIndex(after: &lo)
+ while lo != hi {
+ if try !areInIncreasingOrder(self[lo], pivot) { break FindLo }
+ formIndex(after: &lo)
+ }
+ break Loop
+ }
+
+ FindHi: do {
+ formIndex(before: &hi)
+ while hi != lo {
+ if try areInIncreasingOrder(self[hi], pivot) { break FindHi }
+ formIndex(before: &hi)
+ }
+ break Loop
+ }
+
+ swapAt(lo, hi)
+ }
+
+ return lo
}
-}
-@inlinable
-internal func _heapSort<C: MutableCollection & RandomAccessCollection>(
- _ elements: inout C,
- subRange range: Range<C.Index>
- , by areInIncreasingOrder: (C.Element, C.Element) throws -> Bool
-) rethrows {
- var hi = range.upperBound
- let lo = range.lowerBound
- try _heapify(&elements, subRange: range, by: areInIncreasingOrder)
- elements.formIndex(before: &hi)
- while hi != lo {
- elements.swapAt(lo, hi)
- try _siftDown(&elements, index: lo, subRange: lo..<hi, by: areInIncreasingOrder)
- elements.formIndex(before: &hi)
+ @inlinable
+ public // @testable
+ mutating func _introSort(
+ within range: Range<Index>,
+ by areInIncreasingOrder: (Element, Element) throws -> Bool
+ ) rethrows {
+
+ let n = distance(from: range.lowerBound, to: range.upperBound)
+ guard n > 1 else { return }
+
+ // Set max recursion depth to 2*floor(log(N)), as suggested in the introsort
+ // paper: http://www.cs.rpi.edu/~musser/gp/introsort.ps
+ let depthLimit = 2 * n._binaryLogarithm()
+ try _introSortImpl(
+ within: range,
+ by: areInIncreasingOrder,
+ depthLimit: depthLimit)
+ }
+
+ @inlinable
+ internal mutating func _introSortImpl(
+ within range: Range<Index>,
+ by areInIncreasingOrder: (Element, Element) throws -> Bool,
+ depthLimit: Int
+ ) rethrows {
+
+ // Insertion sort is better at handling smaller regions.
+ if distance(from: range.lowerBound, to: range.upperBound) < 20 {
+ try _insertionSort(within: range, by: areInIncreasingOrder)
+ } else if depthLimit == 0 {
+ try _heapSort(within: range, by: areInIncreasingOrder)
+ } else {
+ // Partition and sort.
+ // We don't check the depthLimit variable for underflow because this
+ // variable is always greater than zero (see check above).
+ let partIdx = try _partition(within: range, by: areInIncreasingOrder)
+ try _introSortImpl(
+ within: range.lowerBound..<partIdx,
+ by: areInIncreasingOrder,
+ depthLimit: depthLimit &- 1)
+ try _introSortImpl(
+ within: partIdx..<range.upperBound,
+ by: areInIncreasingOrder,
+ depthLimit: depthLimit &- 1)
+ }
+ }
+
+ @inlinable
+ internal mutating func _siftDown(
+ _ idx: Index,
+ within range: Range<Index>,
+ by areInIncreasingOrder: (Element, Element) throws -> Bool
+ ) rethrows {
+ var i = idx
+ var countToIndex = distance(from: range.lowerBound, to: i)
+ var countFromIndex = distance(from: i, to: range.upperBound)
+ // Check if left child is within bounds. If not, stop iterating, because
+ // there are no children of the given node in the heap.
+ while countToIndex + 1 < countFromIndex {
+ let left = index(i, offsetBy: countToIndex + 1)
+ var largest = i
+ if try areInIncreasingOrder(self[largest], self[left]) {
+ largest = left
+ }
+ // Check if right child is also within bounds before trying to examine it.
+ if countToIndex + 2 < countFromIndex {
+ let right = index(after: left)
+ if try areInIncreasingOrder(self[largest], self[right]) {
+ largest = right
+ }
+ }
+ // If a child is bigger than the current node, swap them and continue sifting
+ // down.
+ if largest != i {
+ swapAt(idx, largest)
+ i = largest
+ countToIndex = distance(from: range.lowerBound, to: i)
+ countFromIndex = distance(from: i, to: range.upperBound)
+ } else {
+ break
+ }
+ }
+ }
+
+ @inlinable
+ internal mutating func _heapify(
+ within range: Range<Index>,
+ by areInIncreasingOrder: (Element, Element) throws -> Bool
+ ) rethrows {
+ // Here we build a heap starting from the lowest nodes and moving to the
+ // root. On every step we sift down the current node to obey the max-heap
+ // property:
+ // parent >= max(leftChild, rightChild)
+ //
+ // We skip the rightmost half of the array, because these nodes don't have
+ // any children.
+ let root = range.lowerBound
+ let half = distance(from: range.lowerBound, to: range.upperBound) / 2
+ var node = index(root, offsetBy: half)
+
+ while node != root {
+ formIndex(before: &node)
+ try _siftDown(node, within: range, by: areInIncreasingOrder)
+ }
+ }
+
+ @inlinable
+ internal mutating func _heapSort(
+ within range: Range<Index>,
+ by areInIncreasingOrder: (Element, Element) throws -> Bool
+ ) rethrows {
+ var hi = range.upperBound
+ let lo = range.lowerBound
+ try _heapify(within: range, by: areInIncreasingOrder)
+ formIndex(before: &hi)
+ while hi != lo {
+ swapAt(lo, hi)
+ try _siftDown(lo, within: lo..<hi, by: areInIncreasingOrder)
+ formIndex(before: &hi)
+ }
}
}
diff --git a/validation-test/stdlib/Algorithm.swift b/validation-test/stdlib/Algorithm.swift
index ef15ea3..e3405a6 100644
--- a/validation-test/stdlib/Algorithm.swift
+++ b/validation-test/stdlib/Algorithm.swift
@@ -243,7 +243,7 @@
]) {
// decorate with offset, but sort by value
var input = Array($0.enumerated())
- _sort3(&input, 0, 1, 2) { $0.element < $1.element }
+ 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 {
diff --git a/validation-test/stdlib/Sort.swift.gyb b/validation-test/stdlib/Sort.swift.gyb
index 3ebd3e9..6cdd31c 100644
--- a/validation-test/stdlib/Sort.swift.gyb
+++ b/validation-test/stdlib/Sort.swift.gyb
@@ -120,7 +120,7 @@
let i1 = 400
let i2 = 700
sortedAry2 = ary
- _introSort(&sortedAry2, subRange: i1..<i2, by: <)
+ sortedAry2._introSort(within: i1..<i2, by: <)
expectEqual(ary[0..<i1], sortedAry2[0..<i1])
expectSortedCollection(sortedAry2[i1..<i2], ary[i1..<i2])
expectEqual(ary[i2..<count], sortedAry2[i2..<count])