Skip to content

Commit 429237e

Browse files
authored
Merge pull request kodecocodes#282 from BenEmdon/master
Rootish Array Stack
2 parents dfc27f8 + b64df56 commit 429237e

File tree

17 files changed

+1413
-0
lines changed

17 files changed

+1413
-0
lines changed

.travis.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ script:
3232
# - xcodebuild test -project ./Priority\ Queue/Tests/Tests.xcodeproj -scheme Tests
3333
- xcodebuild test -project ./Queue/Tests/Tests.xcodeproj -scheme Tests
3434
# - xcodebuild test -project ./Quicksort/Tests/Tests.xcodeproj -scheme Tests
35+
- xcodebuild test -project ./Rootish\ Array\ Stack/Tests/Tests.xcodeproj -scheme Tests
3536
# - xcodebuild test -project ./Run-Length\ Encoding/Tests/Tests.xcodeproj -scheme Tests
3637
# - xcodebuild test -project ./Select\ Minimum\ Maximum/Tests/Tests.xcodeproj -scheme Tests
3738
- xcodebuild test -project ./Selection\ Sort/Tests/Tests.xcodeproj -scheme Tests

README.markdown

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@ Most of the time using just the built-in `Array`, `Dictionary`, and `Set` types
130130
- [Bit Set](Bit Set/). A fixed-size sequence of *n* bits.
131131
- [Fixed Size Array](Fixed Size Array/). When you know beforehand how large your data will be, it might be more efficient to use an old-fashioned array with a fixed size.
132132
- [Ordered Array](Ordered Array/). An array that is always sorted.
133+
- [Rootish Array Stack](Rootish Array Stack/). A space and time efficient variation on Swift arrays.
133134

134135
### Queues
135136

