Skip to content

Commit 0a1ab85

Browse files
authored
Merge pull request kodecocodes#684 from gringoireDM/master
[New Algorithm] Introsort sorting algorithm
2 parents 5eb8ad7 + f34ccf2 commit 0a1ab85

15 files changed

+494
-0
lines changed

Introsort/HeapSort.swift

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import Foundation
2+
3+
private func shiftDown<T>(_ elements: inout [T], _ index: Int, _ range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) {
4+
let countToIndex = elements.distance(from: range.lowerBound, to: index)
5+
let countFromIndex = elements.distance(from: index, to: range.upperBound)
6+
7+
guard countToIndex + 1 < countFromIndex else { return }
8+
9+
let left = elements.index(index, offsetBy: countToIndex + 1)
10+
var largest = index
11+
if areInIncreasingOrder(elements[largest], elements[left]) {
12+
largest = left
13+
}
14+
15+
if countToIndex + 2 < countFromIndex {
16+
let right = elements.index(after: left)
17+
if areInIncreasingOrder(elements[largest], elements[right]) {
18+
largest = right
19+
}
20+
}
21+
22+
if largest != index {
23+
elements.swapAt(index, largest)
24+
shiftDown(&elements, largest, range, by: areInIncreasingOrder)
25+
}
26+
27+
}
28+
29+
private func heapify<T>(_ list: inout [T], _ range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) {
30+
let root = range.lowerBound
31+
var node = list.index(root, offsetBy: list.distance(from: range.lowerBound, to: range.upperBound)/2)
32+
33+
while node != root {
34+
list.formIndex(before: &node)
35+
shiftDown(&list, node, range, by: areInIncreasingOrder)
36+
}
37+
}
38+
39+
public func heapsort<T>(for array: inout [T], range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) {
40+
var hi = range.upperBound
41+
let lo = range.lowerBound
42+
heapify(&array, range, by: areInIncreasingOrder)
43+
array.formIndex(before: &hi)
44+
45+
while hi != lo {
46+
array.swapAt(lo, hi)
47+
shiftDown(&array, lo, lo..<hi, by: areInIncreasingOrder)
48+
array.formIndex(before: &hi)
49+
}
50+
}

Introsort/InsertionSort.swift

+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import Foundation
2+
3+
public func insertionSort<T>(for array: inout [T], range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) {
4+
guard !range.isEmpty else { return }
5+
6+
let start = range.lowerBound
7+
var sortedEnd = start
8+
9+
array.formIndex(after: &sortedEnd)
10+
while sortedEnd != range.upperBound {
11+
let x = array[sortedEnd]
12+
13+
var i = sortedEnd
14+
repeat {
15+
let predecessor = array[array.index(before: i)]
16+
17+
guard areInIncreasingOrder(x, predecessor) else { break }
18+
array[i] = predecessor
19+
array.formIndex(before: &i)
20+
} while i != start
21+
22+
if i != sortedEnd {
23+
array[i] = x
24+
}
25+
array.formIndex(after: &sortedEnd)
26+
}
27+
28+
}

