├── 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 | --------------------------------------------------------------------------------