Skip to content

[New Algorithm] Introsort sorting algorithm #684

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
Feb 7, 2018
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
50 changes: 50 additions & 0 deletions Introsort/HeapSort.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import Foundation

private func shiftDown<T>(_ elements: inout [T], _ index: Int, _ range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) {
let countToIndex = elements.distance(from: range.lowerBound, to: index)
let countFromIndex = elements.distance(from: index, to: range.upperBound)

guard countToIndex + 1 < countFromIndex else { return }

let left = elements.index(index, offsetBy: countToIndex + 1)
var largest = index
if areInIncreasingOrder(elements[largest], elements[left]) {
largest = left
}

if countToIndex + 2 < countFromIndex {
let right = elements.index(after: left)
if areInIncreasingOrder(elements[largest], elements[right]) {
largest = right
}
}

if largest != index {
elements.swapAt(index, largest)
shiftDown(&elements, largest, range, by: areInIncreasingOrder)
}

}

private func heapify<T>(_ list: inout [T], _ range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) {
let root = range.lowerBound
var node = list.index(root, offsetBy: list.distance(from: range.lowerBound, to: range.upperBound)/2)

while node != root {
list.formIndex(before: &node)
shiftDown(&list, node, range, by: areInIncreasingOrder)
}
}

public func heapsort<T>(for array: inout [T], range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) {
var hi = range.upperBound
let lo = range.lowerBound
heapify(&array, range, by: areInIncreasingOrder)
array.formIndex(before: &hi)

while hi != lo {
array.swapAt(lo, hi)
shiftDown(&array, lo, lo..<hi, by: areInIncreasingOrder)
array.formIndex(before: &hi)
}
}
28 changes: 28 additions & 0 deletions Introsort/InsertionSort.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation

public func insertionSort<T>(for array: inout [T], range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) {
guard !range.isEmpty else { return }

let start = range.lowerBound
var sortedEnd = start

array.formIndex(after: &sortedEnd)
while sortedEnd != range.upperBound {
let x = array[sortedEnd]

var i = sortedEnd
repeat {
let predecessor = array[array.index(before: i)]

guard areInIncreasingOrder(x, predecessor) else { break }
array[i] = predecessor
array.formIndex(before: &i)
} while i != start

if i != sortedEnd {
array[i] = x
}
array.formIndex(after: &sortedEnd)
}

}
32 changes: 32 additions & 0 deletions Introsort/IntroSort.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Foundation

public func introsort<T>(_ array: inout [T], by areInIncreasingOrder: (T, T) -> Bool) {
//The depth limit is as best practice 2 * log( n )
let depthLimit = 2 * floor(log2(Double(array.count)))

introSortImplementation(for: &array, range: 0..<array.count, depthLimit: Int(depthLimit), by: areInIncreasingOrder)
}

///This method is recursively executed for each partition result of the quicksort part of the algorithm
private func introSortImplementation<T>(for array: inout [T], range: Range<Int>, depthLimit: Int, by areInIncreasingOrder: (T, T) -> Bool) {
if array.distance(from: range.lowerBound, to: range.upperBound) < 20 {
//if the partition count is less than 20 we can sort it using insertion sort. This algorithm in fact performs well on collections
//of this size, plus, at this point is quite probable that the quisksort part of the algorithm produced a partition which is
//nearly sorted. As we knoe insertion sort tends to O( n ) if this is the case.
insertionSort(for: &array, range: range, by: areInIncreasingOrder)
} else if depthLimit == 0 {
//If we reached the depth limit for this recursion branch, it's possible that we are hitting quick sort's worst case.
//Since quicksort degrades to O( n^2 ) in its worst case we stop using quicksort for this recursion branch and we switch to heapsort.
//Our preference remains quicksort, and we hope to be rare to see this condition to be true
heapsort(for: &array, range: range, by: areInIncreasingOrder)
} else {
//By default we use quicksort to sort our collection. The partition index method chose a pivot, and puts all the
//elements less than pivot on the left, and the ones bigger than pivot on the right. At the end of the operation the
//position of the pivot in the array is returned so that we can form the two partitions.
let partIdx = partitionIndex(for: &array, subRange: range, by: areInIncreasingOrder)

//We can recursively call introsort implementation, decreasing the depthLimit for the left partition and the right partition.
introSortImplementation(for: &array, range: range.lowerBound..<partIdx, depthLimit: depthLimit &- 1, by: areInIncreasingOrder)
introSortImplementation(for: &array, range: partIdx..<range.upperBound, depthLimit: depthLimit &- 1, by: areInIncreasingOrder)
}
}
41 changes: 41 additions & 0 deletions Introsort/Introsort.playground/Contents.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//: Playground - noun: a place where people can play

