Skip to content

[stdlib] De-gyb sorting #9135

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 1, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion stdlib/public/core/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ set(SWIFTLIB_ESSENTIAL
ShadowProtocols.swift
Shims.swift
Slice.swift.gyb
Sort.swift.gyb
Sort.swift
StaticString.swift
Stride.swift.gyb
StringCharacterView.swift # ORDER DEPENDENCY: Must precede String.swift
Expand Down
2 changes: 1 addition & 1 deletion stdlib/public/core/CollectionAlgorithms.swift.gyb
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ extension MutableCollection
return ()
}
if didSortUnsafeBuffer == nil {
_introSort(&self, subRange: startIndex..<endIndex)
_introSort(&self, subRange: startIndex..<endIndex, by: <)
}
}
}
Expand Down
194 changes: 62 additions & 132 deletions stdlib/public/core/Sort.swift.gyb → stdlib/public/core/Sort.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,40 +10,15 @@
//
//===----------------------------------------------------------------------===//

%{
def cmp(a, b, p):
if p:
return "(try areInIncreasingOrder(" + a + ", " + b + "))"
else:
return "(" + a + " < " + b + ")"

}%

// Generate two versions of sorting functions: one with an explicitly passed
// predicate 'areInIncreasingOrder' and the other for Comparable types that don't
// need such a predicate.
% preds = [True, False]
% for p in preds:
%{
if p:
rethrows_ = "rethrows"
try_ = "try"
else:
rethrows_ = ""
try_ = ""
}%

