Skip to content

Commit 8b51b40

Browse files
committed
Implement insertion sort
1 parent bdbfb05 commit 8b51b40

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+4173
-213
lines changed

.jazzy.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@ author: "Kyle Hughes"
22
author_url: "https://kylehugh.es"
33
github_url: https://github.com/kylehughes/sort-state-university
44
module: "SortStateUniversity"
5-
module_version: "1.0.0"
5+
module_version: "1.1.0"

README.md

Lines changed: 28 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,14 @@ called to produce the next step, and so on, until the output is produced.
1717

1818
Sort State University brings this dream to life.
1919

20+
### Provided Algorithms
21+
22+
- Insertion Sort
23+
- Merge Sort
24+
2025
### Use Cases
2126

27+
- Asynchronous sorting
2228
- Sorting visualizations
2329
- Et cetera
2430

@@ -27,19 +33,20 @@ implementations.
2733

2834
### The Name
2935

30-
I knew that "sort" and "state" had to be in the name. It seemed natural and funny to append "university."
36+
I knew that "sort" and "state" had to be in the name. It seemed natural and funny to append "university." There is nothing inherently
37+
educational about this framework.
3138

3239
## Usage
3340

3441
### Create an Algorithm
3542

36-
The input to an algorithm does not need to conform to `Comparable` because the answers to the comparisons are supplied
37-
by the caller.
38-
3943
```swift
4044
var algorithm = MergeSort(input: elements)
4145
```
4246

47+
The input to an algorithm does not need to conform to `Comparable` because the answers to the comparisons are supplied
48+
by the caller.
49+
4350
### Advance the Algorithm
4451

4552
A mutable algorithm can be called like a function. The return value is the next step in the algorithm: either a
@@ -56,11 +63,11 @@ case let .finished(output):
5663

5764
### Answer the Comparison
5865

59-
A comparison is a decision about the inherent order of two elements. The answer to a comparison will produce the next
60-
state of the algorithm. The caller is responsible for consistently applying the inherent order to the comparisons.
66+
A comparison is a decision about the inherent order of two elements. The caller is responsible for consistently applying the inherent
67+
order to the comparisons. For example, the "inherent order" could be a user's personal preference, so the answer to the comparions
68+
would be whichever element the user prefers.
6169

62-
For example, the "inherent order" could be a user's personal preference, so the answer to the comparions would
63-
be whichever element the user prefers.
70+
A comparison can be answered directly to produce the next state of the sorting algorithm.
6471

6572
```swift
6673
algorithm = comparison(.left)
@@ -72,6 +79,19 @@ or
7279
algorithm = comparison(.right)
7380
```
7481

82+
The answer to a comparison can also be provided to, and mutate, the algorithm directly. Both approaches produce the same result but
83+
their calling patterns suit different use cases.
84+
85+
```swift
86+
algorithm.answer(.left)
87+
```
88+
89+
or
90+
91+
```swift
92+
algorithm.answer(.right)
93+
```
94+
7595
### Handle the Sorted Output
7696

