Skip to content

Commit 55bc61b

Browse files
committed
Add ordered array
1 parent 7442fd5 commit 55bc61b

File tree

6 files changed

+262
-1
lines changed

6 files changed

+262
-1
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
//: Playground - noun: a place where people can play
2+
3+
public struct OrderedArray<T: Comparable> {
4+
private var array = [T]()
5+
6+
public init(array: [T]) {
7+
self.array = array.sort()
8+
}
9+
10+
public var isEmpty: Bool {
11+
return array.isEmpty
12+
}
13+
14+
public var count: Int {
15+
return array.count
16+
}
17+
18+
public subscript(index: Int) -> T {
19+
return array[index]
20+
}
21+
22+
public mutating func removeAtIndex(index: Int) -> T {
23+
return array.removeAtIndex(index)
24+
}
25+
26+
public mutating func removeAll() {
27+
array.removeAll()
28+
}
29+
30+
public mutating func insert(newElement: T) -> Int {
31+
let i = findInsertionPoint(newElement)
32+
array.insert(newElement, atIndex: i)
33+
return i
34+
}
35+
36+
/*
37+
// Slow version that looks at every element in the array.
38+
private func findInsertionPoint(newElement: T) -> Int {
39+
for i in 0..<array.count {
40+
if newElement <= array[i] {
41+
return i
42+
}
43+
}
44+
return array.count
45+
}
46+
*/
47+
48+
// Fast version that uses a binary search.
49+
private func findInsertionPoint(newElement: T) -> Int {
50+
var range = 0..<array.count
51+
while range.startIndex < range.endIndex {
52+
let midIndex = range.startIndex + (range.endIndex - range.startIndex) / 2
53+
if array[midIndex] == newElement {
54+
return midIndex
55+
} else if array[midIndex] < newElement {
56+
range.startIndex = midIndex + 1
57+
} else {
58+
range.endIndex = midIndex
59+
}
60+
}
61+
return range.startIndex
62+
}
63+
}
64+
65+
extension OrderedArray: CustomStringConvertible {
66+
public var description: String {
67+
return array.description
68+
}
69+
}
70+
71+
72+
73+
var a = OrderedArray<Int>(array: [5, 1, 3, 9, 7, -1])
74+
a // [-1, 1, 3, 5, 7, 9]
75+
76+
a.insert(4) // inserted at index 3
77+
a // [-1, 1, 3, 4, 5, 7, 9]
78+
79+
a.insert(-2) // inserted at index 0
80+
a.insert(10) // inserted at index 8
81+
a // [-2, -1, 1, 3, 4, 5, 7, 9, 10]
82+
Lines changed: 4 additions & 0 deletions
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='osx'>
3+
<timeline fileName='timeline.xctimeline'/>
4+
</playground>
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<Timeline
3+
version = "3.0">
4+
<TimelineItems>
5+
</TimelineItems>
6+
</Timeline>

Ordered Array/OrderedArray.swift

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/*
2+
An ordered array. When you add a new item to this array, it is inserted in
3+
sorted position.
4+
*/
5+
public struct OrderedArray<T: Comparable> {
6+
private var array = [T]()
7+
8+
public init(array: [T]) {
9+
self.array = array.sort()
10+
}
11+
12+
public var isEmpty: Bool {
13+
return array.isEmpty
14+
}
15+
16+
public var count: Int {
17+
return array.count
18+
}
19+
20+
public subscript(index: Int) -> T {
21+
return array[index]
22+
}
23+
24+
public mutating func removeAtIndex(index: Int) -> T {
25+
return array.removeAtIndex(index)
26+
}
27+
28+
public mutating func removeAll() {
29+
array.removeAll()
30+
}
31+
32+
public mutating func insert(newElement: T) -> Int {
33+
let i = findInsertionPoint(newElement)
34+
array.insert(newElement, atIndex: i)
35+
return i
36+
}
37+
38+
private func findInsertionPoint(newElement: T) -> Int {
39+
var range = 0..<array.count
40+
while range.startIndex < range.endIndex {
41+
let midIndex = range.startIndex + (range.endIndex - range.startIndex) / 2
42+
if array[midIndex] == newElement {
43+
return midIndex
44+
} else if array[midIndex] < newElement {
45+
range.startIndex = midIndex + 1
46+
} else {
47+
range.endIndex = midIndex
48+
}
49+
}
50+
return range.startIndex
51+
}
52+
}
53+
54+
extension OrderedArray: CustomStringConvertible {
55+
public var description: String {
56+
return array.description
57+
}
58+
}