@@ -206,6 +207,7 @@ The following books are available for free online:
206207
- [Algorithms, Etc.](http://jeffe.cs.illinois.edu/teaching/algorithms/) by Erickson
207208
- [Algorithms + Data Structures = Programs](http://www.ethoberon.ethz.ch/WirthPubl/AD.pdf) by Wirth
208209
- Algorithms and Data Structures: The Basic Toolbox by Mehlhorn and Sanders
210+
- [Open Data Structures](http://opendatastructures.org) by Pat Morin
209211
- [Wikibooks: Algorithms and Implementations](https://en.wikibooks.org/wiki/Algorithm_Implementation)
210212

211213
Other algorithm repositories:

Rootish Array Stack/README.md

Lines changed: 243 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
# Rootish Array Stack
2+
3+
A *Rootish Array Stack* is an ordered array based structure that minimizes wasted space (based on [Gauss's summation technique](https://betterexplained.com/articles/techniques-for-adding-the-numbers-1-to-100/)). A *Rootish Array Stack* consists of an array holding many fixed size arrays in ascending size.
4+
5+
![Rootish Array Stack Intro](images/RootishArrayStackIntro.png)
6+
7+
A resizable array holds references to blocks (arrays of fixed size). A block's capacity is the same as it's index in the resizable array. Blocks don't grow/shrink like regular Swift arrays. Instead, when their capacity is reached, a new slightly larger block is created. When a block is emptied the last block is freed. This is a great improvement on what Swift arrays do in terms of wasted space.
8+
9+
![Rootish Array Stack Intro](images/RootishArrayStackExample.png)
10+
11+
Here you can see how insert/remove operations would behave (very similar to how a Swift array handles such operations).
12+
13+
## Gauss' Summation Trick
14+
One of the most well known legends about famous mathematician [Carl Friedrich Gauss](https://en.wikipedia.org/wiki/Carl_Friedrich_Gauss) goes back to when he was in primary school. One day, Gauss' teacher asked his class to add up all the numbers from 1 to 100, hoping that the task would take long enough for him to step out for a smoke break. The teacher was shocked when young Gauss had his hand up with the answer `5050`. So soon? The teacher suspected a cheat, but no. Gauss had found a formula to sidestep the problem of manually adding up all the numbers 1 by 1. His formula:
15+
```
16+
sum from 1...n = n * (n + 1) / 2
17+
```
18+
To understand this imagine `n` blocks where `x` represents `1` unit. In this example let `n` be `5`:
19+
```
20+
blocks: [x] [x x] [x x x] [x x x x] [x x x x x]
21+
# of x's: 1 2 3 4 5
22+
```
23+
_Block `1` has 1 `x`, block `2` as 2 `x`s, block `3` has 3 `x`s, etc..._
24+
25+
If you wanted to take the sum of all the blocks from `1` to `n`, you could go through and count them _one by one_. This is okay, but for a large sequence of blocks that could take a long time! Instead, you could arrange the blocks to look like a _half pyramid_:
26+
```
27+
# | blocks
28+
--|-------------
29+
1 | x
30+
2 | x x
31+
3 | x x x
32+
4 | x x x x
33+
5 | x x x x x
34+
35+
```
36+
Then we mirror the _half pyramid_ and rearrange the image so that it fits with the original _half pyramid_ in a rectangular shape:
37+
```
38+
x o x o o o o o
39+
x x o o x x o o o o
40+
x x x o o o => x x x o o o
41+
x x x x o o o o x x x x o o
42+
x x x x x o o o o o x x x x x o
43+
```
44+
Here we have `n` rows and `n + 1` columns. _5 rows and 6 columns_.
45+
46+
We can calculate the sum just as we would an area! Let's also express the width and height in terms of `n`:
47+
```
48+
area of a rectangle = height * width = n * (n + 1)
49+
```
50+
We only want to calculate the amount of `x`s, not the amount of `o`s. Since there's a 1:1 ratio between `x`s and `o`s we can just divide our area by 2!
51+
```
52+
area of only x = n * (n + 1) / 2
53+
```
54+
Voila! A super fast way to take a sum of all the blocks! This equation is useful for deriving fast `block` and `inner block index` equations.
55+
<!-- TODO: Define block and innerBlockIndex -->
56+
57+
## Get/Set with Speed
58+
Next, we want to find an efficient and accurate way to access an element at a random index. For example, which block does `rootishArrayStack[12]` point to? To answer this we will need more math!
59+
Determining the inner block `index` turns out to be easy. If `index` is in some `block` then:
60+
```
61+
inner block index = index - block * (block + 1) / 2
62+
```
63+
Determining which `block` an index points to is more difficult. The number of elements up to and including the element requested is: `index + 1` elements. The number of elements in blocks `0...block` is `(block + 1) * (block + 2) / 2` (equation derived above). The relationship between the `block` and the `index` is as follows:
64+
```
65+
(block + 1) * (block + 2) / 2 >= index + 1
66+
```
67+
This can be rewritten as:
68+
```
69+
(block)^2 + (3 * block) - (2 * index) >= 0
70+
```
71+
Using the quadratic formula we get:
72+
```
73+
block = (-3 ± √(9 + 8 * index)) / 2
74+
```
75+
A negative block doesn't make sense, so we take the positive root instead. In general, this solution is not an integer. However, going back to our inequality, we want the smallest block such that `block => (-3 + √(9 + 8 * index)) / 2`. Next, we take the ceiling of the result:
76+
```
77+
block = ⌈(-3 + √(9 + 8 * index)) / 2⌉
78+
```
79+
80+
Now we can figure out what `rootishArrayStack[12]` points to! First, let's see which block the `12` points to:
81+
```
82+
block = ⌈(-3 + √(9 + 8 * (12))) / 2⌉
83+
block = ⌈(-3 + √105) / 2⌉
84+
block = ⌈(-3 + (10.246950766)) / 2⌉
85+
block = ⌈(7.246950766) / 2⌉
86+
block = ⌈3.623475383⌉
87+
block = 4
88+
```
89+
Next lets see which `innerBlockIndex` `12` points to:
90+
```
91+
inner block index = (12) - (4) * ((4) + 1) / 2
92+
inner block index = (12) - (4) * (5) / 2
93+
inner block index = (12) - 10
94+
inner block index = 2
95+
```
96+
Therefore, `rootishArrayStack[12]` points to the block at index `4` and at inner block index `2`.
97+
![Rootish Array Stack Intro](images/RootishArrayStackExample2.png)
98+
99+
### Interesting Discovery
100+
Using the `block` equation, we can see that the number of `blocks` is proportional to the square root of the number of elements: **O(blocks) = O(√n)**.
101+
102+
# Implementation Details
103+
Let's start with instance variables and struct declaration:
104+
```swift
105+
import Darwin
106+
107+
public struct RootishArrayStack<T> {
108+
109+
fileprivate var blocks = [Array<T?>]()
110+
fileprivate var internalCount = 0
111+
112+
public init() { }
113+
114+
var count: Int {
115+
return internalCount
116+
}
117+
118+
...
119+
120+
}
121+
122+
```
123+
The elements are of generic type `T`, so data of any kind can be stored in the list. `blocks` will be a resizable array to hold fixed sized arrays that take type `T?`.
124+
> The reason for the fixed size arrays taking type `T?` is so that references to elements aren't retained after they've been removed. Eg: if you remove the last element, the last index must be set to `nil` to prevent the last element being held in memory at an inaccessible index.
125+
126+
`internalCount` is an internal mutable counter that keeps track of the number of elements. `count` is a read only variable that returns the `internalCount` value. `Darwin` is imported here to provide simple math functions such as `ceil()` and `sqrt()`.
127+
128+
The `capacity` of the structure is simply the Gaussian summation trick:
129+
```swift
130+
var capacity: Int {
131+
return blocks.count * (blocks.count + 1) / 2
132+
}
133+
```
134+
135+
Next, let's look at how we would `get` and `set` elements:
136+
```swift
137+
fileprivate func block(fromIndex: Int) -> Int {
138+
let block = Int(ceil((-3.0 + sqrt(9.0 + 8.0 * Double(index))) / 2))
139+
return block
140+
}
141+
142+
fileprivate func innerBlockIndex(fromIndex index: Int, fromBlock block: Int) -> Int {
143+
return index - block * (block + 1) / 2
144+
}
145+
146+
public subscript(index: Int) -> T {
147+
get {
148+
let block = self.block(fromIndex: index)
149+
let innerBlockIndex = self.innerBlockIndex(fromIndex: index, fromBlock: block)
150+
return blocks[block][innerBlockIndex]!
151+
}
152+
set(newValue) {
153+
let block = self.block(fromIndex: index)
154+
let innerBlockIndex = self.innerBlockIndex(fromIndex: index, fromBlock: block)
155+
blocks[block][innerBlockIndex] = newValue
156+
}
157+
}
158+
```
159+
`block(fromIndex:)` and `innerBlockIndex(fromIndex:, fromBlock:)` are wrapping the `block` and `inner block index` equations we derived earlier. `superscript` lets us have `get` and `set` access to the structure with the familiar `[index:]` syntax. For both `get` and `set` in `superscript` we use the same logic:
160+
161+
1. determine the block that the index points to
162+
2. determine the inner block index
163+
3. `get`/`set` the value
164+
165+
Next, let's look at how we would `growIfNeeded()` and `shrinkIfNeeded()`.
166+
```swift
167+
fileprivate mutating func growIfNeeded() {
168+
if capacity - blocks.count < count + 1 {
169+
let newArray = [T?](repeating: nil, count: blocks.count + 1)
170+
blocks.append(newArray)
171+
}
172+
}
173+
174+
fileprivate mutating func shrinkIfNeeded() {
175+
if capacity + blocks.count >= count {
176+
while blocks.count > 0 && (blocks.count - 2) * (blocks.count - 1) / 2 > count {
177+
blocks.remove(at: blocks.count - 1)
178+
}
179+
}
180+
}
181+
```
182+
If our data set grows or shrinks in size, we want our data structure to accommodate the change.
183+
Just like a Swift array, when a capacity threshold is met we will `grow` or `shrink` the size of our structure. For the Rootish Array Stack we want to `grow` if the second last block is full on an `insert` operation, and `shrink` if the two last blocks are empty.
184+
185+
Now to the more familiar Swift array behaviour.
186+
```swift
187+
public mutating func insert(element: T, atIndex index: Int) {
188+
growIfNeeded()
189+
internalCount += 1
190+
var i = count - 1
191+
while i > index {
192+
self[i] = self[i - 1]
193+
i -= 1
194+
}
195+
self[index] = element
196+
}
197+
198+
public mutating func append(element: T) {
199+
insert(element: element, atIndex: count)
200+
}
201+
202+
public mutating func remove(atIndex index: Int) -> T {
203+
let element = self[index]
204+
for i in index..<count - 1 {
205+
self[i] = self[i + 1]
206+
}
207+
internalCount -= 1
208+
makeNil(atIndex: count)
209+
shrinkIfNeeded()
210+
return element
211+
}
212+
213+
fileprivate mutating func makeNil(atIndex index: Int) {
214+
let block = self.block(fromIndex: index)
215+
let innerBlockIndex = self.innerBlockIndex(fromIndex: index, fromBlock: block)
216+
blocks[block][innerBlockIndex] = nil
217+
}
218+
```
219+
To `insert(element:, atIndex:)` we move all elements after the `index` to the right by 1. After space has been made for the element, we set the value using the `subscript` convenience method.
220+
`append(element:)` is just a convenience method to `insert` to the end.
221+
To `remove(atIndex:)` we move all the elements after the `index` to the left by 1. After the removed value is covered by it's proceeding value, we set the last value in the structure to `nil`.
222+
`makeNil(atIndex:)` uses the same logic as our `subscript` method but is used to set the root optional at a particular index to `nil` (because setting it's wrapped value to `nil` is something only the user of the data structure should do).
223+
> Setting a optionals value to `nil` is different than setting it's wrapped value to `nil`. An optionals wrapped value is an embedded type within the optional reference. This means that a `nil` wrapped value is actually `.some(.none)` whereas setting the root reference to `nil` is `.none`. To better understand Swift optionals I recommend checking out @SebastianBoldt's article [Swift! Optionals?](https://medium.com/ios-os-x-development/swift-optionals-78dafaa53f3#.rvjobhuzs).
224+
225+
# Performance
226+
* An internal counter keeps track of the number of elements in the structure. `count` is executed in **O(1)** time.
227+
228+
* `capacity` can be calculated using Gauss' summation trick in an equation which takes **O(1)** time to execute.
229+
230+
* Since `subcript[index:]` uses the `block` and `inner block index` equations, which can be executed in **O(1)** time, all get and set operations take **O(1)**.
231+
232+
* Ignoring the time cost to `grow` and `shrink`, `insert(atIndex:)` and `remove(atIndex:)` operations shift all elements right of the specified index resulting in **O(n)** time.
233+
234+
# Analysis of Growing and Shrinking
235+
The performance analysis doesn't account for the cost to `grow` and `shrink`. Unlike a regular Swift array, `grow` and `shrink` operations don't copy all the elements into a backing array. They only allocate or free an array proportional to the number of `blocks`. The number of `blocks` is proportional to the square root of the number of elements. Growing and shrinking only costs **O(√n)**.
236+
237+
# Wasted Space
238+
Wasted space is how much memory with respect to the number of elements `n` is unused. The Rootish Array Stack never has more than 2 empty blocks and it never has less than 1 empty block. The last two blocks are proportional to the number of blocks, which is proportional to the square root of the number of elements. The number of references needed to point to each block is the same as the number of blocks. Therefore, the amount of wasted space with respect to the number of elements is **O(√n)**.
239+
240+
241+
_Written for Swift Algorithm Club by @BenEmdon_
242+
243+
_With help from [OpenDataStructures.org](http://opendatastructures.org)_

0 commit comments

Comments
 (0)