7797
The output is a sorted array of elements. Handling this value is an exercise left to the reader.
Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
//
2+
// InsertionSort.swift
3+
// SortStateUniversity
4+
//
5+
// Created by Kyle Hughes on 6/22/21.
6+
//
7+
8+
import Foundation
9+
10+
/// A simple sorting algorithm that sorts its elements one at a time.
11+
///
12+
/// - SeeAlso: https://en.wikipedia.org/wiki/Insertion_sort
13+
public struct InsertionSort<Element>: Identifiable {
14+
/// A type that represents the collection of the elements that the algorithm is sorting.
15+
public typealias Elements = Array<Element>
16+
17+
/// The stable identity of the algorithm.
18+
public let id: UUID
19+
20+
/// The given elements that the algorithm is sorting.
21+
///
22+
/// This value is constant and will not change after instantiation.
23+
public let input: Elements
24+
25+
/// The current result of applying the sorting algorithm to `input`.
26+
///
27+
/// This value is "live" and will change as the algorithm is executed. When the algorithm is finished this value
28+
/// will contain the sorted result of `input`. It is primarily exposed to allow the internals of the algorithm to
29+
/// be observed.
30+
///
31+
/// This value should not be used as the final output of the algorithm unless it is known that the algorithm has
32+
/// finished. It may be easier to perform `callAsFunction()` and respond to the step that is returned – the output
33+
/// will be reported through that function if the algorithm is finished.
34+
///
35+
/// - SeeAlso: `outputAfterTransactions`
36+
public private(set) var output: Elements
37+
38+
/// The position (in `output`) of the element that is currently being sorted.
39+
public private(set) var sortingElementIndex: Elements.Index
40+
41+
/// The position (in `output`) that is one greater than the last sorted element in `output`.
42+
///
43+
/// `output` is sorted when this value is equal to `output.endIndex`.
44+
public private(set) var sortedEndIndex: Elements.Index
45+
46+
// MARK: Public Initialization
47+
48+
/// Creates an algorithm to sort the given input using insertion sort.
49+
///
50+
/// - Parameter input: The elements to sort.
51+
public init(input: Elements) {
52+
self.input = input
53+
54+
id = UUID()
55+
output = input
56+
sortingElementIndex = output.index(after: output.startIndex)
57+
sortedEndIndex = sortingElementIndex
58+
}
59+
60+
// MARK: Private Instance Interface
61+
62+
private var adjacentSortedElementIndex: Elements.Index {
63+
output.index(before: sortingElementIndex)
64+
}
65+
66+
private var isNotFinished: Bool {
67+
sortedEndIndex < output.endIndex
68+
}
69+
70+
private mutating func advanceToNextIndexToSort() {
71+
output.formIndex(after: &sortedEndIndex)
72+
sortingElementIndex = sortedEndIndex
73+
}
74+
75+
@discardableResult
76+
private mutating func swapSortingElementWithAdjacentSortedElement() -> Elements.Index {
77+
let sortedElementIndex = adjacentSortedElementIndex
78+
output.swapAt(sortingElementIndex, sortedElementIndex)
79+
80+
return sortedElementIndex
81+
}
82+
83+
private mutating func swapSortingElementWithAdjacentSortedElementAndAdvanceToNextComparison() {
84+
let newSortingElementIndex = swapSortingElementWithAdjacentSortedElement()
85+
86+
if output.startIndex < newSortingElementIndex {
87+
sortingElementIndex = newSortingElementIndex
88+
} else {
89+
advanceToNextIndexToSort()
90+
}
91+
}
92+
}
93+
94+
// MARK: - SortingAlgorithm Extension
95+
96+
extension InsertionSort: SortingAlgorithm {
97+
// MARK: Public Static Interface
98+
99+
/// The runtime complexity of the algorithm.
100+
public static var complexity: Complexity {
101+
.quadratic
102+
}
103+
104+
/// The unique name of the sorting algorithm.
105+
public static var label: SortingAlgorithmLabel {
106+
.insertion
107+
}
108+
109+
/// Returns the number of comparisons that the algorithm will perform, in the worst case, given an input
110+
/// with `n` elements.
111+
///
112+
/// The algorithm may require fewer total comparisons than returned depending on the state of the input and the
113+
/// answers to the comparisons. The algorithm is guaranteed to not require more comparisons than returned.
114+
///
115+
/// This value is provably correct and precise. It is not an estimate.
116+
///
117+
/// - SeeAlso: http://watson.latech.edu/book/algorithms/algorithmsSorting2.html
118+
/// - Parameter n: The number of elements.
119+
/// - Returns: The number of comparisons that the algorithm will perform in the worst case.
120+
public static func calculateMaximumNumberOfComparisonsInWorstCase(for n: Int) -> Int {
121+
guard 1 < n else {
122+
return 0
123+
}
124+
125+
var sum = 0
126+
127+
for i in 1 ... n-1 {
128+
sum += i
129+
}
130+
131+
return sum
132+
}
133+
134+
// MARK: Public Instance Interface
135+
136+
/// Answers the current comparison with the given side.
137+
///
138+
/// The algorithm is advanced to the state that follows the answer.
139+
///
140+
/// If the algorithm is not at a point of comparison then this function will have no effect.
141+
///
142+
/// - Parameter answer: The answer to the current comparison.
143+
public mutating func answer(_ answer: Comparison<Self>.Side) {
144+
guard isNotFinished else {
145+
return
146+
}
147+
148+
switch answer {
149+
case .left:
150+
advanceToNextIndexToSort()
151+
case .right:
152+
swapSortingElementWithAdjacentSortedElementAndAdvanceToNextComparison()
153+
}
154+
}
155+
156+
/// Executes the algorithm in its current state and, if possible, advances it to the next state and returns the
157+
/// step to the caller.
158+
///
159+
/// When the algorithm is not finished this function will return the next comparison that needs to
160+
/// be answered to continue the algorithm. When the algorithm is finished this function will return the sorted
161+
/// output.
162+
///
163+
/// This function is idempotent: for a given state of the algorithm, calling this function will always produce
164+
/// the same result and will always leave the algorithm in the same – possibly new – state. Performing this
165+
/// function consecutively on the same algorithm will have no additional affect.
166+
///
167+
/// - Returns: The next step in the algorithm: either the next comparison to answer, or the sorted output.
168+
public func callAsFunction() -> SortingAlgorithmStep<Self> {
169+
guard isNotFinished else {
170+
return .finished(output)
171+
}
172+
173+
return .comparison(Comparison(source: self))
174+
}
175+
176+
/// Returns the element that represents the given side of the current comparison.
177+
///
178+
/// If the algorithm is not at a point of comparison then `nil` will be returned. For example, if the
179+
/// algorithm has not started or is finished then `nil` will be returned.
180+
///
181+
/// - Parameter answer: The answer that represents the side of the comparison to peek at.
182+
/// - Returns: The element that represents the given side of the current comparison, or `nil` if the algorithm
183+
/// is not at a point of comparison.
184+
public func peekAtElement(for answer: Comparison<InsertionSort<Element>>.Side) -> Element? {
185+
guard isNotFinished else {
186+
return nil
187+
}
188+
189+
switch answer {
190+
case .left:
191+
return output[adjacentSortedElementIndex]
192+
case .right:
193+
return output[sortingElementIndex]
194+
}
195+
}
196+
}
197+
198+
// MARK: - Codable Extension
199+
200+
extension InsertionSort: Codable where Element: Codable {
201+
// NO-OP
202+
}
203+
204+
// MARK: - Equatable Extension
205+
206+
extension InsertionSort: Equatable where Element: Equatable {
207+
// NO-OP
208+
}
209+
210+
// MARK: - Hashable Extension
211+
212+
extension InsertionSort: Hashable where Element: Hashable {
213+
// NO-OP
214+
}

