I'm trying this in a playground:
extension MutableCollection {
public mutating func stableSort(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows {
let end = endIndex
var sortEnd = try sortedPrefix(by: areInIncreasingOrder).endIndex
while sortEnd < end {
let nextEnd = try self[sortEnd...].sortedPrefix(by: areInIncreasingOrder).endIndex
try self[..<nextEnd].mergeSortedPartitions(around: sortEnd, by: areInIncreasingOrder)
assert(try! self[..<nextEnd].sortedPrefix(by: areInIncreasingOrder).endIndex == nextEnd)
sortEnd = nextEnd
}
}
}
extension MutableCollection where Element: Comparable {
@inlinable
public mutating func stableSort() {
stableSort(by: <)
}
}
var c = Array<Int>()
c.reserveCapacity(100)
for _ in 0..<100 {
c.append(Int.random(in: -50..<50))
}
c
c.stableSort()
The array looks OK for the first quarter of it, then I see unsorted elements, which triggers the assert I added. I don't know where I messed up. I think the sort is correct. Of course, I also think the building up methods are correct too.
extension Collection {
public func sortedPrefix(by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows -> SubSequence {
let end = endIndex
var first = startIndex
guard var second = index(first, offsetBy: +1, limitedBy: end) else {
return self[first...]
}
while second < end {
guard try !areInIncreasingOrder(self[second], self[first]) else {
break
}
first = second
formIndex(after: &second)
}
return self[..<second]
}
}
extension MutableCollection {
public mutating func swapPartitions(around pivot: Index) -> Index {
let start = startIndex, end = endIndex
guard start < pivot else { return end }
guard pivot < end else { return start }
var firstMarker = start, secondMarker = pivot
while firstMarker < pivot, secondMarker < end {
swapAt(firstMarker, secondMarker)
formIndex(after: &firstMarker)
formIndex(after: &secondMarker)
}
assert(firstMarker == pivot || secondMarker == end)
if firstMarker < pivot {
_ = self[firstMarker...].swapPartitions(around: pivot)
return firstMarker
} else if secondMarker < end {
return self[pivot...].swapPartitions(around: secondMarker)
}
return pivot
// To-Do: Make an iterative version.
}
public mutating func mergeSortedPartitions(around pivot: Index, by areInIncreasingOrder: (Element, Element) throws -> Bool) rethrows {
// Don't need to merge empty partitions.
let end = endIndex
guard pivot < end else { return }
// Segregate any elements of the first partition that would be within
// the second.
let midValue = self[pivot]
guard let greaterInFirst = try self[..<pivot].firstIndex(where: {
try areInIncreasingOrder(midValue, $0)
}) else {
// First partition already completely less than the second.
// (Or the first partition is empty.)
return
}
// Segregate the elements of the second partition that would be outside
// any moved parts of the first.
let insertionValue = self[greaterInFirst]
let notLessInSecond = try self[pivot...].firstIndex(where: {
try !areInIncreasingOrder($0, insertionValue)
}) ?? end
self[greaterInFirst..<notLessInSecond].swapPartitions(around: pivot)
}
}
(I'm building these to showcase something else to the forums.)