@_inlineable
@_versioned
func _insertionSort<C>(
_ elements: inout C,
subRange range: Range<C.Index>
${", by areInIncreasingOrder: (C.Iterator.Element, C.Iterator.Element) throws -> Bool" if p else ""}
) ${rethrows_}
where
C : MutableCollection & BidirectionalCollection
${"" if p else ", C.Iterator.Element : Comparable"} {

subRange range: Range<C.Index>,
by areInIncreasingOrder: (C.Iterator.Element, C.Iterator.Element) throws -> Bool
) rethrows where
C : MutableCollection & BidirectionalCollection
{
if !range.isEmpty {
let start = range.lowerBound

Expand All @@ -62,25 +37,20 @@ func _insertionSort<C>(
// moving elements forward to make room.
var i = sortedEnd
repeat {
let predecessor: C.Iterator.Element = elements[elements.index(before: i)]
let predecessor: C.Iterator.Element =
elements[elements.index(before: i)]

% if p:
// If clouser throws the error, We catch the error put the element at right
// place and rethrow the error.
// If closure throws the error, We catch the error put the element at
// right place and rethrow the error.
do {
// if x doesn't belong before y, we've found its position
if !${cmp("x", "predecessor", p)} {
// if x doesn't belong before y, we've found its position
if try !areInIncreasingOrder(x, predecessor) {
break
}
} catch {
elements[i] = x
throw error
}
% else:
if !${cmp("x", "predecessor", p)} {
break
}
% end

// Move y forward
elements[i] = predecessor
Expand Down Expand Up @@ -108,12 +78,11 @@ func _insertionSort<C>(
public // @testable
func _sort3<C>(
_ elements: inout C,
_ a: C.Index, _ b: C.Index, _ c: C.Index
${", by areInIncreasingOrder: (C.Iterator.Element, C.Iterator.Element) throws -> Bool" if p else ""}
) ${rethrows_}
_ a: C.Index, _ b: C.Index, _ c: C.Index,
by areInIncreasingOrder: (C.Iterator.Element, C.Iterator.Element) throws -> Bool
) rethrows
where
C : MutableCollection & RandomAccessCollection
${"" if p else ", C.Iterator.Element : Comparable"}
{
// There are thirteen possible permutations for the original ordering of
// the elements at indices `a`, `b`, and `c`. The comments in the code below
Expand All @@ -135,8 +104,8 @@ func _sort3<C>(
// 122, 212, or 221.
// - If all three elements are equivalent, they are already in order: 111.

switch (${cmp("elements[b]", "elements[a]", p)},
${cmp("elements[c]", "elements[b]", p)}) {
switch (try areInIncreasingOrder(elements[b], elements[a]),
try areInIncreasingOrder(elements[c], elements[b])) {
case (false, false):
// 0 swaps: 123, 112, 122, 111
break
Expand All @@ -151,7 +120,7 @@ func _sort3<C>(
// swap(a, b): 213->123, 212->122, 312->132, 211->121
elements.swapAt(a, b)

if ${cmp("elements[c]", "elements[b]", p)} {
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)
Expand All @@ -162,7 +131,7 @@ func _sort3<C>(
// swap(b, c): 132->123, 121->112, 231->213, 221->212
elements.swapAt(b, c)

if ${cmp("elements[b]", "elements[a]", p)} {
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)
Expand All @@ -180,12 +149,11 @@ func _sort3<C>(
@_versioned
func _partition<C>(
_ elements: inout C,
subRange range: Range<C.Index>
${", by areInIncreasingOrder: (C.Iterator.Element, C.Iterator.Element) throws -> Bool" if p else ""}
) ${rethrows_} -> C.Index
subRange range: Range<C.Index>,
by areInIncreasingOrder: (C.Iterator.Element, C.Iterator.Element) throws -> Bool
) rethrows -> C.Index
where
C : MutableCollection & RandomAccessCollection
${"" if p else ", C.Iterator.Element : Comparable"}
{
var lo = range.lowerBound
var hi = elements.index(before: range.upperBound)
Expand All @@ -194,8 +162,7 @@ func _partition<C>(
// 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" if p else ""})
try _sort3(&elements, lo, mid, hi, by: areInIncreasingOrder)
let pivot = elements[mid]

// Loop invariants:
Expand All @@ -206,7 +173,7 @@ func _partition<C>(
FindLo: do {
elements.formIndex(after: &lo)
while lo != hi {
if !${cmp("elements[lo]", "pivot", p)} { break FindLo }
if try !areInIncreasingOrder(elements[lo], pivot) { break FindLo }
elements.formIndex(after: &lo)
}
break Loop
Expand All @@ -215,7 +182,7 @@ func _partition<C>(
FindHi: do {
elements.formIndex(before: &hi)
while hi != lo {
if ${cmp("elements[hi]", "pivot", p)} { break FindHi }
if try areInIncreasingOrder(elements[hi], pivot) { break FindHi }
elements.formIndex(before: &hi)
}
break Loop
Expand All @@ -231,87 +198,66 @@ func _partition<C>(
public // @testable
func _introSort<C>(
_ elements: inout C,
subRange range: Range<C.Index>
${", by areInIncreasingOrder: (C.Iterator.Element, C.Iterator.Element) throws -> Bool" if p else ""}
) ${rethrows_}
subRange range: Range<C.Index>,
by areInIncreasingOrder: (C.Iterator.Element, C.Iterator.Element) throws -> Bool
) rethrows
where
C : MutableCollection & RandomAccessCollection
${"" if p else ", C.Iterator.Element : Comparable"} {

let count =
elements.distance(from: range.lowerBound, to: range.upperBound)
{
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 * _floorLog2(Int64(count))
${try_} _introSortImpl(
&elements,
subRange: range,
${"by: areInIncreasingOrder," if p else ""}
try _introSortImpl(&elements, subRange: range, by: areInIncreasingOrder,
depthLimit: depthLimit)
}

@_inlineable
@_versioned
func _introSortImpl<C>(
_ elements: inout C,
subRange range: Range<C.Index>
${", by areInIncreasingOrder: (C.Iterator.Element, C.Iterator.Element) throws -> Bool" if p else ""},
subRange range: Range<C.Index>,
by areInIncreasingOrder: (C.Iterator.Element, C.Iterator.Element) throws -> Bool,
depthLimit: Int
) ${rethrows_}
) rethrows
where
C : MutableCollection & RandomAccessCollection
${"" if p else ", C.Iterator.Element : Comparable"} {

{
// 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" if p else ""})
try _insertionSort(&elements, subRange: range, by: areInIncreasingOrder)
return
}
if depthLimit == 0 {
${try_} _heapSort(
&elements,
subRange: range
${", by: areInIncreasingOrder" if p else ""})
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" if p else ""})
${try_} _introSortImpl(
&elements,
subRange: range.lowerBound..<partIdx,
${"by: areInIncreasingOrder, " if p else ""}
depthLimit: depthLimit &- 1)
${try_} _introSortImpl(
&elements,
subRange: partIdx..<range.upperBound,
${"by: areInIncreasingOrder, " if p else ""}
depthLimit: depthLimit &- 1)
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)
}

@_inlineable
@_versioned
func _siftDown<C>(
_ elements: inout C,
index: C.Index,
subRange range: Range<C.Index>
${", by areInIncreasingOrder: (C.Iterator.Element, C.Iterator.Element) throws -> Bool" if p else ""}
) ${rethrows_}
subRange range: Range<C.Index>,
by areInIncreasingOrder: (C.Iterator.Element, C.Iterator.Element) throws -> Bool
) rethrows
where
C : MutableCollection & RandomAccessCollection
${"" if p else ", C.Iterator.Element : Comparable"} {

{
let countToIndex = elements.distance(from: range.lowerBound, to: index)
let countFromIndex = elements.distance(from: index, to: range.upperBound)
// Check if left child is within bounds. If not, return, because there are
Expand All @@ -321,38 +267,35 @@ func _siftDown<C>(
}
let left = elements.index(index, offsetBy: countToIndex + 1)
var largest = index
if ${cmp("elements[largest]", "elements[left]", p)} {
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 ${cmp("elements[largest]", "elements[right]", p)} {
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 != index {
elements.swapAt(index, largest)
${try_} _siftDown(
&elements,
index: largest,
subRange: range
${", by: areInIncreasingOrder" if p else ""})
try _siftDown(&elements, index: largest, subRange: range,
by: areInIncreasingOrder)
}
}

@_inlineable
@_versioned
func _heapify<C>(
_ elements: inout C,
subRange range: Range<C.Index>
${", by areInIncreasingOrder: (C.Iterator.Element, C.Iterator.Element) throws -> Bool" if p else ""}
) ${rethrows_}
subRange range: Range<C.Index>,
by areInIncreasingOrder: (C.Iterator.Element, C.Iterator.Element) throws -> Bool
) rethrows
where
C : MutableCollection & RandomAccessCollection
${"" if p else ", C.Iterator.Element : Comparable"} {
{
// 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)
Expand All @@ -367,45 +310,32 @@ func _heapify<C>(

while node != root {
elements.formIndex(before: &node)
${try_} _siftDown(
&elements,
index: node,
subRange: range
${", by: areInIncreasingOrder" if p else ""})
try _siftDown(&elements, index: node, subRange: range,
by: areInIncreasingOrder)
}
}

@_inlineable
@_versioned
func _heapSort<C>(
_ elements: inout C,
subRange range: Range<C.Index>
${", by areInIncreasingOrder: (C.Iterator.Element, C.Iterator.Element) throws -> Bool" if p else ""}
) ${rethrows_}
subRange range: Range<C.Index>,
by areInIncreasingOrder: (C.Iterator.Element, C.Iterator.Element) throws -> Bool
) rethrows
where
C : MutableCollection & RandomAccessCollection
${"" if p else ", C.Iterator.Element : Comparable"} {
{
var hi = range.upperBound
let lo = range.lowerBound
${try_} _heapify(
&elements,
subRange: range
${", by: areInIncreasingOrder" if p else ""})
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" if p else ""})
try _siftDown(&elements, index: lo, subRange: lo..<hi, by: areInIncreasingOrder)
elements.formIndex(before: &hi)
}
}

% end
// for p in preds

/// Exchange the values of `a` and `b`.
///
/// - Precondition: `a` and `b` do not alias each other.
Expand Down
Loading