Sources/SortStateUniversity/Merge Sort/MergeSort.swift

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
import Foundation
99

10-
/// A divide-and-conquer sorting algorithm, implemented statefully.
10+
/// A divide-and-conquer sorting algorithm.
1111
///
1212
/// - SeeAlso: https://en.wikipedia.org/wiki/Merge_sort
1313
public struct MergeSort<Element>: Identifiable {
@@ -64,6 +64,8 @@ public struct MergeSort<Element>: Identifiable {
6464
ongoingMerge = nil
6565
output = input
6666
partitionSize = 1
67+
68+
_ = self()
6769
}
6870

6971
// MARK: Public Instance Interface
@@ -147,6 +149,11 @@ extension MergeSort: SortingAlgorithm {
147149
.linearithmic
148150
}
149151

152+
/// The unique name of the sorting algorithm.
153+
public static var label: SortingAlgorithmLabel {
154+
.merge
155+
}
156+
150157
/// Returns the number of comparisons that the algorithm will perform, in the worst case, given an input
151158
/// with `n` elements.
152159
///

Sources/SortStateUniversity/SortingAlgorithm.swift

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ public protocol SortingAlgorithm: Identifiable {
2323
/// The runtime complexity of the algorithm.
2424
static var complexity: Complexity { get }
2525

26+
/// The unique label of the algorithm.
27+
static var label: SortingAlgorithmLabel { get }
28+
2629
/// Returns the number of comparisons that the algorithm will perform, in the worst case, given an input
2730
/// with `n` elements.
2831
///
@@ -34,7 +37,7 @@ public protocol SortingAlgorithm: Identifiable {
3437
/// - Parameter n: The number of elements.
3538
/// - Returns: The number of comparisons that the algorithm will perform in the worst case.
3639
static func calculateMaximumNumberOfComparisonsInWorstCase(for n: Int) -> Int
37-
40+
3841
// MARK: Instance Interface
3942

4043
/// The given elements that the algorithm is sorting.
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
//
2+
// SortingAlgorithmLabel.swift
3+
// SortStateUniversity
4+
//
5+
// Created by Kyle Hughes on 6/24/21.
6+
//
7+
8+
/// The type used to uniquely identify a sorting algorithm implementation.
9+
public struct SortingAlgorithmLabel: Codable, Equatable, Hashable, Identifiable {
10+
/// The stable identity of the sorting algorithm label.
11+
public let id: String
12+
13+
/// The name of the associated sorting algorithm.
14+
///
15+
/// Suitable for display in UI.
16+
///
17+
/// All names from the library are supplied in English.
18+
public let name: String
19+
20+
// MARK: Public Initialization
21+
22+
/// Creates a new label for a sorting algorithm.
23+
///
24+
/// - Parameter name: The name of the sorting algorithm.
25+
public init(id: String, name: String) {
26+
self.id = id
27+
self.name = name
28+
}
29+
}
30+
31+
// MARK: - Constants
32+
33+
extension SortingAlgorithmLabel {
34+
// MARK: Collections
35+
36+
/// The labels of all sorting algorithms that are built into the library.
37+
public static let allBuiltIn: [SortingAlgorithmLabel] = [
38+
.insertion,
39+
.merge,
40+
]
41+
42+
// MARK: Individual Sorts
43+
44+
/// The label of the `InsertionSort` algorithm.
45+
public static let insertion = SortingAlgorithmLabel(id: "insertion", name: "Insertion Sort")
46+
47+
/// The label of the `MergeSort` algorithm.
48+
public static let merge = SortingAlgorithmLabel(id: "merge", name: "Merge Sort")
49+
}

0 commit comments

Comments
 (0)