├── skiplists.xcodeproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
├── xcuserdata
│ └── peter.dasilva.xcuserdatad
│ │ └── xcschemes
│ │ ├── xcschememanagement.plist
│ │ └── skiplists.xcscheme
└── project.pbxproj
├── skiplists
├── speedtables.swift
├── queries.swift
├── speedtable-example.swift
├── main.swift
└── skiplists.swift
└── README.md
/skiplists.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/skiplists/speedtables.swift:
--------------------------------------------------------------------------------
1 | //
2 | // speedtables.swift
3 | // skiplists
4 | //
5 | // Created by Peter da Silva on 5/31/16.
6 | // Copyright © 2016 Flightaware. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol SpeedTable {
12 | }
13 |
14 | protocol SpeedTableRow {
15 | }
16 |
17 | // SpeedTable protocol errors
18 | public enum SpeedTableError: ErrorType {
19 | case KeyNotUnique(key: Key)
20 | }
21 |
--------------------------------------------------------------------------------
/skiplists.xcodeproj/xcuserdata/peter.dasilva.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | skiplists.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | 9BE496A31CF63053002D643C
16 |
17 | primary
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/skiplists/queries.swift:
--------------------------------------------------------------------------------
1 | //
2 | // queries.swift
3 | // skiplists
4 | //
5 | // Created by Peter da Silva on 5/31/16.
6 | // Copyright © 2016 Flightaware. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | private struct QueryState {
12 | var node: SLNode? = nil
13 | var index: Int = -1
14 |
15 | private init(node: SLNode?) {
16 | self.node = node
17 | self.index = -1
18 | }
19 | }
20 |
21 | public class Query: SequenceType {
22 | let list: SkipList
23 | let min: Key?
24 | let max: Key?
25 | let minEqual: Bool
26 | let maxEqual: Bool
27 | private var state: QueryState
28 |
29 | init(list: SkipList, min: Key? = nil, max: Key? = nil, minEqual: Bool = true, maxEqual: Bool = true) {
30 | self.list = list
31 | self.min = min
32 | self.minEqual = minEqual
33 | self.max = max
34 | self.maxEqual = maxEqual
35 | self.state = QueryState(node: nil)
36 | }
37 |
38 | private func start() -> QueryState {
39 | var node: SLNode?
40 |
41 | if let min = self.min {
42 | node = list.search(greaterThanOrEqualTo: min)
43 | if node != nil && minEqual == false && node!.key == min {
44 | node = node!.next[0]
45 | }
46 | } else {
47 | node = list.head.next[0]
48 | }
49 | return QueryState(node: node)
50 | }
51 |
52 | private func step(inout state: QueryState) {
53 | guard state.node != nil else { return }
54 |
55 | // step to the next index
56 | state.index += 1
57 |
58 | // if we've stepped past the current node's values, keep stepping until we get a node with values
59 | while state.node != nil && state.index >= state.node!.values.count {
60 | state.node = state.node!.next[0]
61 | state.index = 0
62 | }
63 |
64 | // if you ran out of nodes, our work is done
65 | guard let node = state.node else { return }
66 |
67 | // If there's no max, our work is done
68 | if max == nil { return }
69 |
70 | if maxEqual {
71 | if max < node.key {
72 | state.node = nil
73 | }
74 | } else {
75 | if max <= node.key {
76 | state.node = nil
77 | }
78 | }
79 | }
80 |
81 | public func reset() {
82 | state = start()
83 | }
84 |
85 | public func first() -> (Key, Value)? {
86 | state = start()
87 | return next()
88 | }
89 |
90 | public func next() -> (Key, Value)? {
91 | step(&state)
92 |
93 | guard let node = state.node else { return nil }
94 |
95 | return (node.key!, node.values[state.index])
96 | }
97 |
98 | public func generate() -> AnyGenerator<(Key, Value)> {
99 | var state = start()
100 |
101 | return AnyGenerator<(Key, Value)> {
102 | self.step(&state)
103 |
104 | guard let node = state.node else { return nil }
105 |
106 | return (node.key!, node.values[state.index])
107 | }
108 | }
109 | }
--------------------------------------------------------------------------------
/skiplists/speedtable-example.swift:
--------------------------------------------------------------------------------
1 | //
2 | // speedtable-example.swift
3 | // skiplists
4 | //
5 | // Created by Peter da Silva on 5/27/16.
6 | // Copyright © 2016 Flightaware. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // Manually generated speedtable definition. This could be automatically generated
12 | // from something SQL-ish like:
13 | // TABLE Table (
14 | // String name indexed
15 | // Int age indexed
16 | // String school optional
17 | // String studentID unique optional indexed
18 | // )
19 | class Table: SpeedTable {
20 | let nameIndex: SkipList
21 | let ageIndex: SkipList
22 | let studentIDIndex: SkipList
23 | init(maxLevel: Int) {
24 | nameIndex = SkipList(maxLevel: maxLevel)
25 | ageIndex = SkipList(maxLevel: maxLevel)
26 | studentIDIndex = SkipList(maxLevel: maxLevel)
27 | }
28 | init(size: Int) {
29 | nameIndex = SkipList(maxNodes: size)
30 | ageIndex = SkipList(maxNodes: size)
31 | studentIDIndex = SkipList(maxNodes: size)
32 | }
33 | func insert(name: String, age: Int) -> TableRow {
34 | // Creating the table row does all the insertion stuff
35 | return TableRow(parent: self, name: name, age: age)
36 | }
37 | func delete(row: TableRow) {
38 | // delegate to row
39 | row.delete()
40 | }
41 | }
42 |
43 | // Each speedtable requires two classes, one for the table as a whole, one for
44 | // the row holding the data
45 | class TableRow: SpeedTableRow, Equatable {
46 | var parent: Table
47 | var name: String {
48 | willSet { parent.nameIndex.delete(name, value: self) }
49 | didSet { parent.nameIndex.insert(name, value: self) }
50 | }
51 | var age: Int {
52 | willSet { parent.ageIndex.delete(age, value: self) }
53 | didSet { parent.ageIndex.insert(age, value: self) }
54 | }
55 | var school: String? // Unindexed value
56 | var studentIDStorage: String? // unique optional value
57 | func getStudentID() -> String? {
58 | return studentIDStorage
59 | }
60 | func setStudentID(ID: String?) throws {
61 | if let key = ID {
62 | if parent.studentIDIndex.exists(key) {
63 | throw SpeedTableError.KeyNotUnique(key: key)
64 | }
65 | }
66 | parent.studentIDIndex.replace(ID, keyStore: &studentIDStorage, value: self)
67 | }
68 | init(parent: Table, name: String, age: Int) {
69 | self.parent = parent
70 | self.name = name
71 | self.age = age
72 | // This needs to be done explicitly because the willSet/didSet doesn't
73 | // fire on initialization.
74 | parent.nameIndex.insert(self.name, value: self)
75 | parent.ageIndex.insert(self.age, value: self)
76 | }
77 | func delete() {
78 | parent.nameIndex.delete(name, value: self)
79 | parent.ageIndex.delete(age, value:self)
80 | if let ID = studentIDStorage {
81 | parent.studentIDIndex.delete(ID, value:self)
82 | }
83 | }
84 | }
85 |
86 | // This function can be anything guaranteed unique for the table. We're using === here
87 | // but it can be a unique key within the row instead, if there is one. Possibly a "primary
88 | // key" field in the generator can be used to drive this.
89 | func ==(lhs: TableRow, rhs: TableRow) -> Bool {
90 | return lhs === rhs
91 | }
92 |
93 |
--------------------------------------------------------------------------------
/skiplists.xcodeproj/xcuserdata/peter.dasilva.xcuserdatad/xcschemes/skiplists.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
39 |
40 |
41 |
42 |
43 |
44 |
54 |
56 |
62 |
63 |
64 |
65 |
66 |
67 |
73 |
75 |
81 |
82 |
83 |
84 |
86 |
87 |
90 |
91 |
92 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Overview
2 |
3 | This is a work in progress. The goal of this project is to create a multi-way indexed relation in Swift. Right now creating an indexed table is pretty manual:
4 |
5 | ```swift
6 | // Manually generated speedtable definition. This could be automatically generated
7 | // from something SQL-ish like:
8 | // TABLE Table (
9 | // String name indexed
10 | // Int age indexed
11 | // String school optional
12 | // String studentID unique optional indexed
13 | // )
14 | class Table: SpeedTable {
15 | let nameIndex: SkipList
16 | let ageIndex: SkipList
17 | let studentIDIndex: SkipList
18 | init(maxLevel: Int) {
19 | nameIndex = SkipList(maxLevel: maxLevel)
20 | ageIndex = SkipList(maxLevel: maxLevel)
21 | studentIDIndex = SkipList(maxLevel: maxLevel)
22 | }
23 | init(size: Int) {
24 | nameIndex = SkipList(maxNodes: size)
25 | ageIndex = SkipList(maxNodes: size)
26 | studentIDIndex = SkipList(maxNodes: size)
27 | }
28 | func insert(name: String, age: Int) -> TableRow {
29 | // Creating the table row does all the insertion stuff
30 | return TableRow(parent: self, name: name, age: age)
31 | }
32 | func delete(row: TableRow) {
33 | // delegate to row
34 | row.delete()
35 | }
36 | }
37 |
38 | // Each speedtable requires two classes, one for the table as a whole, one for
39 | // the row holding the data
40 | class TableRow: SpeedTableRow, Equatable {
41 | var parent: Table?
42 | var name: String {
43 | willSet { parent!.nameIndex.delete(name, value: self) }
44 | didSet { parent!.nameIndex.insert(name, value: self) }
45 | }
46 | var age: Int {
47 | willSet { parent!.ageIndex.delete(age, value: self) }
48 | didSet { parent!.ageIndex.insert(age, value: self) }
49 | }
50 | var school: String? // Unindexed value
51 | var studentIDStorage: String? // unique optional value
52 | func getStudentID() -> String? {
53 | return studentIDStorage
54 | }
55 | func setStudentID(ID: String?) throws {
56 | if let key = ID {
57 | if parent!.studentIDIndex.exists(key) {
58 | throw SpeedTableError.KeyNotUnique(key: key)
59 | }
60 | }
61 | parent!.studentIDIndex.replace(ID, keyStore: &studentIDStorage, value: self)
62 | }
63 | init(parent: Table, name: String, age: Int) {
64 | self.parent = parent
65 | self.name = name
66 | self.age = age
67 | // This needs to be done explicitly because the willSet/didSet doesn't
68 | // fire on initialization.
69 | parent.nameIndex.insert(self.name, value: self)
70 | parent.ageIndex.insert(self.age, value: self)
71 | }
72 | func delete() {
73 | parent!.nameIndex.delete(name, value: self)
74 | parent!.ageIndex.delete(age, value:self)
75 | if let ID = studentIDStorage {
76 | parent!.studentIDIndex.delete(ID, value:self)
77 | }
78 | parent = nil // do not modify a row after it's deleted!
79 | }
80 | }
81 |
82 | // This function can be anything guaranteed unique for the table. We're using === here
83 | // but it can be a unique key within the row instead, if there is one. Possibly a "primary
84 | // key" field in the generator can be used to drive this.
85 | func ==(lhs: TableRow, rhs: TableRow) -> Bool {
86 | return lhs === rhs
87 | }
88 | ```
89 |
90 | Eventually, we would like to have this created by a little applet that takes a Tcl
91 | or SQL-ish speedtable definition and generates this framework.
92 |
93 | There are a few operations you can do on a table right now. The first two are implicit
94 | in the above definition.
95 |
96 | * ```table.insert(column, column, column...)```
97 |
98 | Insert a new row into the table, and all indexes, returns the row... or you can search for the row later.
99 |
100 | * ```table.delete(row)```
101 |
102 | Delete a row from the table, unthreads it from all the indexes and unlinks it from the table.
103 |
104 | * ```row.column = value```
105 |
106 | When you update a column in a row, it updates the index on the column automatically.
107 |
108 | The rest of the operations are implied by the behaviour of the indexes (skiplists), for
109 | example:
110 |
111 | ```swift
112 | for row in table.nameIndex.search(equal: myName) {
113 | if(row.age > = 16) {
114 | row.school = "senior"
115 | }
116 | }
117 | ```
118 |
119 | Indexes are skiplists:
120 |
121 | * ```thingIndex = SkipList(maxLevel: maxLevel)```
122 | * ```thingIndex = SkipList(maxSize: maxSize)```
123 |
124 | ** maxLevel - maximum depth of the list, at least log(2) maximum nodes
125 | ** maxSize - maximum number of nodes (convenience init)
126 | ** unique - true if the index is unique
127 |
128 | The operations on indexes are:
129 |
130 | * ```skiplist.search(key)```
131 |
132 | Search looks up a key and returns an array of rows that match the key. This is just a simple skiplist lookup.
133 |
134 | * ```skiplist.insert(key, value)```
135 | * ```skiplist.delete(key, value)```
136 |
137 | You will not be using these directly in speedtables. The insert and delete a key-value pair from the index.
138 |
139 | * ```skiplist.exists(key)```
140 | * ```skiplist.exists(key, value)```
141 |
142 | Helper functions for handling unique lists.
143 |
144 | * ```for (key, value) in skiplist```
145 |
146 | Walks the entire skiplist and returns all the key-value pairs. The value in a speedtable will be a speedtable row.
147 |
148 | * ```for (key, value) in skiplist.query(lessThan: key)```
149 | * ```for (key, value) in skiplist.query(lessThanOrEqual: key)```
150 | * ```for (key, value) in skiplist.query(greaterThan: key)```
151 | * ```for (key, value) in skiplist.query(greaterThanOrEqual: key)```
152 | * ```for (key, value) in skiplist.query(from: key, to: key)```
153 | * ```for (key, value) in skiplist.query(from: key, through: key)```
154 |
155 | There is no "equalTo" because skiplist.search() already provides this functionality
156 | directly.
157 |
158 | These are all convenience functions on the general:
159 |
160 | * ```for (key value) in skiplist.query(min: key, max: key, minEquals: Bool, maxEquals: Bool)```
161 |
162 | The query object actually suports two mechanisms for walking the results, either the
163 | generator implied above, or the functions:
164 |
165 | * ```(key, value)? = query.first()```
166 | * ```(key, value)? = query.next()```
167 |
168 |
--------------------------------------------------------------------------------
/skiplists/main.swift:
--------------------------------------------------------------------------------
1 | //
2 | // main.swift
3 | // skiplists
4 | //
5 | // Created by Peter da Silva on 5/25/16.
6 | // Copyright © 2016 Flightaware. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | print("Basic skiplist test")
12 |
13 | let l = SkipList(maxLevel: 20)
14 |
15 | print("\nPopulating list.")
16 | l.insert("hello", value: "I say hello")
17 | l.insert("goodbye", value: "You say goodbye")
18 | l.insert("yes", value: "I say yes")
19 | l.insert("no", value: "You say no")
20 | l.insert("high", value: "I say high")
21 | l.insert("low", value: "you say low")
22 | l.insert("stop", value: "You say stop")
23 | l.insert("go", value: "I say go go go")
24 | l.insert("hello", value: "Hello my baby")
25 | l.insert("hello", value: "Hello my honey")
26 | l.insert("hello", value: "Hello my ragtime gal")
27 | l.insert("goodbye", value: "Goodnight America, and all the ships at sea")
28 | print("Dumping list:")
29 | for (key, value) in l {
30 | print(" \(key): \(value)")
31 | }
32 |
33 | func delete_all(l: SkipList, key: String) {
34 | for val in l.search(equalTo: key) {
35 | print("Deleting \((key, val))")
36 | l.delete(key, value: val)
37 | }
38 | }
39 |
40 | print("Delete test")
41 | delete_all(l, key: "high")
42 | delete_all(l, key: "low")
43 | delete_all(l, key: "goodbye")
44 |
45 | print("Dumping list:")
46 | for (key, value) in l {
47 | print("\(key): \(value)")
48 | }
49 |
50 | print("\n\nSpeedtables test")
51 |
52 | let t = Table(maxLevel: 20)
53 |
54 | print("Adding cartoon characters")
55 | t.insert("Nick", age: 32) // "200 dollars a day since I was twelve"
56 | t.insert("Judy", age: 22) // Guess
57 | t.insert("chip", age: 5) // How old can a chipmunk be?
58 | t.insert("dale", age: 5)
59 |
60 | // Just use a fixed year for repeatability.
61 | let year = 2016
62 | t.insert("mickey", age: year - 1928) // Steamboat Willie
63 | t.insert("bugs", age: year - 1940) // A Wild Hare
64 |
65 | print("Looking for 5 year olds")
66 | for row in t.ageIndex.search(equalTo: 5) {
67 | print("Name: \(row.name), age: \(row.age)")
68 | }
69 |
70 | print("Change chip's age to 6,, and make sure there is only one chip")
71 | let chips: [TableRow] = t.nameIndex.search(equalTo: "chip")
72 | if(chips.count == 1) {
73 | chips[0].age = 6
74 | } else {
75 | print("OOPS! chips.count(\(chips.count)) should be 1")
76 | }
77 |
78 | print("Adding more cartoon characters")
79 | t.insert("gadget", age: 5)
80 | t.insert("monty", age: 8)
81 |
82 | print("Adding dwarves. Dwarves are really old")
83 | t.insert("happy", age: 500)
84 | t.insert("sleepy", age: 500)
85 | t.insert("grumpy", age: 500)
86 |
87 | print("Adding vampires. They're really old too, and some have multiple ages")
88 | t.insert("lestat", age: 500)
89 | t.insert("dracula", age: 500)
90 | t.insert("dracula", age: year - 1897) // Book
91 | t.insert("dracula", age: year - 1931) // Bella Lugosi
92 | t.insert("dracula", age: year - 1958) // Christopher Lee
93 | t.insert("dracula", age: year - 1966) // ""
94 | t.insert("dracula", age: year - 1970) // ""
95 | t.insert("dracula", age: year - 1979) // Frank langella
96 | t.insert("dracula", age: year - 1992) // Gary Oldman
97 |
98 | print("Wait on, we can't have 500 year old schoolkids!")
99 | for row in t.ageIndex.search(equalTo: 500) {
100 | print("Deleting impossible entry \(row.name), \(row.age)")
101 | t.delete(row)
102 | }
103 |
104 | print("Walking nameIndex:")
105 | var lastName = ""
106 | for (key, row) in t.nameIndex {
107 | if key != lastName {
108 | print(" Key: \(key)")
109 | lastName = key
110 | }
111 | print(" Name: \(row.name), age: \(row.age)")
112 | }
113 | print("Walking ageIndex:")
114 | var lastAge = -1
115 | for (key, row) in t.ageIndex {
116 | if key != lastAge {
117 | print(" Key: \(key)")
118 | lastAge = key
119 | }
120 | print(" Name: \(row.name), age: \(row.age)")
121 | }
122 |
123 | print("Queries...")
124 | print(" Query: age from: 8 to: 50 // not including 50")
125 | for (key, row) in t.ageIndex.query(from: 8, to: 50) {
126 | print(" Name: \(row.name), age: \(row.age)")
127 | }
128 | print(" Query: age from: 8 through: 50 // including 50")
129 | for (key, row) in t.ageIndex.query(from: 8, through: 50) {
130 | print(" Name: \(row.name), age: \(row.age)")
131 | }
132 | print(" Query: name from: \"A\" through: \"Z~\")")
133 | for (key, row) in t.nameIndex.query(from: "A", through: "Z~") {
134 | print(" Name: \(row.name), age: \(row.age)")
135 | }
136 |
137 | print("Unique and optional columns")
138 | var nextID = 627846
139 | for (key, row) in t.nameIndex {
140 | try row.setStudentID("CC\(nextID)")
141 | nextID += 1
142 | }
143 | print("Initial student IDs")
144 | for (key, row) in t.studentIDIndex {
145 | print("Name: \(row.name) ID: \(key)")
146 | }
147 | print("Setting dracula to XXXXXXXX - should have several failures")
148 | for row in t.nameIndex.search(equalTo: "dracula") {
149 | do {
150 | try row.setStudentID("XXXXXXXX")
151 | } catch {
152 | print(error)
153 | }
154 | }
155 | print("Setting dracula to nil - should always succeed")
156 | for row in t.nameIndex.search(equalTo: "dracula") {
157 | do {
158 | try row.setStudentID(nil)
159 | } catch {
160 | print(error)
161 | }
162 | }
163 | print("Deleting rats and mice (fully indexed)")
164 | for row in t.nameIndex.search(equalTo: "monty") { t.delete(row) }
165 | for row in t.nameIndex.search(equalTo: "gadget") { t.delete(row) }
166 | for row in t.nameIndex.search(equalTo: "mickey") { t.delete(row) }
167 | print("Deleting young draculas (no Student ID index)")
168 | var rows: [TableRow] = t.nameIndex.search(equalTo: "dracula")
169 | for row in rows {
170 | if row.age < 50 {
171 | t.delete(row)
172 | }
173 | }
174 | print("Final entries")
175 | for (key, row) in t.nameIndex {
176 | print("Name: \(key), Age: \(row.age), ID: \(row.getStudentID())")
177 | }
178 | print("Final student IDs")
179 | for (key, row) in t.studentIDIndex {
180 | print("Name: \(row.name) ID: \(key)")
181 | }
182 |
183 | print("\nSpeed test")
184 |
185 | func randomString(length: Int = 6) -> String {
186 | enum Statics {
187 | static let letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ".characters.map {String($0)}
188 | }
189 | var string = ""
190 | for _ in 0.. Int {
197 | let t0 = clock()
198 | var tLast = t0 - t0
199 | for i in 1...1000000 {
200 | let name = randomString(6)
201 | if i % 100000 == 0 {
202 | let tNext = clock() - t0
203 | print("Not inserting \(name), \(i) at \(Int(tNext) - Int(tLast))µs")
204 | tLast = tNext
205 | }
206 | }
207 | let tFinal = clock() - t0
208 | print("Total for fake \(tFinal)µs")
209 | return Int(tFinal)
210 | }
211 | let overhead = forfake()
212 | func forskiplists() -> Int {
213 | let t0 = clock()
214 | var tLast = t0 - t0
215 | for i in 1...1000000 {
216 | let name = randomString(6)
217 | if i % 100000 == 0 {
218 | let tNext = clock() - t0
219 | print("Inserting \(name), \(i) at \(Int(tNext) - Int(tLast))µs")
220 | tLast = tNext
221 | }
222 | l.insert(name, value: String(i))
223 | }
224 | let tFinal = clock() - t0
225 | print("Total for skiplists \(tFinal)µs")
226 | return Int(tFinal)
227 | }
228 | let skiplists = forskiplists()
229 | print("Skiplists delta: \(skiplists - overhead)µs, \(((skiplists - overhead) / overhead) * 100)%")
230 | func forspeedtables() -> Int {
231 | let t0 = clock()
232 | var tLast = t0 - t0
233 | for i in 1...1000000 {
234 | let name = randomString(6)
235 | if i % 100000 == 0 {
236 | let tNext = clock() - t0
237 | print("Inserting \(name), \(i) at \(Int(tNext) - Int(tLast))µs")
238 | tLast = tNext
239 | }
240 | t.insert(name, age: i)
241 | }
242 | let tFinal = clock() - t0
243 | print("Total for speedtables \(tFinal)µs")
244 | return Int(tFinal)
245 | }
246 | let speedtables = forspeedtables()
247 | print("Speedtables delta: \(speedtables - overhead)µs, \(((speedtables - overhead) / overhead) * 100)%")
248 |
249 |
--------------------------------------------------------------------------------
/skiplists/skiplists.swift:
--------------------------------------------------------------------------------
1 | //
2 | // skiplists.swift
3 | // skiplists
4 | //
5 | // Created by Peter da Silva on 5/25/16.
6 | // Copyright © 2016 Flightaware. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | let randomProbability = 0.5
12 |
13 | func SkipListRandomLevel(maxLevel: Int) -> Int {
14 | var newLevel = 1
15 | while drand48() < randomProbability && newLevel < maxLevel {
16 | newLevel += 1
17 | }
18 | return newLevel
19 | }
20 |
21 | func SkipListMaxLevel(maxNodes: Int) -> Int {
22 | let logMaxNodes = log(Double(maxNodes)) / log(1.0 / randomProbability)
23 | return Int(round(logMaxNodes))
24 | }
25 |
26 | class SLNode {
27 | let key: Key?
28 | var values: [Value]
29 | var level: Int
30 | var next: [SLNode?]
31 | init(_ key: Key?, value: Value? = nil, maxLevel: Int, level: Int = 0) {
32 | self.key = key
33 | if let v = value {
34 | self.values = [v]
35 | } else {
36 | self.values = []
37 | }
38 | self.level = (level > 0) ? level : SkipListRandomLevel(maxLevel)
39 | self.next = Array?>(count: maxLevel, repeatedValue: nil)
40 | }
41 | }
42 |
43 | public class SkipList: SequenceType {
44 | let head: SLNode
45 | var maxLevel: Int
46 | var level: Int
47 |
48 | public init(maxLevel: Int) {
49 | self.maxLevel = maxLevel
50 | self.level = 1
51 | self.head = SLNode(nil, maxLevel: maxLevel, level: maxLevel)
52 | }
53 |
54 | public convenience init(maxNodes: Int) {
55 | self.init(maxLevel: SkipListMaxLevel(maxNodes))
56 | }
57 |
58 | func search(greaterThanOrEqualTo key: Key) -> SLNode? {
59 | var x = head
60 |
61 | // look for the key
62 | for i in (1 ... self.level).reverse() {
63 | while let next = x.next[i-1] where next.key < key {
64 | x = next
65 | }
66 | }
67 |
68 | return x.next[0]
69 | }
70 |
71 | public func search(greaterThanOrEqualTo key: Key) -> [Value] {
72 | if let array = search(greaterThanOrEqualTo: key)?.values {
73 | return array
74 | } else {
75 | return []
76 | }
77 | }
78 |
79 | func search(equalTo key: Key) -> SLNode? {
80 | // Check for an exact match
81 | if let x: SLNode = search(greaterThanOrEqualTo: key) where x.key == key {
82 | return x
83 | } else {
84 | return nil
85 | }
86 | }
87 |
88 | public func exists(key: Key) -> Bool {
89 | let x: SLNode? = search(equalTo: key)
90 | return x != nil
91 | }
92 |
93 | public func exists(key: Key, value: Value) -> Bool {
94 | if let array = search(equalTo: key)?.values {
95 | return array.contains(value)
96 | }
97 | return false;
98 | }
99 |
100 | public func search(equalTo key: Key) -> [Value] {
101 | if let array = search(equalTo: key)?.values {
102 | return array
103 | } else {
104 | return []
105 | }
106 | }
107 |
108 | // Replace an entry in a skiplist - for optional keys
109 | public func replace(newKey: Key?, inout keyStore: Key?, value: Value) {
110 | // no change - no work
111 | if newKey == keyStore {
112 | return
113 | }
114 |
115 | // showtime -- remove the old entry, update the keystore, insert the new value
116 | if let k = keyStore {
117 | delete(k, value: value)
118 | }
119 | keyStore = newKey
120 | if let k = newKey {
121 | insert(k, value: value)
122 | }
123 | }
124 |
125 | // Replace an entry in a skiplist - for non-optional keys
126 | public func replace(newKey: Key, inout keyStore: Key, value: Value) {
127 | // no change - no work
128 | if newKey == keyStore {
129 | return
130 | }
131 |
132 | // showtime -- remove the old entry, update the keystore, insert the new value
133 | delete(keyStore, value: value)
134 | keyStore = newKey
135 | insert(keyStore, value: value)
136 | }
137 |
138 | public func insert(key: Key, value newValue: Value) {
139 | var update = Array?>(count: maxLevel, repeatedValue: nil)
140 | var x = head
141 | var i: Int
142 |
143 | // look for the key, and save the previous nodes all the way down in the update[] list
144 | i = self.level
145 | while i >= 1 {
146 | while let next = x.next[i-1] where next.key < key {
147 | x = next
148 | }
149 | update[i-1] = x
150 | i -= 1
151 | }
152 |
153 | // If we haven't run off the end...
154 | if let next = x.next[0] {
155 | x = next
156 |
157 | // If we're looking at the right key already, then there's nothing to insert. Just add
158 | // the new value to the values array.
159 | if x.key == key {
160 | if x.values.contains(newValue) {
161 | return
162 | }
163 |
164 | x.values += [newValue]
165 |
166 | return
167 | }
168 | }
169 |
170 | // Pick a random level for the new node
171 | let level = SkipListRandomLevel(maxLevel)
172 |
173 | // If the new node is higher than the current level, fill up the update[] list
174 | // with head
175 | while level > self.level {
176 | self.level += 1
177 | update[self.level-1] = self.head
178 | }
179 |
180 | // make a new node and patch it in to the saved nodes in the update[] list
181 | let newNode = SLNode(key, value: newValue, maxLevel: maxLevel, level: level)
182 | i = 1
183 | while i <= level {
184 | newNode.next[i-1] = update[i-1]!.next[i-1]
185 | update[i-1]!.next[i-1] = newNode
186 | i += 1
187 | }
188 | }
189 |
190 | public func delete(key: Key, value: Value) -> Bool {
191 | var update = Array?>(count: maxLevel, repeatedValue: nil)
192 | var x = head
193 | var i: Int
194 |
195 | // look for the key, and save the previous nodes all the way down in the update[] list
196 | i = self.level
197 | while i >= 1 {
198 | while let next = x.next[i-1] where next.key < key {
199 | x = next
200 | }
201 | update[i-1] = x
202 | i -= 1
203 | }
204 |
205 | // check if run off end of list, nothing to do,
206 | guard let next = x.next[0] else {
207 | return false
208 | }
209 |
210 | // Point to the node we're maybe going to delete, if it matches
211 | x = next
212 |
213 | // Look for a key match
214 | if x.key != key {
215 | return false
216 | }
217 |
218 | // look for match in values
219 | guard let foundIndex = x.values.indexOf(value) else {
220 | // If we didn't find a matching value, we didn't actually find a match
221 | return false
222 | }
223 |
224 | // Remove the value we found, and if it wasn't the last one return success
225 | x.values.removeAtIndex(foundIndex)
226 | if(x.values.count > 0) {
227 | return true
228 | }
229 |
230 | // Now we've found a value, deleted it, and emptied the values list, we can delete this whole node
231 |
232 | // point all the previous node to the new next node
233 | i = 1
234 | while i <= self.level {
235 | if update[i-1]!.next[i-1] != nil && update[i-1]!.next[i-1] !== x {
236 | break
237 | }
238 | update[i-1]!.next[i-1] = x.next[i-1]
239 | i += 1
240 | }
241 |
242 | // if that was the biggest node, and we can see the end of the list from the head,
243 | // lower the list until we're pointing at a node
244 | while self.level > 1 && self.head.next[self.level-1] == nil {
245 | self.level -= 1
246 | }
247 |
248 | return true
249 | }
250 |
251 | public func generate() -> AnyGenerator<(Key, Value)> {
252 | var row = head
253 | var index = -1
254 |
255 | return AnyGenerator<(Key, Value)> {
256 | if index < 0 || index >= row.values.count {
257 | repeat {
258 | guard let next = row.next[0] else { return nil }
259 | row = next
260 | } while row.values.count == 0
261 | index = 0
262 | }
263 | let next = row.values[index]
264 | index += 1
265 | return (row.key!, next)
266 | }
267 | }
268 |
269 | func query(from start: Key?, through end: Key?) -> Query {
270 | return query(min: start, max: end, minEqual: true, maxEqual: true)
271 | }
272 |
273 | func query(from start: Key?, to end: Key?) -> Query {
274 | return query(min: start, max: end, minEqual: true, maxEqual: false)
275 | }
276 |
277 | func query(greaterThanOrEqual key: Key?) -> Query {
278 | return query(min: key, minEqual: true)
279 | }
280 |
281 | func query(greaterThan key: Key?) -> Query {
282 | return query(min: key, minEqual: false)
283 | }
284 |
285 | func query(lessThanOrEqual key: Key?) -> Query {
286 | return query(max: key, maxEqual: true)
287 | }
288 |
289 | func query(lessThan key: Key?) -> Query {
290 | return query(max: key, maxEqual: false)
291 | }
292 |
293 | func query(min min: Key? = nil, max: Key? = nil, minEqual: Bool = false, maxEqual: Bool = false) -> Query {
294 | return Query(list: self, min: min, max: max, minEqual: minEqual, maxEqual: maxEqual)
295 | }
296 | }
297 |
--------------------------------------------------------------------------------
/skiplists.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 9B2ECA7A1D3FFF820061DE12 /* skiplists.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE496AE1CF63082002D643C /* skiplists.swift */; };
11 | 9BE496A81CF63053002D643C /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE496A71CF63053002D643C /* main.swift */; };
12 | 9BE496B31CF8ACC5002D643C /* speedtable-example.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE496B21CF8ACC5002D643C /* speedtable-example.swift */; };
13 | 9BE496B51CFDC663002D643C /* queries.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE496B41CFDC663002D643C /* queries.swift */; };
14 | 9BE496B71CFE29F0002D643C /* speedtables.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9BE496B61CFE29F0002D643C /* speedtables.swift */; };
15 | /* End PBXBuildFile section */
16 |
17 | /* Begin PBXCopyFilesBuildPhase section */
18 | 9BE496A21CF63053002D643C /* CopyFiles */ = {
19 | isa = PBXCopyFilesBuildPhase;
20 | buildActionMask = 2147483647;
21 | dstPath = /usr/share/man/man1/;
22 | dstSubfolderSpec = 0;
23 | files = (
24 | );
25 | runOnlyForDeploymentPostprocessing = 1;
26 | };
27 | /* End PBXCopyFilesBuildPhase section */
28 |
29 | /* Begin PBXFileReference section */
30 | 9BE496A41CF63053002D643C /* skiplists */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = skiplists; sourceTree = BUILT_PRODUCTS_DIR; };
31 | 9BE496A71CF63053002D643C /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = ""; };
32 | 9BE496AE1CF63082002D643C /* skiplists.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = skiplists.swift; sourceTree = ""; };
33 | 9BE496B21CF8ACC5002D643C /* speedtable-example.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "speedtable-example.swift"; sourceTree = ""; };
34 | 9BE496B41CFDC663002D643C /* queries.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = queries.swift; sourceTree = ""; };
35 | 9BE496B61CFE29F0002D643C /* speedtables.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = speedtables.swift; sourceTree = ""; };
36 | /* End PBXFileReference section */
37 |
38 | /* Begin PBXFrameworksBuildPhase section */
39 | 9BE496A11CF63053002D643C /* Frameworks */ = {
40 | isa = PBXFrameworksBuildPhase;
41 | buildActionMask = 2147483647;
42 | files = (
43 | );
44 | runOnlyForDeploymentPostprocessing = 0;
45 | };
46 | /* End PBXFrameworksBuildPhase section */
47 |
48 | /* Begin PBXGroup section */
49 | 9BE4969B1CF63053002D643C = {
50 | isa = PBXGroup;
51 | children = (
52 | 9BE496A61CF63053002D643C /* skiplists */,
53 | 9BE496A51CF63053002D643C /* Products */,
54 | );
55 | sourceTree = "";
56 | };
57 | 9BE496A51CF63053002D643C /* Products */ = {
58 | isa = PBXGroup;
59 | children = (
60 | 9BE496A41CF63053002D643C /* skiplists */,
61 | );
62 | name = Products;
63 | sourceTree = "";
64 | };
65 | 9BE496A61CF63053002D643C /* skiplists */ = {
66 | isa = PBXGroup;
67 | children = (
68 | 9BE496A71CF63053002D643C /* main.swift */,
69 | 9BE496AE1CF63082002D643C /* skiplists.swift */,
70 | 9BE496B21CF8ACC5002D643C /* speedtable-example.swift */,
71 | 9BE496B41CFDC663002D643C /* queries.swift */,
72 | 9BE496B61CFE29F0002D643C /* speedtables.swift */,
73 | );
74 | path = skiplists;
75 | sourceTree = "";
76 | };
77 | /* End PBXGroup section */
78 |
79 | /* Begin PBXNativeTarget section */
80 | 9BE496A31CF63053002D643C /* skiplists */ = {
81 | isa = PBXNativeTarget;
82 | buildConfigurationList = 9BE496AB1CF63053002D643C /* Build configuration list for PBXNativeTarget "skiplists" */;
83 | buildPhases = (
84 | 9BE496A01CF63053002D643C /* Sources */,
85 | 9BE496A11CF63053002D643C /* Frameworks */,
86 | 9BE496A21CF63053002D643C /* CopyFiles */,
87 | );
88 | buildRules = (
89 | );
90 | dependencies = (
91 | );
92 | name = skiplists;
93 | productName = skiplists;
94 | productReference = 9BE496A41CF63053002D643C /* skiplists */;
95 | productType = "com.apple.product-type.tool";
96 | };
97 | /* End PBXNativeTarget section */
98 |
99 | /* Begin PBXProject section */
100 | 9BE4969C1CF63053002D643C /* Project object */ = {
101 | isa = PBXProject;
102 | attributes = {
103 | LastSwiftUpdateCheck = 0730;
104 | LastUpgradeCheck = 0730;
105 | ORGANIZATIONNAME = Flightaware;
106 | TargetAttributes = {
107 | 9BE496A31CF63053002D643C = {
108 | CreatedOnToolsVersion = 7.3.1;
109 | LastSwiftMigration = 0800;
110 | };
111 | };
112 | };
113 | buildConfigurationList = 9BE4969F1CF63053002D643C /* Build configuration list for PBXProject "skiplists" */;
114 | compatibilityVersion = "Xcode 3.2";
115 | developmentRegion = English;
116 | hasScannedForEncodings = 0;
117 | knownRegions = (
118 | en,
119 | );
120 | mainGroup = 9BE4969B1CF63053002D643C;
121 | productRefGroup = 9BE496A51CF63053002D643C /* Products */;
122 | projectDirPath = "";
123 | projectRoot = "";
124 | targets = (
125 | 9BE496A31CF63053002D643C /* skiplists */,
126 | );
127 | };
128 | /* End PBXProject section */
129 |
130 | /* Begin PBXSourcesBuildPhase section */
131 | 9BE496A01CF63053002D643C /* Sources */ = {
132 | isa = PBXSourcesBuildPhase;
133 | buildActionMask = 2147483647;
134 | files = (
135 | 9BE496B31CF8ACC5002D643C /* speedtable-example.swift in Sources */,
136 | 9B2ECA7A1D3FFF820061DE12 /* skiplists.swift in Sources */,
137 | 9BE496B51CFDC663002D643C /* queries.swift in Sources */,
138 | 9BE496B71CFE29F0002D643C /* speedtables.swift in Sources */,
139 | 9BE496A81CF63053002D643C /* main.swift in Sources */,
140 | );
141 | runOnlyForDeploymentPostprocessing = 0;
142 | };
143 | /* End PBXSourcesBuildPhase section */
144 |
145 | /* Begin XCBuildConfiguration section */
146 | 9BE496A91CF63053002D643C /* Debug */ = {
147 | isa = XCBuildConfiguration;
148 | buildSettings = {
149 | ALWAYS_SEARCH_USER_PATHS = NO;
150 | CLANG_ANALYZER_NONNULL = YES;
151 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
152 | CLANG_CXX_LIBRARY = "libc++";
153 | CLANG_ENABLE_MODULES = YES;
154 | CLANG_ENABLE_OBJC_ARC = YES;
155 | CLANG_WARN_BOOL_CONVERSION = YES;
156 | CLANG_WARN_CONSTANT_CONVERSION = YES;
157 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
158 | CLANG_WARN_EMPTY_BODY = YES;
159 | CLANG_WARN_ENUM_CONVERSION = YES;
160 | CLANG_WARN_INT_CONVERSION = YES;
161 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
162 | CLANG_WARN_UNREACHABLE_CODE = YES;
163 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
164 | CODE_SIGN_IDENTITY = "-";
165 | COPY_PHASE_STRIP = NO;
166 | DEBUG_INFORMATION_FORMAT = dwarf;
167 | ENABLE_STRICT_OBJC_MSGSEND = YES;
168 | ENABLE_TESTABILITY = YES;
169 | GCC_C_LANGUAGE_STANDARD = gnu99;
170 | GCC_DYNAMIC_NO_PIC = NO;
171 | GCC_NO_COMMON_BLOCKS = YES;
172 | GCC_OPTIMIZATION_LEVEL = s;
173 | GCC_PREPROCESSOR_DEFINITIONS = (
174 | "DEBUG=1",
175 | "$(inherited)",
176 | );
177 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
178 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
179 | GCC_WARN_UNDECLARED_SELECTOR = YES;
180 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
181 | GCC_WARN_UNUSED_FUNCTION = YES;
182 | GCC_WARN_UNUSED_VARIABLE = YES;
183 | MACOSX_DEPLOYMENT_TARGET = 10.11;
184 | MTL_ENABLE_DEBUG_INFO = YES;
185 | ONLY_ACTIVE_ARCH = YES;
186 | SDKROOT = macosx;
187 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
188 | };
189 | name = Debug;
190 | };
191 | 9BE496AA1CF63053002D643C /* Release */ = {
192 | isa = XCBuildConfiguration;
193 | buildSettings = {
194 | ALWAYS_SEARCH_USER_PATHS = NO;
195 | CLANG_ANALYZER_NONNULL = YES;
196 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
197 | CLANG_CXX_LIBRARY = "libc++";
198 | CLANG_ENABLE_MODULES = YES;
199 | CLANG_ENABLE_OBJC_ARC = YES;
200 | CLANG_WARN_BOOL_CONVERSION = YES;
201 | CLANG_WARN_CONSTANT_CONVERSION = YES;
202 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
203 | CLANG_WARN_EMPTY_BODY = YES;
204 | CLANG_WARN_ENUM_CONVERSION = YES;
205 | CLANG_WARN_INT_CONVERSION = YES;
206 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
207 | CLANG_WARN_UNREACHABLE_CODE = YES;
208 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
209 | CODE_SIGN_IDENTITY = "-";
210 | COPY_PHASE_STRIP = NO;
211 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
212 | ENABLE_NS_ASSERTIONS = NO;
213 | ENABLE_STRICT_OBJC_MSGSEND = YES;
214 | GCC_C_LANGUAGE_STANDARD = gnu99;
215 | GCC_NO_COMMON_BLOCKS = YES;
216 | GCC_OPTIMIZATION_LEVEL = s;
217 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
218 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
219 | GCC_WARN_UNDECLARED_SELECTOR = YES;
220 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
221 | GCC_WARN_UNUSED_FUNCTION = YES;
222 | GCC_WARN_UNUSED_VARIABLE = YES;
223 | MACOSX_DEPLOYMENT_TARGET = 10.11;
224 | MTL_ENABLE_DEBUG_INFO = NO;
225 | SDKROOT = macosx;
226 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
227 | };
228 | name = Release;
229 | };
230 | 9BE496AC1CF63053002D643C /* Debug */ = {
231 | isa = XCBuildConfiguration;
232 | buildSettings = {
233 | CLANG_USE_OPTIMIZATION_PROFILE = YES;
234 | PRODUCT_NAME = "$(TARGET_NAME)";
235 | SWIFT_VERSION = 2.3;
236 | };
237 | name = Debug;
238 | };
239 | 9BE496AD1CF63053002D643C /* Release */ = {
240 | isa = XCBuildConfiguration;
241 | buildSettings = {
242 | CLANG_USE_OPTIMIZATION_PROFILE = YES;
243 | PRODUCT_NAME = "$(TARGET_NAME)";
244 | SWIFT_VERSION = 2.3;
245 | };
246 | name = Release;
247 | };
248 | /* End XCBuildConfiguration section */
249 |
250 | /* Begin XCConfigurationList section */
251 | 9BE4969F1CF63053002D643C /* Build configuration list for PBXProject "skiplists" */ = {
252 | isa = XCConfigurationList;
253 | buildConfigurations = (
254 | 9BE496A91CF63053002D643C /* Debug */,
255 | 9BE496AA1CF63053002D643C /* Release */,
256 | );
257 | defaultConfigurationIsVisible = 0;
258 | defaultConfigurationName = Release;
259 | };
260 | 9BE496AB1CF63053002D643C /* Build configuration list for PBXNativeTarget "skiplists" */ = {
261 | isa = XCConfigurationList;
262 | buildConfigurations = (
263 | 9BE496AC1CF63053002D643C /* Debug */,
264 | 9BE496AD1CF63053002D643C /* Release */,
265 | );
266 | defaultConfigurationIsVisible = 0;
267 | defaultConfigurationName = Release;
268 | };
269 | /* End XCConfigurationList section */
270 | };
271 | rootObject = 9BE4969C1CF63053002D643C /* Project object */;
272 | }
273 |
--------------------------------------------------------------------------------