diff --git a/stdlib/public/core/CollectionAlgorithms.swift b/stdlib/public/core/CollectionAlgorithms.swift index 650006d89763e..92364033a9a32 100644 --- a/stdlib/public/core/CollectionAlgorithms.swift +++ b/stdlib/public/core/CollectionAlgorithms.swift @@ -320,14 +320,21 @@ extension MutableCollection where Self : BidirectionalCollection { ) rethrows -> Index { let maybeOffset = try _withUnsafeMutableBufferPointerIfSupported { (bufferPointer) -> Int in - let unsafeBufferPivot = try bufferPointer.partition( + let unsafeBufferPivot = try bufferPointer._partitionImpl( by: belongsInSecondPartition) return unsafeBufferPivot - bufferPointer.startIndex } if let offset = maybeOffset { - return index(startIndex, offsetBy: numericCast(offset)) + return index(startIndex, offsetBy: offset) + } else { + return try _partitionImpl(by: belongsInSecondPartition) } - + } + + @usableFromInline + internal mutating func _partitionImpl( + by belongsInSecondPartition: (Element) throws -> Bool + ) rethrows -> Index { var lo = startIndex var hi = endIndex diff --git a/stdlib/public/core/Sort.swift b/stdlib/public/core/Sort.swift index 622f27a9b815e..e250731bb1c1e 100644 --- a/stdlib/public/core/Sort.swift +++ b/stdlib/public/core/Sort.swift @@ -21,9 +21,6 @@ extension Sequence where Element: Comparable { /// You can sort any sequence of elements that conform to the `Comparable` /// protocol by calling this method. Elements are sorted in ascending order. /// - /// The sorting algorithm is not stable. A nonstable sort may change the - /// relative order of elements that compare equal. - /// /// Here's an example of sorting a list of students' names. Strings in Swift /// conform to the `Comparable` protocol, so the names are sorted in /// ascending order according to the less-than operator (`<`). @@ -40,6 +37,9 @@ extension Sequence where Element: Comparable { /// print(descendingStudents) /// // Prints "["Peter", "Kweku", "Kofi", "Akosua", "Abena"]" /// + /// The sorting algorithm is not guaranteed to be stable. A stable sort + /// preserves the relative order of elements that compare equal. + /// /// - Returns: A sorted array of the sequence's elements. /// /// - Complexity: O(*n* log *n*), where *n* is the length of the sequence. @@ -55,26 +55,9 @@ extension Sequence { /// /// When you want to sort a sequence of elements that don't conform to the /// `Comparable` protocol, pass a predicate to this method that returns - /// `true` when the first element passed should be ordered before the - /// second. The elements of the resulting array are ordered according to the - /// given predicate. - /// - /// The predicate must be a *strict weak ordering* over the elements. That - /// is, for any elements `a`, `b`, and `c`, the following conditions must - /// hold: - /// - /// - `areInIncreasingOrder(a, a)` is always `false`. (Irreflexivity) - /// - If `areInIncreasingOrder(a, b)` and `areInIncreasingOrder(b, c)` are - /// both `true`, then `areInIncreasingOrder(a, c)` is also `true`. - /// (Transitive comparability) - /// - Two elements are *incomparable* if neither is ordered before the other - /// according to the predicate. If `a` and `b` are incomparable, and `b` - /// and `c` are incomparable, then `a` and `c` are also incomparable. - /// (Transitive incomparability) - /// - /// The sorting algorithm is not stable. A nonstable sort may change the - /// relative order of elements for which `areInIncreasingOrder` does not - /// establish an order. + /// `true` when the first element should be ordered before the second. The + /// elements of the resulting array are ordered according to the given + /// predicate. /// /// In the following example, the predicate provides an ordering for an array /// of a custom `HTTPResponse` type. The predicate orders errors before @@ -121,6 +104,23 @@ extension Sequence { /// print(students.sorted(by: <)) /// // Prints "["Abena", "Akosua", "Kofi", "Kweku", "Peter"]" /// + /// The predicate must be a *strict weak ordering* over the elements. That + /// is, for any elements `a`, `b`, and `c`, the following conditions must + /// hold: + /// + /// - `areInIncreasingOrder(a, a)` is always `false`. (Irreflexivity) + /// - If `areInIncreasingOrder(a, b)` and `areInIncreasingOrder(b, c)` are + /// both `true`, then `areInIncreasingOrder(a, c)` is also `true`. + /// (Transitive comparability) + /// - Two elements are *incomparable* if neither is ordered before the other + /// according to the predicate. If `a` and `b` are incomparable, and `b` + /// and `c` are incomparable, then `a` and `c` are also incomparable. + /// (Transitive incomparability) + /// + /// The sorting algorithm is not guaranteed to be stable. A stable sort + /// preserves the relative order of elements for which + /// `areInIncreasingOrder` does not establish an order. + /// /// - Parameter areInIncreasingOrder: A predicate that returns `true` if its /// first argument should be ordered before its second argument; /// otherwise, `false`. @@ -146,9 +146,6 @@ where Self: RandomAccessCollection, Element: Comparable { /// `Comparable` protocol by calling this method. Elements are sorted in /// ascending order. /// - /// The sorting algorithm is not stable. A nonstable sort may change the - /// relative order of elements that compare equal. - /// /// Here's an example of sorting a list of students' names. Strings in Swift /// conform to the `Comparable` protocol, so the names are sorted in /// ascending order according to the less-than operator (`<`). @@ -165,6 +162,9 @@ where Self: RandomAccessCollection, Element: Comparable { /// print(students) /// // Prints "["Peter", "Kweku", "Kofi", "Akosua", "Abena"]" /// + /// The sorting algorithm is not guaranteed to be stable. A stable sort + /// preserves the relative order of elements that compare equal. + /// /// - Complexity: O(*n* log *n*), where *n* is the length of the collection. @inlinable public mutating func sort() { @@ -176,27 +176,9 @@ extension MutableCollection where Self: RandomAccessCollection { /// Sorts the collection in place, using the given predicate as the /// comparison between elements. /// - /// When you want to sort a collection of elements that doesn't conform to + /// When you want to sort a collection of elements that don't conform to /// the `Comparable` protocol, pass a closure to this method that returns - /// `true` when the first element passed should be ordered before the - /// second. - /// - /// The predicate must be a *strict weak ordering* over the elements. That - /// is, for any elements `a`, `b`, and `c`, the following conditions must - /// hold: - /// - /// - `areInIncreasingOrder(a, a)` is always `false`. (Irreflexivity) - /// - If `areInIncreasingOrder(a, b)` and `areInIncreasingOrder(b, c)` are - /// both `true`, then `areInIncreasingOrder(a, c)` is also `true`. - /// (Transitive comparability) - /// - Two elements are *incomparable* if neither is ordered before the other - /// according to the predicate. If `a` and `b` are incomparable, and `b` - /// and `c` are incomparable, then `a` and `c` are also incomparable. - /// (Transitive incomparability) - /// - /// The sorting algorithm is not stable. A nonstable sort may change the - /// relative order of elements for which `areInIncreasingOrder` does not - /// establish an order. + /// `true` when the first element should be ordered before the second. /// /// In the following example, the closure provides an ordering for an array /// of a custom enumeration that describes an HTTP response. The predicate @@ -236,6 +218,23 @@ extension MutableCollection where Self: RandomAccessCollection { /// print(students) /// // Prints "["Peter", "Kweku", "Kofi", "Akosua", "Abena"]" /// + /// `areInIncreasingOrder` must be a *strict weak ordering* over the + /// elements. That is, for any elements `a`, `b`, and `c`, the following + /// conditions must hold: + /// + /// - `areInIncreasingOrder(a, a)` is always `false`. (Irreflexivity) + /// - If `areInIncreasingOrder(a, b)` and `areInIncreasingOrder(b, c)` are + /// both `true`, then `areInIncreasingOrder(a, c)` is also `true`. + /// (Transitive comparability) + /// - Two elements are *incomparable* if neither is ordered before the other + /// according to the predicate. If `a` and `b` are incomparable, and `b` + /// and `c` are incomparable, then `a` and `c` are also incomparable. + /// (Transitive incomparability) + /// + /// The sorting algorithm is not guaranteed to be stable. A stable sort + /// preserves the relative order of elements for which + /// `areInIncreasingOrder` does not establish an order. + /// /// - Parameter areInIncreasingOrder: A predicate that returns `true` if its /// first argument should be ordered before its second argument; /// otherwise, `false`. If `areInIncreasingOrder` throws an error during @@ -249,310 +248,454 @@ extension MutableCollection where Self: RandomAccessCollection { ) rethrows { let didSortUnsafeBuffer = try _withUnsafeMutableBufferPointerIfSupported { buffer -> Void? in - try buffer.sort(by: areInIncreasingOrder) + try buffer._stableSortImpl(by: areInIncreasingOrder) } if didSortUnsafeBuffer == nil { - try _introSort(within: startIndex.., + within range: Range, + sortedEnd: Index, by areInIncreasingOrder: (Element, Element) throws -> Bool ) rethrows { - - guard !range.isEmpty else { return } - - let start = range.lowerBound - - // 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) - + var sortedEnd = sortedEnd + + // Continue sorting until the sorted elements cover the whole sequence. while sortedEnd != range.upperBound { - // get the first unsorted element - // 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 + // Look backwards for `self[i]`'s position in the sorted sequence, + // moving each element forward to make room. repeat { let j = index(before: i) - let predecessor = self[j] - - // 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) { - break - } - } catch { - self[i] = x - throw error + + // If `self[i]` doesn't belong before `self[j]`, we've found + // its position. + if try !areInIncreasingOrder(self[i], self[j]) { + break } - - // Move y forward - self[i] = predecessor + + swapAt(i, j) i = j - } while i != start - - if i != sortedEnd { - // Plop x into position - self[i] = x - } + } while i != range.lowerBound + formIndex(after: &sortedEnd) } } -} - -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]` + + /// Sorts `self[range]` according to `areInIncreasingOrder`. Stable. @inlinable public // @testable - mutating func _sort3( - _ a: Index, _ b: Index, _ c: Index, + mutating func _insertionSort( + within range: Range, 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(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) - } + if range.isEmpty { + return } + + // One element is trivially already-sorted, so the actual sort can + // start on the second element. + let sortedEnd = index(after: range.lowerBound) + try _insertionSort( + within: range, sortedEnd: sortedEnd, by: areInIncreasingOrder) } -} - -extension MutableCollection where Self: RandomAccessCollection { - /// Reorders the collection and returns an index `p` such that every element - /// in `range.lowerBound..= 3 i.e. - /// `distance(from: range.lowerBound, to: range.upperBound) >= 3` + + /// Reverses the elements in the given range. @inlinable - internal mutating func _partition( - within range: Range, - by areInIncreasingOrder: (Element, Element) throws -> Bool - ) rethrows -> Index { - var lo = range.lowerBound - var hi = index(before: range.upperBound) - - // 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] + internal mutating func _reverse( + within range: Range + ) { + var f = range.lowerBound + var l = range.upperBound + while f < l { + formIndex(before: &l) + swapAt(f, l) + formIndex(after: &f) + } + } +} - // Loop invariants: - // * lo < hi - // * self[i] < pivot, for i in range.lowerBound..( + low: UnsafeMutablePointer, + mid: UnsafeMutablePointer, + high: UnsafeMutablePointer, + buffer: UnsafeMutablePointer, + by areInIncreasingOrder: (Element, Element) throws -> Bool +) rethrows -> Bool { + let lowCount = mid - low + let highCount = high - mid + + var destLow = low // Lower bound of uninitialized storage + var bufferLow = buffer // Lower bound of the initialized buffer + var bufferHigh = buffer // Upper bound of the initialized buffer + + // When we exit the merge, move any remaining elements from the buffer back + // into `destLow` (aka the collection we're sorting). The buffer can have + // remaining elements if `areIncreasingOrder` throws, or more likely if the + // merge runs out of elements from the array before exhausting the buffer. + defer { + destLow.moveInitialize(from: bufferLow, count: bufferHigh - bufferLow) + } + + if lowCount < highCount { + // Move the lower group of elements into the buffer, then traverse from + // low to high in both the buffer and the higher group of elements. + // + // After moving elements, the storage and buffer look like this, where + // `x` is uninitialized memory: + // + // Storage: [x, x, x, x, x, 6, 8, 8, 10, 12, 15] + // ^ ^ + // destLow srcLow + // + // Buffer: [4, 4, 7, 8, 9, x, ...] + // ^ ^ + // bufferLow bufferHigh + buffer.moveInitialize(from: low, count: lowCount) + bufferHigh = bufferLow + lowCount + + var srcLow = mid + + // Each iteration moves the element that compares lower into `destLow`, + // preferring the buffer when equal to maintain stability. Elements are + // moved from either `bufferLow` or `srcLow`, with those pointers + // incrementing as elements are moved. + while bufferLow < bufferHigh && srcLow < high { + if try areInIncreasingOrder(srcLow.pointee, bufferLow.pointee) { + destLow.moveInitialize(from: srcLow, count: 1) + srcLow += 1 + } else { + destLow.moveInitialize(from: bufferLow, count: 1) + bufferLow += 1 } - - FindHi: do { - formIndex(before: &hi) - while hi != lo { - if try areInIncreasingOrder(self[hi], pivot) { break FindHi } - formIndex(before: &hi) - } - break Loop + destLow += 1 + } + } else { + // Move the higher group of elements into the buffer, then traverse from + // high to low in both the buffer and the lower group of elements. + // + // After moving elements, the storage and buffer look like this, where + // `x` is uninitialized memory: + // + // Storage: [4, 4, 7, 8, 9, 6, x, x, x, x, x] + // ^ ^ ^ + // srcHigh destLow destHigh (past the end) + // + // Buffer: [8, 8, 10, 12, 15, x, ...] + // ^ ^ + // bufferLow bufferHigh + buffer.moveInitialize(from: mid, count: highCount) + bufferHigh = bufferLow + highCount + + var destHigh = high + var srcHigh = mid + destLow = mid + + // Each iteration moves the element that compares higher into `destHigh`, + // preferring the buffer when equal to maintain stability. Elements are + // moved from either `bufferHigh - 1` or `srcHigh - 1`, with those + // pointers decrementing as elements are moved. + // + // Note: At the start of each iteration, each `...High` pointer points one + // past the element they're referring to. + while bufferHigh > bufferLow && srcHigh > low { + destHigh -= 1 + if try areInIncreasingOrder( + (bufferHigh - 1).pointee, (srcHigh - 1).pointee + ) { + srcHigh -= 1 + destHigh.moveInitialize(from: srcHigh, count: 1) + + // Moved an element from the lower initialized portion to the upper, + // sorted, initialized portion, so `destLow` moves down one. + destLow -= 1 + } else { + bufferHigh -= 1 + destHigh.moveInitialize(from: bufferHigh, count: 1) } - - swapAt(lo, hi) } - - return lo } - @inlinable - public // @testable - mutating func _introSort( - within range: Range, - by areInIncreasingOrder: (Element, Element) throws -> Bool - ) rethrows { + // FIXME: Remove this, it works around rdar://problem/45044610 + return true +} - let n = distance(from: range.lowerBound, to: range.upperBound) - guard n > 1 else { return } +/// Calculates an optimal minimum run length for sorting a collection. +/// +/// "... pick a minrun in range(32, 65) such that N/minrun is exactly a power +/// of 2, or if that isn't possible, is close to, but strictly less than, a +/// power of 2. This is easier to do than it may sound: take the first 6 bits +/// of N, and add 1 if any of the remaining bits are set." +/// - From the Timsort introduction, at +/// https://svn.python.org/projects/python/trunk/Objects/listsort.txt +/// +/// - Parameter c: The number of elements in a collection. +/// - Returns: If `c <= 64`, returns `c`. Otherwise, returns a value in +/// `32...64`. +@inlinable +internal func _minimumMergeRunLength(_ c: Int) -> Int { + // Max out at `2^6 == 64` elements + let bitsToUse = 6 + + if c < 1 << bitsToUse { + return c + } + let offset = (Int.bitWidth - bitsToUse) - c.leadingZeroBitCount + let mask = (1 << offset) - 1 + return c >> offset + (c & mask == 0 ? 0 : 1) +} - // 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) +/// Returns the end of the next in-order run along with a Boolean value +/// indicating whether the elements in `start..( + in elements: C, + from start: C.Index, + by areInIncreasingOrder: (C.Element, C.Element) throws -> Bool +) rethrows -> (end: C.Index, descending: Bool) { + _sanityCheck(start < elements.endIndex) + + var previous = start + var current = elements.index(after: start) + guard current < elements.endIndex else { + // This is a one-element run, so treating it as ascending saves a + // meaningless call to `reverse()`. + return (current, false) } - @inlinable - internal mutating func _introSortImpl( - within range: Range, - by areInIncreasingOrder: (Element, Element) throws -> Bool, - depthLimit: Int - ) rethrows { + // Check whether the run beginning at `start` is ascending or descending. + // An ascending run can include consecutive equal elements, but because a + // descending run will be reversed, it must be strictly descending. + let isDescending = + try areInIncreasingOrder(elements[current], elements[previous]) + + // Advance `current` until there's a break in the ascending / descending + // pattern. + repeat { + previous = current + elements.formIndex(after: ¤t) + } while try current < elements.endIndex && + isDescending == areInIncreasingOrder(elements[current], elements[previous]) + + return(current, isDescending) +} - // 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.. 1` and `i > 0` + /// - Precondition: `buffer` must have at least + /// `min(runs[i].count, runs[i - 1].count)` uninitialized elements. + @inlinable + public mutating func _mergeRuns( + _ runs: inout [Range], + at i: Int, + buffer: UnsafeMutablePointer, + by areInIncreasingOrder: (Element, Element) throws -> Bool + ) rethrows -> Bool { + _sanityCheck(runs[i - 1].upperBound == runs[i].lowerBound) + let low = runs[i - 1].lowerBound + let middle = runs[i].lowerBound + let high = runs[i].upperBound + + let result = try _merge( + low: baseAddress! + low, + mid: baseAddress! + middle, + high: baseAddress! + high, + buffer: buffer, + by: areInIncreasingOrder) + + runs[i - 1] = low.., + public mutating func _mergeTopRuns( + _ runs: inout [Range], + buffer: UnsafeMutablePointer, by areInIncreasingOrder: (Element, Element) throws -> Bool - ) rethrows { - var idx = idx - var countToIndex = distance(from: range.lowerBound, to: idx) - var countFromIndex = distance(from: idx, 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(idx, offsetBy: countToIndex + 1) - var largest = idx - 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 + ) rethrows -> Bool { + // The invariants for the `runs` array are: + // (a) - for all i in 2.. runs[i - 1].count + runs[i].count + // (b) - for c = runs.count - 1: + // - runs[i - 1].count > runs[i].count + // + // Loop until the invariant is satisified for the top four elements of + // `runs`. Because this method is called for every added run, and only + // the top three runs are ever merged, this guarantees the invariant holds + // for the whole array. + // + // At all times, `runs` is one of the following, where W, X, Y, and Z are + // the counts of their respective ranges: + // - [ ...?, W, X, Y, Z ] + // - [ X, Y, Z ] + // - [ Y, Z ] + // + // If W > X + Y, X > Y + Z, and Y > Z, then the invariants are satisfied + // for the entirety of `runs`. + + // FIXME: Remove this, it works around rdar://problem/45044610 + var result = true + + // The invariant is always in place for a single element. + while runs.count > 1 { + var lastIndex = runs.count - 1 + + // Check for the three invariant-breaking conditions, and break out of + // the while loop if none are met. + if lastIndex >= 3 && + (runs[lastIndex - 3].count <= + runs[lastIndex - 2].count + runs[lastIndex - 1].count) + { + // Second-to-last three runs do not follow W > X + Y. + // Always merge Y with the smaller of X or Z. + if runs[lastIndex - 2].count < runs[lastIndex].count { + lastIndex -= 1 } - } - // If a child is bigger than the current node, swap them and continue - // sifting down. - if largest != idx { - swapAt(idx, largest) - idx = largest - countToIndex = distance(from: range.lowerBound, to: idx) - countFromIndex = distance(from: idx, to: range.upperBound) + } else if lastIndex >= 2 && + (runs[lastIndex - 2].count <= + runs[lastIndex - 1].count + runs[lastIndex].count) + { + // Last three runs do not follow X > Y + Z. + // Always merge Y with the smaller of X or Z. + if runs[lastIndex - 2].count < runs[lastIndex].count { + lastIndex -= 1 + } + } else if runs[lastIndex - 1].count <= runs[lastIndex].count { + // Last two runs do not follow Y > Z, so merge Y and Z. + // This block is intentionally blank--the merge happens below. } else { + // All invariants satisfied! break } + + // Merge the runs at `i` and `i - 1`. + result = try result && _mergeRuns( + &runs, at: lastIndex, buffer: buffer, by: areInIncreasingOrder) } - } + return result + } + + /// Merges elements of `runs` until only one run remains. + /// + /// - Precondition: `buffer` must have at least + /// `min(runs[i].count, runs[i - 1].count)` uninitialized elements. + /// - Precondition: The ranges in `runs` must be consecutive, such that for + /// any i, `runs[i].upperBound == runs[i + 1].lowerBound`. @inlinable - internal mutating func _heapify( - within range: Range, + public mutating func _finalizeRuns( + _ runs: inout [Range], + buffer: UnsafeMutablePointer, 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) + ) rethrows -> Bool { + // FIXME: Remove this, it works around rdar://problem/45044610 + var result = true + while runs.count > 1 { + result = try result && _mergeRuns( + &runs, at: runs.count - 1, buffer: buffer, by: areInIncreasingOrder) } + return result } - + + /// Sorts the elements of this buffer according to `areInIncreasingOrder`, + /// using a stable, adaptive merge sort. + /// + /// The adaptive algorithm used is Timsort, modified to perform a straight + /// merge of the elements using a temporary buffer. @inlinable - public // @testable - mutating func _heapSort( - within range: Range, + public mutating func _stableSortImpl( 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..(_unsafeUninitializedCapacity: count / 2) { + buffer, _ in + var runs: [Range] = [] + + var start = startIndex + while start < endIndex { + // Find the next consecutive run, reversing it if necessary. + var (end, descending) = + try _findNextRun(in: self, from: start, by: areInIncreasingOrder) + if descending { + _reverse(within: start..( + _ body: (inout UnsafeMutableBufferPointer) throws -> R + ) rethrows -> R? { + return try body(&self) + } + % else: /// Creates an immutable typed buffer pointer referencing the same memory as the diff --git a/test/Prototypes/IntroSort.swift b/test/Prototypes/IntroSort.swift new file mode 100644 index 0000000000000..64c7d249ef6fc --- /dev/null +++ b/test/Prototypes/IntroSort.swift @@ -0,0 +1,397 @@ +//===--- IntroSort.swift --------------------------------------*- swift -*-===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2014 - 2018 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// +// RUN: %empty-directory(%t) +// RUN: %target-build-swift -g -Onone -DUSE_STDLIBUNITTEST %s -o %t/a.out +// RUN: %target-codesign %t/a.out +// RUN: %target-run %t/a.out +// REQUIRES: executable_test + +// Note: This introsort was the standard library's sort algorithm until Swift 5. + +import Swift +import StdlibUnittest + +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(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) + } + } + } +} + +extension MutableCollection where Self: RandomAccessCollection { + /// Reorders the collection and returns an index `p` such that every element + /// in `range.lowerBound..= 3 i.e. + /// `distance(from: range.lowerBound, to: range.upperBound) >= 3` + @inlinable + internal mutating func _partition( + within range: Range, + by areInIncreasingOrder: (Element, Element) throws -> Bool + ) rethrows -> Index { + var lo = range.lowerBound + var hi = index(before: range.upperBound) + + // 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) + + // FIXME: Stashing the pivot element instead of using the index won't work + // for move-only types. + let pivot = self[mid] + + // Loop invariants: + // * lo < hi + // * self[i] < pivot, for i in range.lowerBound.., + 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, + 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.., + by areInIncreasingOrder: (Element, Element) throws -> Bool + ) rethrows { + var idx = idx + var countToIndex = distance(from: range.lowerBound, to: idx) + var countFromIndex = distance(from: idx, 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(idx, offsetBy: countToIndex + 1) + var largest = idx + 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 != idx { + swapAt(idx, largest) + idx = largest + countToIndex = distance(from: range.lowerBound, to: idx) + countFromIndex = distance(from: idx, to: range.upperBound) + } else { + break + } + } + } + + @inlinable + internal mutating func _heapify( + within range: Range, + 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 + public // @testable + mutating func _heapSort( + within range: Range, + 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.. [Int] { + var candidate: Int = 0 + var keys = [Int: Int]() + func Compare(_ x: Int, y : Int) -> Bool { + if keys[x] == nil && keys[y] == nil { + if (x == candidate) { + keys[x] = keys.count + } else { + keys[y] = keys.count + } + } + if keys[x] == nil { + candidate = x + return true + } + if keys[y] == nil { + candidate = y + return false + } + return keys[x]! > keys[y]! + } + + var ary = [Int](repeating: 0, count: len) + var ret = [Int](repeating: 0, count: len) + for i in 0..(_ a: [T], by areInIncreasingOrder: (T, T) -> Bool) -> Bool { + return !zip(a.dropFirst(), a).contains(where: areInIncreasingOrder) +} + +suite.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 + }) +} + +suite.test("heapSort") { + // Generates the next permutation of `num` as a binary integer, using long + // arithmetics approach. + // + // - Precondition: All values in `num` are either 0 or 1. + func addOne(to num: [Int]) -> [Int] { + // `num` represents a binary integer. To add one, we toggle any bits until + // we've set a clear bit. + var num = num + for i in num.indices { + if num[i] == 1 { + num[i] = 0 + } else { + num[i] = 1 + break + } + } + return num + } + + // Test binary number size. + let numberLength = 11 + var binaryNumber = Array(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.. to <τ_0_0 where τ_0_0 : BidirectionalCollection, τ_0_0 : MutableCollection> + +/* RawRepresentable Changes */ + /* Removed Decls */ Class _stdlib_AtomicInt has been removed +Func MutableCollection._heapSort(within:by:) has been removed +Func MutableCollection._heapify(within:by:) has been removed +Func MutableCollection._introSort(within:by:) has been removed +Func MutableCollection._introSortImpl(within:by:depthLimit:) has been removed +Func MutableCollection._siftDown(_:within:by:) has been removed +Func MutableCollection._sort3(_:_:_:by:) has been removed Func _stdlib_atomicCompareExchangeStrongInt(object:expected:desired:) has been removed Func _stdlib_atomicCompareExchangeStrongInt32(object:expected:desired:) has been removed Func _stdlib_atomicCompareExchangeStrongInt64(object:expected:desired:) has been removed @@ -33,3 +45,15 @@ Func _swift_stdlib_atomicStoreInt32(object:desired:) has been removed Func _swift_stdlib_atomicStoreInt64(object:desired:) has been removed Func _swift_stdlib_atomicStoreUInt32(object:desired:) has been removed Func _swift_stdlib_atomicStoreUInt64(object:desired:) has been removed + +/* Moved Decls */ + +/* Renamed Decls */ +Func MutableCollection._partition(within:by:) has been renamed to Func MutableCollection._partitionImpl(by:) + +/* Type Changes */ +Func MutableCollection._partition(within:by:) has parameter 0 type change from Range<τ_0_0.Index> to (τ_0_0.Element) throws -> Bool + +/* Decl Attribute changes */ + +/* Protocol Requirement Changes */ diff --git a/validation-test/stdlib/Algorithm.swift b/validation-test/stdlib/Algorithm.swift index d784d39974ae5..26e933a7bfd7c 100644 --- a/validation-test/stdlib/Algorithm.swift +++ b/validation-test/stdlib/Algorithm.swift @@ -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(_ 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.., _ originalAry: ArraySl expectSortedCollection([Int](sortedAry), [Int](originalAry)) } +func expectSortedCollection(_ a: C, + by areInIncreasingOrder: (C.Element, C.Element) -> Bool +) { + expectFalse(zip(a.dropFirst(), a).contains(where: areInIncreasingOrder)) +} + class OffsetCollection : MutableCollection, RandomAccessCollection { typealias Indices = Range @@ -104,28 +110,44 @@ class OffsetCollection : MutableCollection, RandomAccessCollection { Algorithm.test("${t}/sorted/${name}") { let count = 1000 - var ary = ${t}((0 ..< count).map { _ in Int.random(in: .min ... .max) }) - var sortedAry1 = [Int]() - var sortedAry2 = ${t}() + let ary = ${t}((0 ..< count).map { _ in Int.random(in: .min ... .max) }) // Similar test for sorting with predicate % if comparePredicate: - sortedAry1 = ary.sorted(by: ${comparePredicate}) + let sortedAry1 = ary.sorted(by: ${comparePredicate}) % else: - sortedAry1 = ary.sorted() + let sortedAry1 = ary.sorted() % end expectSortedCollection(sortedAry1, ary) // Check that sorting works well on intervals let i1 = 400 let i2 = 700 - sortedAry2 = ary - sortedAry2._introSort(within: i1...allocate(capacity: count) + defer { allocated.deallocate() } + allocated.initialize(repeating: 1, count: count) + + var buffer = UnsafeMutableBufferPointer(start: allocated, count: count) + let result = buffer._withUnsafeMutableBufferPointerIfSupported { buffer in + return buffer.reduce(0, +) + } + expectEqual(count, result) +} + +UnsafeMutableBufferPointerTestSuite.test("sort") { + var values = (0..<1000).map({ _ in Int.random(in: 0..<100) }) + let sortedValues = values.sorted() + values.withUnsafeMutableBufferPointer { buffer in + buffer.sort() + } + expectEqual(values, sortedValues) +} + +UnsafeMutableBufferPointerTestSuite.test("partition") { + var values = (0..<1000).map({ _ in Int.random(in: 0..<100) }) + let partitionIndex = values.withUnsafeMutableBufferPointer { buffer in + return buffer.partition(by: { $0 % 2 == 0 }) + } + expectTrue(values[..