import Foundation

func introsort<T>(_ array: inout [T], by areInIncreasingOrder: (T, T) -> Bool) {
//The depth limit is as best practice 2 * log( n )
let depthLimit = 2 * floor(log2(Double(array.count)))

introSortImplementation(for: &array, range: 0..<array.count, depthLimit: Int(depthLimit), by: areInIncreasingOrder)
}

///This method is recursively executed for each partition result of the quicksort part of the algorithm
private func introSortImplementation<T>(for array: inout [T], range: Range<Int>, depthLimit: Int, by areInIncreasingOrder: (T, T) -> Bool) {
if array.distance(from: range.lowerBound, to: range.upperBound) < 20 {
//if the partition count is less than 20 we can sort it using insertion sort. This algorithm in fact performs well on collections
//of this size, plus, at this point is quite probable that the quisksort part of the algorithm produced a partition which is
//nearly sorted. As we knoe insertion sort tends to O( n ) if this is the case.
insertionSort(for: &array, range: range, by: areInIncreasingOrder)
} else if depthLimit == 0 {
//If we reached the depth limit for this recursion branch, it's possible that we are hitting quick sort's worst case.
//Since quicksort degrades to O( n^2 ) in its worst case we stop using quicksort for this recursion branch and we switch to heapsort.
//Our preference remains quicksort, and we hope to be rare to see this condition to be true
heapsort(for: &array, range: range, by: areInIncreasingOrder)
} else {
//By default we use quicksort to sort our collection. The partition index method chose a pivot, and puts all the
//elements less than pivot on the left, and the ones bigger than pivot on the right. At the end of the operation the
//position of the pivot in the array is returned so that we can form the two partitions.
let partIdx = partitionIndex(for: &array, subRange: range, by: areInIncreasingOrder)

//We can recursively call introsort implementation, decreasing the depthLimit for the left partition and the right partition.
introSortImplementation(for: &array, range: range.lowerBound..<partIdx, depthLimit: depthLimit &- 1, by: areInIncreasingOrder)
introSortImplementation(for: &array, range: partIdx..<range.upperBound, depthLimit: depthLimit &- 1, by: areInIncreasingOrder)
}
}

var unsorted = randomize(n: 1000)

let swiftSorted = unsorted.sorted()
introsort(&unsorted, by: <)

print("does it work? \(unsorted == swiftSorted)")
50 changes: 50 additions & 0 deletions Introsort/Introsort.playground/Sources/HeapSort.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import Foundation

private func shiftDown<T>(_ elements: inout [T], _ index: Int, _ range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) {
let countToIndex = elements.distance(from: range.lowerBound, to: index)
let countFromIndex = elements.distance(from: index, to: range.upperBound)

guard countToIndex + 1 < countFromIndex else { return }

let left = elements.index(index, offsetBy: countToIndex + 1)
var largest = index
if areInIncreasingOrder(elements[largest], elements[left]) {
largest = left
}

if countToIndex + 2 < countFromIndex {
let right = elements.index(after: left)
if areInIncreasingOrder(elements[largest], elements[right]) {
largest = right
}
}

if largest != index {
elements.swapAt(index, largest)
shiftDown(&elements, largest, range, by: areInIncreasingOrder)
}

}

private func heapify<T>(_ list: inout [T], _ range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) {
let root = range.lowerBound
var node = list.index(root, offsetBy: list.distance(from: range.lowerBound, to: range.upperBound)/2)

while node != root {
list.formIndex(before: &node)
shiftDown(&list, node, range, by: areInIncreasingOrder)
}
}

public func heapsort<T>(for array: inout [T], range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) {
var hi = range.upperBound
let lo = range.lowerBound
heapify(&array, range, by: areInIncreasingOrder)
array.formIndex(before: &hi)

while hi != lo {
array.swapAt(lo, hi)
shiftDown(&array, lo, lo..<hi, by: areInIncreasingOrder)
array.formIndex(before: &hi)
}
}
28 changes: 28 additions & 0 deletions Introsort/Introsort.playground/Sources/InsertionSort.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Foundation