Introsort/IntroSort.swift

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import Foundation
2+
3+
public func introsort<T>(_ array: inout [T], by areInIncreasingOrder: (T, T) -> Bool) {
4+
//The depth limit is as best practice 2 * log( n )
5+
let depthLimit = 2 * floor(log2(Double(array.count)))
6+
7+
introSortImplementation(for: &array, range: 0..<array.count, depthLimit: Int(depthLimit), by: areInIncreasingOrder)
8+
}
9+
10+
///This method is recursively executed for each partition result of the quicksort part of the algorithm
11+
private func introSortImplementation<T>(for array: inout [T], range: Range<Int>, depthLimit: Int, by areInIncreasingOrder: (T, T) -> Bool) {
12+
if array.distance(from: range.lowerBound, to: range.upperBound) < 20 {
13+
//if the partition count is less than 20 we can sort it using insertion sort. This algorithm in fact performs well on collections
14+
//of this size, plus, at this point is quite probable that the quisksort part of the algorithm produced a partition which is
15+
//nearly sorted. As we knoe insertion sort tends to O( n ) if this is the case.
16+
insertionSort(for: &array, range: range, by: areInIncreasingOrder)
17+
} else if depthLimit == 0 {
18+
//If we reached the depth limit for this recursion branch, it's possible that we are hitting quick sort's worst case.
19+
//Since quicksort degrades to O( n^2 ) in its worst case we stop using quicksort for this recursion branch and we switch to heapsort.
20+
//Our preference remains quicksort, and we hope to be rare to see this condition to be true
21+
heapsort(for: &array, range: range, by: areInIncreasingOrder)
22+
} else {
23+
//By default we use quicksort to sort our collection. The partition index method chose a pivot, and puts all the
24+
//elements less than pivot on the left, and the ones bigger than pivot on the right. At the end of the operation the
25+
//position of the pivot in the array is returned so that we can form the two partitions.
26+
let partIdx = partitionIndex(for: &array, subRange: range, by: areInIncreasingOrder)
27+
28+
//We can recursively call introsort implementation, decreasing the depthLimit for the left partition and the right partition.
29+
introSortImplementation(for: &array, range: range.lowerBound..<partIdx, depthLimit: depthLimit &- 1, by: areInIncreasingOrder)
30+
introSortImplementation(for: &array, range: partIdx..<range.upperBound, depthLimit: depthLimit &- 1, by: areInIncreasingOrder)
31+
}
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
//: Playground - noun: a place where people can play
2+
3+
import Foundation
4+
5+
func introsort<T>(_ array: inout [T], by areInIncreasingOrder: (T, T) -> Bool) {
6+
//The depth limit is as best practice 2 * log( n )
7+
let depthLimit = 2 * floor(log2(Double(array.count)))
8+
9+
introSortImplementation(for: &array, range: 0..<array.count, depthLimit: Int(depthLimit), by: areInIncreasingOrder)
10+
}
11+
12+
///This method is recursively executed for each partition result of the quicksort part of the algorithm
13+
private func introSortImplementation<T>(for array: inout [T], range: Range<Int>, depthLimit: Int, by areInIncreasingOrder: (T, T) -> Bool) {
14+
if array.distance(from: range.lowerBound, to: range.upperBound) < 20 {
15+
//if the partition count is less than 20 we can sort it using insertion sort. This algorithm in fact performs well on collections
16+
//of this size, plus, at this point is quite probable that the quisksort part of the algorithm produced a partition which is
17+
//nearly sorted. As we knoe insertion sort tends to O( n ) if this is the case.
18+
insertionSort(for: &array, range: range, by: areInIncreasingOrder)
19+
} else if depthLimit == 0 {
20+
//If we reached the depth limit for this recursion branch, it's possible that we are hitting quick sort's worst case.
21+
//Since quicksort degrades to O( n^2 ) in its worst case we stop using quicksort for this recursion branch and we switch to heapsort.
22+
//Our preference remains quicksort, and we hope to be rare to see this condition to be true
23+
heapsort(for: &array, range: range, by: areInIncreasingOrder)
24+
} else {
25+
//By default we use quicksort to sort our collection. The partition index method chose a pivot, and puts all the
26+
//elements less than pivot on the left, and the ones bigger than pivot on the right. At the end of the operation the
27+
//position of the pivot in the array is returned so that we can form the two partitions.
28+
let partIdx = partitionIndex(for: &array, subRange: range, by: areInIncreasingOrder)
29+
30+
//We can recursively call introsort implementation, decreasing the depthLimit for the left partition and the right partition.
31+
introSortImplementation(for: &array, range: range.lowerBound..<partIdx, depthLimit: depthLimit &- 1, by: areInIncreasingOrder)
32+
introSortImplementation(for: &array, range: partIdx..<range.upperBound, depthLimit: depthLimit &- 1, by: areInIncreasingOrder)
33+
}
34+
}
35+
36+
var unsorted = randomize(n: 1000)
37+
38+
let swiftSorted = unsorted.sorted()
39+
introsort(&unsorted, by: <)
40+
41+
print("does it work? \(unsorted == swiftSorted)")
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import Foundation
2+
3+
private func shiftDown<T>(_ elements: inout [T], _ index: Int, _ range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) {
4+
let countToIndex = elements.distance(from: range.lowerBound, to: index)
5+
let countFromIndex = elements.distance(from: index, to: range.upperBound)
6+
7+
guard countToIndex + 1 < countFromIndex else { return }
8+
9+
let left = elements.index(index, offsetBy: countToIndex + 1)
10+
var largest = index
11+
if areInIncreasingOrder(elements[largest], elements[left]) {
12+
largest = left
13+
}
14+
15+
if countToIndex + 2 < countFromIndex {
16+
let right = elements.index(after: left)
17+
if areInIncreasingOrder(elements[largest], elements[right]) {
18+
largest = right
19+
}
20+
}
21+
22+
if largest != index {
23+
elements.swapAt(index, largest)
24+
shiftDown(&elements, largest, range, by: areInIncreasingOrder)
25+
}
26+
27+
}
28+
29+
private func heapify<T>(_ list: inout [T], _ range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) {
30+
let root = range.lowerBound
31+
var node = list.index(root, offsetBy: list.distance(from: range.lowerBound, to: range.upperBound)/2)
32+
33+
while node != root {
34+
list.formIndex(before: &node)
35+
shiftDown(&list, node, range, by: areInIncreasingOrder)
36+
}
37+
}
38+
39+
public func heapsort<T>(for array: inout [T], range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) {
40+
var hi = range.upperBound
41+
let lo = range.lowerBound
42+
heapify(&array, range, by: areInIncreasingOrder)
43+
array.formIndex(before: &hi)
44+
45+
while hi != lo {
46+
array.swapAt(lo, hi)
47+
shiftDown(&array, lo, lo..<hi, by: areInIncreasingOrder)
48+
array.formIndex(before: &hi)
49+
}
50+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import Foundation
2+
3+
public func insertionSort<T>(for array: inout [T], range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) {
4+
guard !range.isEmpty else { return }
5+
6+
let start = range.lowerBound
7+
var sortedEnd = start
8+
9+
array.formIndex(after: &sortedEnd)
10+
while sortedEnd != range.upperBound {
11+
let x = array[sortedEnd]
12+
13+
var i = sortedEnd
14+
repeat {
15+
let predecessor = array[array.index(before: i)]
16+
17+
guard areInIncreasingOrder(x, predecessor) else { break }
18+
array[i] = predecessor
19+
array.formIndex(before: &i)
20+
} while i != start
21+
22+
if i != sortedEnd {
23+
array[i] = x
24+
}
25+
array.formIndex(after: &sortedEnd)
26+
}
27+
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import Foundation
2+
3+
public func partitionIndex<T>(for elements: inout [T], subRange range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) -> Int {
4+
var lo = range.lowerBound
5+
var hi = elements.index(before: range.upperBound)
6+
7+
// Sort the first, middle, and last elements, then use the middle value
8+
// as the pivot for the partition.
9+
let half = elements.distance(from: lo, to: hi) / 2
10+
let mid = elements.index(lo, offsetBy: half)
11+
12+
sort3(in: &elements, a: lo, b: mid, c: hi, by: areInIncreasingOrder)
13+
let pivot = elements[mid]
14+
15+
while true {
16+
elements.formIndex(after: &lo)
17+
guard findLo(in: elements, pivot: pivot, from: &lo, to: hi, by: areInIncreasingOrder) else { break }
18+
elements.formIndex(before: &hi)
19+
guard findHi(in: elements, pivot: pivot, from: lo, to: &hi, by: areInIncreasingOrder) else { break }
20+
elements.swapAt(lo, hi)
21+
}
22+
23+
24+
return lo
25+
}
26+
27+
private func findLo<T>(in array: [T], pivot: T, from lo: inout Int, to hi: Int, by areInIncreasingOrder: (T, T)->Bool) -> Bool {
28+
while lo != hi {
29+
if !areInIncreasingOrder(array[lo], pivot) {
30+
return true
31+
}
32+
array.formIndex(after: &lo)
33+
}
34+
return false
35+
}
36+
37+
private func findHi<T>(in array: [T], pivot: T, from lo: Int, to hi: inout Int, by areInIncreasingOrder: (T, T)->Bool) -> Bool {
38+
while hi != lo {
39+
if areInIncreasingOrder(array[hi], pivot) { return true }
40+
array.formIndex(before: &hi)
41+
}
42+
return false
43+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import Foundation
2+
3+
public func randomize(n: Int) -> [Int] {
4+
var unsorted = [Int]()
5+
for _ in 0..<n {
6+
unsorted.append(Int(arc4random()))
7+
}
8+
return unsorted
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import Foundation
2+
3+
public func sort3<T>(in array: inout [T], a: Int, b: Int, c: Int, by areInIncreasingOrder: (T, T) -> Bool) {
4+
switch (areInIncreasingOrder(array[b], array[a]),
5+
areInIncreasingOrder(array[c], array[b])) {
6+
case (false, false): break
7+
case (true, true): array.swapAt(a, c)
8+
case (true, false):
9+
array.swapAt(a, b)
10+
11+
if areInIncreasingOrder(array[c], array[b]) {
12+
array.swapAt(b, c)
13+
}
14+
case (false, true):
15+
array.swapAt(b, c)
16+
17+
if areInIncreasingOrder(array[b], array[a]) {
18+
array.swapAt(a, b)
19+
}
20+
}
21+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
2+
<playground version='5.0' target-platform='ios'>
3+
<timeline fileName='timeline.xctimeline'/>
4+
</playground>

Introsort/Partition.swift

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import Foundation
2+
3+
public func partitionIndex<T>(for elements: inout [T], subRange range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) -> Int {
4+
var lo = range.lowerBound
5+
var hi = elements.index(before: range.upperBound)
6+
7+
// Sort the first, middle, and last elements, then use the middle value
8+
// as the pivot for the partition.
9+
let half = elements.distance(from: lo, to: hi) / 2
10+
let mid = elements.index(lo, offsetBy: half)
11+
12+
sort3(in: &elements, a: lo, b: mid, c: hi, by: areInIncreasingOrder)
13+
let pivot = elements[mid]
14+
15+
while true {
16+
elements.formIndex(after: &lo)
17+
guard findLo(in: elements, pivot: pivot, from: &lo, to: hi, by: areInIncreasingOrder) else { break }
18+
elements.formIndex(before: &hi)
19+
guard findHi(in: elements, pivot: pivot, from: lo, to: &hi, by: areInIncreasingOrder) else { break }
20+
elements.swapAt(lo, hi)
21+
}
22+
23+
24+
return lo
25+
}
26+
27+
private func findLo<T>(in array: [T], pivot: T, from lo: inout Int, to hi: Int, by areInIncreasingOrder: (T, T)->Bool) -> Bool {
28+
while lo != hi {
29+
if !areInIncreasingOrder(array[lo], pivot) {
30+
return true
31+
}
32+
array.formIndex(after: &lo)
33+
}
34+
return false
35+
}
36+
37+
private func findHi<T>(in array: [T], pivot: T, from lo: Int, to hi: inout Int, by areInIncreasingOrder: (T, T)->Bool) -> Bool {
38+
while hi != lo {
39+
if areInIncreasingOrder(array[hi], pivot) { return true }
40+
array.formIndex(before: &hi)
41+
}
42+
return false
43+
}

0 commit comments

Comments
 (0)