Ordered Array/README.markdown

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
# Ordered Array
2+
3+
This is an array that is always sorted from low to high. Whenever you add a new item to this array, it is inserted in its sorted position.
4+
5+
An ordered array is useful for when you want your data to be sorted and you're inserting new items relatively rarely. In that case, it's faster than sorting the entire array. However, if you need to change the array often, it's probably faster to use a regular array and sort it manually.
6+
7+
The implementation is quite basic. It's simply a wrapper around Swift's built-in array:
8+
9+
```swift
10+
public struct OrderedArray<T: Comparable> {
11+
private var array = [T]()
12+
13+
public init(array: [T]) {
14+
self.array = array.sort()
15+
}
16+
17+
public var isEmpty: Bool {
18+
return array.isEmpty
19+
}
20+
21+
public var count: Int {
22+
return array.count
23+
}
24+
25+
public subscript(index: Int) -> T {
26+
return array[index]
27+
}
28+
29+
public mutating func removeAtIndex(index: Int) -> T {
30+
return array.removeAtIndex(index)
31+
}
32+
33+
public mutating func removeAll() {
34+
array.removeAll()
35+
}
36+
}
37+
38+
extension OrderedArray: CustomStringConvertible {
39+
public var description: String {
40+
return array.description
41+
}
42+
}
43+
```
44+
45+
As you can see, all these methods simply call the corresponding method on the internal `array` variable.
46+
47+
What remains is the `insert()` function. Here is an initial stab at it:
48+
49+
```swift
50+
public mutating func insert(newElement: T) -> Int {
51+
let i = findInsertionPoint(newElement)
52+
array.insert(newElement, atIndex: i)
53+
return i
54+
}
55+
56+
private func findInsertionPoint(newElement: T) -> Int {
57+
for i in 0..<array.count {
58+
if newElement <= array[i] {
59+
return i
60+
}
61+
}
62+
return array.count // insert at the end
63+
}
64+
```
65+
66+
The helper function `findInsertionPoint()` simply loops through the entire array, looking for the right place to insert the new element.
67+
68+
> **Note:** Quite conveniently, `array.insert(... atIndex: array.count)` adds the new object to the end of the array, so if no suitable insertion point was found we can simply return `array.count` as the index.
69+
70+
Here's how you can test it in a playground:
71+
72+
```swift
73+
var a = OrderedArray<Int>(array: [5, 1, 3, 9, 7, -1])
74+
a // [-1, 1, 3, 5, 7, 9]
75+
76+
a.insert(4) // inserted at index 3
77+
a // [-1, 1, 3, 4, 5, 7, 9]
78+
79+
a.insert(-2) // inserted at index 0
80+
a.insert(10) // inserted at index 8
81+
a // [-2, -1, 1, 3, 4, 5, 7, 9, 10]
82+
```
83+
84+
The array's contents will always be sorted from low to high, now matter what.
85+
86+
Unfortunately, the current `findInsertionPoint()` function is a bit slow. In the worst case, it needs to scan through the entire array in order to insert the new element. We can speed this up by using a [binary search](../Binary Search) to find the insertion point.
87+
88+
Here is the new version:
89+
90+
```swift
91+
private func findInsertionPoint(newElement: T) -> Int {
92+
var range = 0..<array.count
93+
while range.startIndex < range.endIndex {
94+
let midIndex = range.startIndex + (range.endIndex - range.startIndex) / 2
95+
if array[midIndex] == newElement {
96+
return midIndex
97+
} else if array[midIndex] < newElement {
98+
range.startIndex = midIndex + 1
99+
} else {
100+
range.endIndex = midIndex
101+
}
102+
}
103+
return range.startIndex
104+
}
105+
```
106+
107+
The big difference with a regular binary search is that this doesn't return `nil` when the value can't be found, but the array index where the element would have been. That's where we insert the new object.
108+
109+
Note that using binary search doesn't change the worst-case running time complexity of `insert()`. The binary search itself takes only **O(log n)** time, but inserting a new object in the middle of an array still involves shifting all remaining elements to the right in memory. So overall, the time complexity is still **O(n)**. But in practice this new version definitely is a lot faster, especially on large arrays.
110+
111+
*Written for Swift Algorithm Club by Matthijs Hollemans*

README.markdown

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ Often just using the built-in `Array`, `Dictionary`, and `Set` types is sufficie
117117

118118
- [Array2D](Array2D/). A two-dimensional array with fixed dimensions. Useful for board games.
119119
- [Fixed Size Array](Fixed Size Array/). When you know beforehand how large your data will be, it might be more efficient to use an array with a fixed size.
120-
- Ordered Array. An array that is always sorted.
120+
- [Ordered Array](Ordered Array/). An array that is always sorted.
121121

122122
### Queues
123123

0 commit comments

Comments
 (0)