public func insertionSort<T>(for array: inout [T], range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) {
guard !range.isEmpty else { return }

let start = range.lowerBound
var sortedEnd = start

array.formIndex(after: &sortedEnd)
while sortedEnd != range.upperBound {
let x = array[sortedEnd]

var i = sortedEnd
repeat {
let predecessor = array[array.index(before: i)]

guard areInIncreasingOrder(x, predecessor) else { break }
array[i] = predecessor
array.formIndex(before: &i)
} while i != start

if i != sortedEnd {
array[i] = x
}
array.formIndex(after: &sortedEnd)
}

}
43 changes: 43 additions & 0 deletions Introsort/Introsort.playground/Sources/Partition.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Foundation

public func partitionIndex<T>(for elements: inout [T], subRange range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) -> Int {
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 = elements.distance(from: lo, to: hi) / 2
let mid = elements.index(lo, offsetBy: half)

sort3(in: &elements, a: lo, b: mid, c: hi, by: areInIncreasingOrder)
let pivot = elements[mid]

while true {
elements.formIndex(after: &lo)
guard findLo(in: elements, pivot: pivot, from: &lo, to: hi, by: areInIncreasingOrder) else { break }
elements.formIndex(before: &hi)
guard findHi(in: elements, pivot: pivot, from: lo, to: &hi, by: areInIncreasingOrder) else { break }
elements.swapAt(lo, hi)
}


return lo
}

private func findLo<T>(in array: [T], pivot: T, from lo: inout Int, to hi: Int, by areInIncreasingOrder: (T, T)->Bool) -> Bool {
while lo != hi {
if !areInIncreasingOrder(array[lo], pivot) {
return true
}
array.formIndex(after: &lo)
}
return false
}

private func findHi<T>(in array: [T], pivot: T, from lo: Int, to hi: inout Int, by areInIncreasingOrder: (T, T)->Bool) -> Bool {
while hi != lo {
if areInIncreasingOrder(array[hi], pivot) { return true }
array.formIndex(before: &hi)
}
return false
}
9 changes: 9 additions & 0 deletions Introsort/Introsort.playground/Sources/Randomize.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import Foundation

public func randomize(n: Int) -> [Int] {
var unsorted = [Int]()
for _ in 0..<n {
unsorted.append(Int(arc4random()))
}
return unsorted
}
21 changes: 21 additions & 0 deletions Introsort/Introsort.playground/Sources/Sort3.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import Foundation

public func sort3<T>(in array: inout [T], a: Int, b: Int, c: Int, by areInIncreasingOrder: (T, T) -> Bool) {
switch (areInIncreasingOrder(array[b], array[a]),
areInIncreasingOrder(array[c], array[b])) {
case (false, false): break
case (true, true): array.swapAt(a, c)
case (true, false):
array.swapAt(a, b)

if areInIncreasingOrder(array[c], array[b]) {
array.swapAt(b, c)
}
case (false, true):
array.swapAt(b, c)

if areInIncreasingOrder(array[b], array[a]) {
array.swapAt(a, b)
}
}
}
4 changes: 4 additions & 0 deletions Introsort/Introsort.playground/contents.xcplayground
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<playground version='5.0' target-platform='ios'>
<timeline fileName='timeline.xctimeline'/>
</playground>
43 changes: 43 additions & 0 deletions Introsort/Partition.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import Foundation

public func partitionIndex<T>(for elements: inout [T], subRange range: Range<Int>, by areInIncreasingOrder: (T, T) -> Bool) -> Int {
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 = elements.distance(from: lo, to: hi) / 2
let mid = elements.index(lo, offsetBy: half)

sort3(in: &elements, a: lo, b: mid, c: hi, by: areInIncreasingOrder)
let pivot = elements[mid]

while true {
elements.formIndex(after: &lo)
guard findLo(in: elements, pivot: pivot, from: &lo, to: hi, by: areInIncreasingOrder) else { break }
elements.formIndex(before: &hi)
guard findHi(in: elements, pivot: pivot, from: lo, to: &hi, by: areInIncreasingOrder) else { break }
elements.swapAt(lo, hi)
}


return lo
}

private func findLo<T>(in array: [T], pivot: T, from lo: inout Int, to hi: Int, by areInIncreasingOrder: (T, T)->Bool) -> Bool {
while lo != hi {
if !areInIncreasingOrder(array[lo], pivot) {
return true
}
array.formIndex(after: &lo)
}
return false
}

private func findHi<T>(in array: [T], pivot: T, from lo: Int, to hi: inout Int, by areInIncreasingOrder: (T, T)->Bool) -> Bool {
while hi != lo {
if areInIncreasingOrder(array[hi], pivot) { return true }
array.formIndex(before: &hi)
}
return false
}
Loading