├── .gitignore ├── .swiftpm └── xcode │ └── package.xcworkspace │ └── contents.xcworkspacedata ├── .travis.yml ├── HAMT.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── HAMT-iOS.xcscheme │ ├── HAMT-macOS.xcscheme │ ├── HAMT-tvOS.xcscheme │ ├── PD4UnitTests.xcscheme │ ├── PD5UnitTests.xcscheme │ └── PerfTool.xcscheme ├── HAMT ├── BetterLocalityConcept1Failed │ ├── PD5Bucket256.swift │ ├── PD5CompressedTable256.swift │ └── PD5Slot256.swift ├── BetterLocalityConcept2WorksLittle │ ├── CT64A.swift │ ├── CT64B.swift │ ├── DefaultProtocol.swift │ └── ValuePool.swift ├── HAMT.swift ├── LegacyNaiveHashTrie │ ├── PD4.swift │ ├── PD4BinaryExponentiation.swift │ ├── PD4BitArray.swift │ ├── PD4Bucket.preset.swift │ ├── PD4Bucket.swift │ ├── PD4BucketConfig.swift │ ├── PD4BucketRef.swift │ ├── PD4BucketSlot.swift │ ├── PD4FixedSizedArray.swift │ ├── PD4HashBitPath.swift │ └── PD4Hashable.swift ├── PD5Bucket.swift ├── PD5Bucket64.swift ├── PD5BucketConfig.swift ├── PD5CompressedTable64.swift ├── PD5Hashable.swift ├── PD5ImmutableArray.swift ├── PD5Iterator.swift ├── PD5Pair.swift └── PD5Slot64.swift ├── HAMTFuzz ├── main.swift └── util.swift ├── HAMTTests └── HAMTTests.swift ├── LICENSE.md ├── PD4UnitTests ├── Info.plist ├── Int.pd4.swift ├── PD4.utility.swift ├── PD4BucketMock1.swift ├── PD4BucketMock1BasedUnitTests.swift ├── PD4BucketMock2.swift ├── PD4BucketMock2BasedUnitTests.swift ├── PD4BucketMocklessUnitTests.swift ├── PD4Stat.swift └── Utilities.swift ├── PD5UnitTests ├── Info.plist ├── PD5CompressedTableUnitTests.swift ├── PD5IterationUnitTests.swift └── PD5UnitTests.swift ├── Package.swift ├── PerfTool ├── BTree │ └── Sources │ │ ├── BTree.swift │ │ ├── BTreeBuilder.swift │ │ ├── BTreeComparisons.swift │ │ ├── BTreeCursor.swift │ │ ├── BTreeIndex.swift │ │ ├── BTreeIterator.swift │ │ ├── BTreeMerger.swift │ │ ├── BTreeNode.swift │ │ ├── BTreePath.swift │ │ ├── BridgedList.swift │ │ ├── Compatibility.swift │ │ ├── Info.plist │ │ ├── List.swift │ │ ├── Map.swift │ │ ├── SortedBag.swift │ │ ├── SortedSet.swift │ │ └── Weak.swift ├── CRUD1.png ├── DB.swift ├── FormattingUtilities.swift ├── Get1.png ├── Perf.numbers └── main.swift ├── README.md └── test.zsh /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .build 3 | xcuserdata 4 | -------------------------------------------------------------------------------- /.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: swift 2 | os: osx 3 | osx_image: xcode11 4 | script: zsh test.zsh 5 | 6 | -------------------------------------------------------------------------------- /HAMT.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /HAMT.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /HAMT.xcodeproj/xcshareddata/xcschemes/HAMT-iOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /HAMT.xcodeproj/xcshareddata/xcschemes/HAMT-macOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 42 | 48 | 49 | 50 | 51 | 52 | 62 | 63 | 69 | 70 | 71 | 72 | 78 | 79 | 85 | 86 | 87 | 88 | 90 | 91 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /HAMT.xcodeproj/xcshareddata/xcschemes/HAMT-tvOS.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 52 | 53 | 59 | 60 | 66 | 67 | 68 | 69 | 71 | 72 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /HAMT.xcodeproj/xcshareddata/xcschemes/PD4UnitTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 16 | 18 | 24 | 25 | 26 | 27 | 28 | 38 | 39 | 45 | 46 | 48 | 49 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /HAMT.xcodeproj/xcshareddata/xcschemes/PD5UnitTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 40 | 41 | 47 | 48 | 50 | 51 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /HAMT.xcodeproj/xcshareddata/xcschemes/PerfTool.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /HAMT/BetterLocalityConcept1Failed/PD5Bucket256.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// PD5Bucket256.swift 3 | //// HAMT 4 | //// 5 | //// Created by Henry on 2019/05/23. 6 | //// 7 | // 8 | //private let hashBitCountPerLevel = UInt(8) // 2^8 = 256 bits. 9 | //private let hashBitMaskPerLevel = UInt(0b0_1111_1111) 10 | //private let maxLevelCount = UInt8(UInt(256) / hashBitCountPerLevel) 11 | //private let maxLevelIndex = UInt8(UInt(256) / hashBitCountPerLevel - 1) 12 | // 13 | ///// 14 | ///// A 256-bit HAMT node. 15 | ///// 16 | ///// One 256-bit HAMT node has 256 slots. There's no way to 17 | ///// adjust this size dynamically. 18 | ///// 19 | //struct PD5Bucket256 where K: PD5Hashable { 20 | // typealias Slot = PD5Slot256 21 | // typealias Pair = PD5Pair 22 | // private(set) var config = PD5BucketConfig() 23 | // /// Total count of all elements in this subtree. 24 | // private(set) var sum = 0 25 | // private(set) var slots = PD5CompressedTable256() 26 | // 27 | // @inlinable 28 | // @inline(__always) 29 | // init() {} 30 | // 31 | // @inline(__always) 32 | // private init(config x: PD5BucketConfig) { 33 | // precondition(x.level < maxLevelCount) 34 | // config = x 35 | // } 36 | // @inlinable 37 | // @inline(__always) 38 | // var count: Int { 39 | // return sum 40 | // } 41 | // @inlinable 42 | // @inline(__always) 43 | // subscript(_ k: K) -> V? { 44 | // get { 45 | // let h = k.hashBits 46 | // return find(h, k) 47 | // } 48 | // set(v) { 49 | // let h = k.hashBits 50 | // if let v = v { 51 | // insertOrReplace(h, k, v) 52 | // } 53 | // else { 54 | // removeOrIgnore(h, k) 55 | // } 56 | // } 57 | // } 58 | // 59 | // @inlinable 60 | // @inline(__always) 61 | // subscript(_ k: K, default defv: @autoclosure() -> V) -> V { 62 | // get { 63 | // let h = k.hashBits 64 | // return find(h, k) ?? defv() 65 | // } 66 | // set(v) { 67 | // let h = k.hashBits 68 | // insertOrReplace(h, k, v) 69 | // } 70 | // } 71 | // 72 | // @inlinable 73 | // @inline(__always) 74 | // func slotIndex(for h: UInt) -> UInt { 75 | // let h1 = h >> (hashBitCountPerLevel * UInt(config.level)) 76 | // let ik = h1 & hashBitMaskPerLevel 77 | // return ik 78 | // } 79 | // @inlinable 80 | // @inline(__always) 81 | // func find(_ h: UInt, _ k: K) -> V? { 82 | // // let ik = slotIndex(for: h) 83 | // // let slot = slots.get(index: ik, default: .none) 84 | // // switch slot { 85 | // // case .none: return nil 86 | // // case .unique(let kv): return kv.key == k ? kv.value : nil 87 | // // case .branch(let b): return b.find(h, k) 88 | // // case .leaf(let a): return a.first(where: { kv in kv.key == k })?.value 89 | // // } 90 | // return findWithPreshiftedHashBits(h, k) 91 | // } 92 | // @inline(__always) 93 | // private func findWithPreshiftedHashBits(_ h: UInt, _ k: K) -> V? { 94 | // var h1 = h 95 | // var b = self 96 | // while true { 97 | // let ik = h1 & hashBitMaskPerLevel 98 | // let slot = b.slots.get(index: ik) ?? .none 99 | // switch slot { 100 | // case .none: return nil 101 | // case .unique(let kv): return kv.key == k ? kv.value : nil 102 | // case .branch(let b1): 103 | // b = b1 104 | // h1 = h1 >> hashBitCountPerLevel 105 | // case .leaf(let a): return a.first(where: { kv in kv.key == k })?.value 106 | // } 107 | // } 108 | // } 109 | // 110 | // enum InsertOrReplaceResult { 111 | // case inserted 112 | // case replaced(V) 113 | // } 114 | // @inlinable 115 | // @inline(__always) 116 | // @discardableResult 117 | // mutating func insertOrReplace(_ h: UInt, _ k: K, _ v: V) -> InsertOrReplaceResult { 118 | // let ik = slotIndex(for: h) 119 | // let s = slots.get(index: ik) ?? .none 120 | // switch s { 121 | // case .none: 122 | // slots.set(index: ik, .unique(Pair(k,v))) 123 | // sum += 1 124 | // return .inserted 125 | // case .unique(let kv): 126 | // if kv.key == k { 127 | // // Replace. 128 | // slots.set(index: ik, .unique(Pair(k,v))) 129 | // return .replaced(kv.value) 130 | // } 131 | // else { 132 | // // Insert. 133 | // if config.level < maxLevelIndex { 134 | // // Branch down. 135 | // var x = config 136 | // x.level += 1 137 | // var b = PD5Bucket256(config: x) 138 | // b.insertOrReplace(kv.key.hashBits, kv.key, kv.value) // Take care that we need to pass correct hash here. 139 | // b.insertOrReplace(h, k, v) 140 | // slots.set(index: ik, .branch(b)) 141 | // } 142 | // else { 143 | // // Reached at max level. 144 | // // Put them into a leaf. 145 | // slots.set(index: ik, .leaf([kv, Pair(k,v)])) 146 | // } 147 | // sum += 1 148 | // return .inserted 149 | // } 150 | // case .branch(var b): 151 | // let r = b.insertOrReplace(h, k, v) 152 | // slots.set(index: ik, .branch(b)) 153 | // switch r { 154 | // case .inserted: sum += 1 155 | // case .replaced(_): break 156 | // } 157 | // return r 158 | // case .leaf(var a): 159 | // for i in a.indices { 160 | // if a[i].key == k { 161 | // // Replace. 162 | // let v1 = a[i].value 163 | // a[i].value = v 164 | // slots.set(index: ik, .leaf(a)) 165 | // return .replaced(v1) 166 | // } 167 | // } 168 | // // Insert. 169 | // a.append(Pair(k,v)) 170 | // slots.set(index: ik, .leaf(a)) 171 | // sum += 1 172 | // return .inserted 173 | // } 174 | // } 175 | // 176 | // enum RemoveOrIgnoreResult { 177 | // case removed(V) 178 | // case ignored 179 | // } 180 | // @inlinable 181 | // @inline(__always) 182 | // @discardableResult 183 | // mutating func removeOrIgnore(_ h: UInt, _ k: K) -> RemoveOrIgnoreResult { 184 | // let ik = slotIndex(for: h) 185 | // let s = slots.get(index: ik) ?? .none 186 | // switch s { 187 | // case .none: 188 | // return .ignored 189 | // case .unique(let kv): 190 | // if kv.key == k { 191 | // slots.set(index: ik, .none) 192 | // sum -= 1 193 | // return .removed(kv.value) 194 | // } 195 | // else { 196 | // return .ignored 197 | // } 198 | // case .branch(var b): 199 | // let r = b.removeOrIgnore(h, k) 200 | // switch r { 201 | // case .removed(let v): 202 | // sum -= 1 203 | // switch b.sum { 204 | // case 0: 205 | // slots.set(index: ik, .none) 206 | // case 1: 207 | // slots.set(index: ik, .unique(b.ADHOC_collectOne())) 208 | // default: 209 | // slots.set(index: ik, .branch(b)) 210 | // } 211 | // return .removed(v) 212 | // case .ignored: 213 | // return .ignored 214 | // } 215 | // case .leaf(var a): 216 | // if let i = a.firstIndex(where: { kv in kv.key == k }) { 217 | // let v = a[i].value 218 | // sum -= 1 219 | // a.remove(at: i) 220 | // switch a.count { 221 | // case 0: slots.set(index: ik, .none) 222 | // case 1: slots.set(index: ik, .unique(a[0])) 223 | // default: slots.set(index: ik, .leaf(a)) 224 | // } 225 | // return .removed(v) 226 | // } 227 | // else { 228 | // return .ignored 229 | // } 230 | // } 231 | // } 232 | // 233 | // private func ADHOC_collectOne() -> Pair { 234 | // precondition(sum == 1) 235 | // for s in slots { 236 | // switch s { 237 | // case .unique(let kv): return kv 238 | // default: break 239 | // } 240 | // } 241 | // fatalError() 242 | // } 243 | //} 244 | ////extension PD5Bucket256: Equatable where V: Equatable { 245 | //// static func == (_ a: PD5Bucket256, _ b: PD5Bucket256) -> Bool { 246 | //// guard a.count == b.count else { return false } 247 | //// for p in a.pairs { 248 | //// guard b[p.key] == p.value else { return false } 249 | //// } 250 | //// for p in b.pairs { 251 | //// guard a[p.key] == p.value else { return false } 252 | //// } 253 | //// return true 254 | //// } 255 | ////} 256 | //// 257 | -------------------------------------------------------------------------------- /HAMT/BetterLocalityConcept1Failed/PD5CompressedTable256.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// PD5CompressedTable256.swift 3 | //// HAMT 4 | //// 5 | //// Created by Henry on 2019/05/23. 6 | //// 7 | // 8 | //struct PD5CompressedTable256: Sequence { 9 | // typealias Element = T 10 | // typealias Table = PD5CompressedTable64 11 | // private var t0 = Table() 12 | // private var t1 = Table() 13 | // private var t2 = Table() 14 | // private var t3 = Table() 15 | // 16 | // private var bitmap = UInt64(0b0) 17 | // private var slots = ContiguousArray() 18 | // 19 | // @inlinable 20 | // @inline(__always) 21 | // var capacity: Int { 22 | // return 256 23 | // } 24 | // @inlinable 25 | // @inline(__always) 26 | // var count: Int { 27 | // return t0.count + t1.count + t2.count + t3.count 28 | // } 29 | // 30 | // typealias Iterator = FlattenSequence], PD5CompressedTable64>>.Iterator 31 | // @inlinable 32 | // @inline(__always) 33 | // func makeIterator() -> Iterator { 34 | // let s = [t0,t1,t2,t3].lazy.flatMap({ $0 }) 35 | // let it = s.makeIterator() 36 | // return it 37 | // } 38 | // 39 | // /// Incoming index-key is ending 8bit of hash bits. (0~255 range) 40 | // @inlinable 41 | // @inline(__always) 42 | // func get(index k: UInt) -> T? { 43 | // let m1 = 0b0_1100_0000 as UInt 44 | // let m2 = 0b0_0011_1111 as UInt 45 | // let a = k & m1 >> 6 46 | // let b = k & m2 47 | // switch a { 48 | // case 0: return t0.get1(index: b) 49 | // case 1: return t1.get1(index: b) 50 | // case 2: return t2.get1(index: b) 51 | // case 3: return t3.get1(index: b) 52 | // default: fatalError("Bad index-key bits.") 53 | // } 54 | //// switch k { 55 | //// case 0..<64: return t0.get(index: k - 0, default: defv()) 56 | //// case 64..<128: return t1.get(index: k - 64, default: defv()) 57 | //// case 128..<192: return t2.get(index: k - 128, default: defv()) 58 | //// case 192..<256: return t3.get(index: k - 192, default: defv()) 59 | //// default: fatalError("Bad index-key bits.") 60 | //// } 61 | // } 62 | // @inlinable 63 | // @inline(__always) 64 | // mutating func set(index k: UInt, _ v: T) { 65 | //// switch k { 66 | //// case 0..<64: t0.set(index: k - 0, v) 67 | //// case 64..<128: t1.set(index: k - 64, v) 68 | //// case 128..<192: t2.set(index: k - 128, v) 69 | //// case 192..<256: t3.set(index: k - 192, v) 70 | //// default: fatalError("Bad index-key bits.") 71 | //// } 72 | // let m1 = 0b0_1100_0000 as UInt 73 | // let m2 = 0b0_0011_1111 as UInt 74 | // let a = k & m1 >> 6 75 | // let b = k & m2 76 | // switch a { 77 | // case 0: t0.set(index: b, v) 78 | // case 1: t1.set(index: b, v) 79 | // case 2: t2.set(index: b, v) 80 | // case 3: t3.set(index: b, v) 81 | // default: fatalError("Bad index-key bits.") 82 | // } 83 | // } 84 | // @inlinable 85 | // @inline(__always) 86 | // mutating func unset(index k: UInt) { 87 | //// switch k { 88 | //// case 0..<64: t0.unset(index: k - 0) 89 | //// case 64..<128: t1.unset(index: k - 64) 90 | //// case 128..<192: t2.unset(index: k - 128) 91 | //// case 192..<256: t3.unset(index: k - 192) 92 | //// default: fatalError("Bad index-key bits.") 93 | //// } 94 | // let m1 = 0b0_1100_0000 as UInt 95 | // let m2 = 0b0_0011_1111 as UInt 96 | // let a = k & m1 >> 6 97 | // let b = k & m2 98 | // switch a { 99 | // case 0: t0.unset(index: b) 100 | // case 1: t1.unset(index: b) 101 | // case 2: t2.unset(index: b) 102 | // case 3: t3.unset(index: b) 103 | // default: fatalError("Bad index-key bits.") 104 | // } 105 | // } 106 | //} 107 | //extension PD5CompressedTable256: Equatable where T: Equatable {} 108 | -------------------------------------------------------------------------------- /HAMT/BetterLocalityConcept1Failed/PD5Slot256.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// PD5Slot256.swift 3 | //// PD5UnitTests 4 | //// 5 | //// Created by Henry on 2019/05/23. 6 | //// 7 | // 8 | //import Foundation 9 | // 10 | ///// Intended to be `indirect` to make slot 11 | ///// size small & regular as machine word size. 12 | //enum PD5Slot256 where K: PD5Hashable { 13 | // typealias Bucket = PD5Bucket256 14 | // typealias Pair = PD5Pair 15 | // case none 16 | // case unique(Pair) 17 | // case branch(Bucket) 18 | // /// - Note: 19 | // /// This needs double indirect jump. 20 | // /// As leaf node is for hash-collided 21 | // /// keys, I afford slowness here 22 | // /// for better performance of non-collided 23 | // /// keys. 24 | // case leaf(ContiguousArray) 25 | //} 26 | // 27 | ////extension PD5Slot256: Equatable where V: Equatable { 28 | //// static func == (_ a: PD5Slot256, _ b: PD5Slot256) -> Bool { 29 | //// switch (a, b) { 30 | //// case (.none, .none): return true 31 | //// case (.unique(let a1), .unique(let b1)): return a1 == b1 32 | //// case (.branch(let a1), .branch(let b1)): return a1 == b1 33 | //// case (.leaf(let a1), .leaf(let b1)): return a1 == b1 34 | //// default: return false 35 | //// } 36 | //// } 37 | ////} 38 | //// 39 | -------------------------------------------------------------------------------- /HAMT/BetterLocalityConcept2WorksLittle/CT64A.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// CT64A.swift 3 | //// HAMT 4 | //// 5 | //// Created by Henry on 2019/05/24. 6 | //// 7 | // 8 | //struct CT64A: Sequence where T: DefaultProtocol { 9 | // typealias Element = T 10 | // private var bitmap = UInt64(0b0) 11 | // private var slots = ContiguousArray(repeating: T.default, count: 64) 12 | // private var flag = false 13 | // 14 | // mutating func allocSlotSpace() { 15 | // flag = true 16 | // } 17 | // mutating func deallocSlotSpace() { 18 | // flag = false 19 | // } 20 | // 21 | // @inlinable 22 | // @inline(__always) 23 | // var capacity: Int { 24 | // return 64 25 | // } 26 | // @inlinable 27 | // @inline(__always) 28 | // var count: Int { 29 | // precondition(flag) 30 | // return bitmap.nonzeroBitCount 31 | // } 32 | // @inlinable 33 | // @inline(__always) 34 | // func makeIterator() -> ContiguousArray.Iterator { 35 | // precondition(flag) 36 | // return slots.makeIterator() 37 | // } 38 | // 39 | // @inlinable 40 | // @inline(__always) 41 | // func get(index k: UInt, default defv: @autoclosure() -> T) -> T { 42 | // precondition(flag) 43 | // assert(k < 64) 44 | // assert(0 <= k) 45 | // let mask = UInt64(0b1) << k 46 | // if (bitmap & mask).nonzeroBitCount == 0 { 47 | // // No value at index. 48 | // return defv() 49 | // } 50 | // else { 51 | // let countingMask = ~(UInt64(0xffff_ffff_ffff_ffff) << k) 52 | // let bitCount = (bitmap & countingMask).nonzeroBitCount 53 | // return slots[bitCount] 54 | // } 55 | // } 56 | // @inlinable 57 | // @inline(__always) 58 | // func get1(index k: UInt) -> T? { 59 | // precondition(flag) 60 | // assert(k < 64) 61 | // assert(0 <= k) 62 | // let mask = UInt64(0b1) << k 63 | // if (bitmap & mask).nonzeroBitCount == 0 { 64 | // // No value at index. 65 | // return nil 66 | // } 67 | // else { 68 | // let countingMask = ~(UInt64(0xffff_ffff_ffff_ffff) << k) 69 | // let bitCount = (bitmap & countingMask).nonzeroBitCount 70 | // return slots[bitCount] 71 | // } 72 | // } 73 | // 74 | // @inlinable 75 | // @inline(__always) 76 | // mutating func set(index k: UInt, _ v: T) { 77 | // precondition(flag) 78 | // assert(k < 64) 79 | // assert(0 <= k) 80 | // let mask = UInt64(0b1) << k 81 | // let countingMask = ~(UInt64(0xffff_ffff_ffff_ffff) << k) 82 | // let bitCount = (bitmap & countingMask).nonzeroBitCount 83 | // bitmap |= mask 84 | // slots[bitCount] = v 85 | // } 86 | // @inlinable 87 | // @inline(__always) 88 | // mutating func unset(index k: UInt) { 89 | // precondition(flag) 90 | // assert(k < 64) 91 | // assert(0 <= k) 92 | // let mask = UInt64(0b1) << k 93 | // let countingMask = ~(UInt64(0xffff_ffff_ffff_ffff) << k) 94 | // let bitCount = (bitmap & countingMask).nonzeroBitCount 95 | // slots[bitCount] = T.default 96 | // bitmap &= ~mask 97 | // } 98 | //} 99 | //extension CT64A: Equatable where T: Equatable {} 100 | // 101 | -------------------------------------------------------------------------------- /HAMT/BetterLocalityConcept2WorksLittle/CT64B.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// CT64B.swift 3 | //// HAMT 4 | //// 5 | //// Created by Henry on 2019/05/24. 6 | //// 7 | // 8 | //import Foundation 9 | // 10 | //struct CT64B: Sequence where T: DefaultProtocol { 11 | // typealias Element = T 12 | // private var bitmap = UInt64(0b0) 13 | // private var slots = ArraySlice.init() 14 | // private var flag = false 15 | // 16 | // mutating func allocSlotSpace() { 17 | // flag = true 18 | // slots = MultiTypeValuePool.shared[T.self].alloc() 19 | //// let uz = MemoryLayout.stride * 64 20 | //// slots = MultiSizeMemoryPool.shared[forUnitStrideInBytes: uz].alloc().bindMemory(to: T.self, capacity: 64) 21 | //// slots = Pool.shared.alloc() 22 | // } 23 | // mutating func deallocSlotSpace() { 24 | //// let uz = MemoryLayout.stride * 64 25 | //// MultiSizeMemoryPool.shared[forUnitStrideInBytes: uz].dealloc(slots) 26 | // MultiTypeValuePool.shared[T.self].dealloc(slots) 27 | // slots = ArraySlice.init() 28 | // flag = false 29 | // } 30 | // 31 | // @inlinable 32 | // @inline(__always) 33 | // var capacity: Int { 34 | // return 64 35 | // } 36 | // @inlinable 37 | // @inline(__always) 38 | // var count: Int { 39 | // precondition(flag) 40 | // return bitmap.nonzeroBitCount 41 | // } 42 | // @inlinable 43 | // @inline(__always) 44 | // func makeIterator() -> ContiguousArray.Iterator { 45 | // precondition(flag) 46 | // var a = ContiguousArray() 47 | // for i in 0..<64 { 48 | // if let v = get1(index: UInt(i)) { 49 | // a.append(v) 50 | // } 51 | // } 52 | // return a.makeIterator() 53 | // } 54 | // 55 | // @inlinable 56 | // @inline(__always) 57 | // func get(index k: UInt, default defv: @autoclosure() -> T) -> T { 58 | // precondition(flag) 59 | // assert(k < 64) 60 | // assert(0 <= k) 61 | // let mask = UInt64(0b1) << k 62 | // if (bitmap & mask).nonzeroBitCount == 0 { 63 | // // No value at index. 64 | // return defv() 65 | // } 66 | // else { 67 | // let countingMask = ~(UInt64(0xffff_ffff_ffff_ffff) << k) 68 | // let bitCount = (bitmap & countingMask).nonzeroBitCount 69 | // return slots[bitCount] 70 | // } 71 | // } 72 | // @inlinable 73 | // @inline(__always) 74 | // func get1(index k: UInt) -> T? { 75 | // precondition(flag) 76 | // assert(k < 64) 77 | // assert(0 <= k) 78 | // let mask = UInt64(0b1) << k 79 | // if (bitmap & mask).nonzeroBitCount == 0 { 80 | // // No value at index. 81 | // return nil 82 | // } 83 | // else { 84 | // let countingMask = ~(UInt64(0xffff_ffff_ffff_ffff) << k) 85 | // let bitCount = (bitmap & countingMask).nonzeroBitCount 86 | // return slots[bitCount] 87 | // } 88 | // } 89 | // 90 | // @inlinable 91 | // @inline(__always) 92 | // mutating func set(index k: UInt, _ v: T) { 93 | // precondition(flag) 94 | // assert(k < 64) 95 | // assert(0 <= k) 96 | // let mask = UInt64(0b1) << k 97 | // let countingMask = ~(UInt64(0xffff_ffff_ffff_ffff) << k) 98 | // let bitCount = (bitmap & countingMask).nonzeroBitCount 99 | // bitmap |= mask 100 | // slots[bitCount] = v 101 | // } 102 | // @inlinable 103 | // @inline(__always) 104 | // mutating func unset(index k: UInt) { 105 | // precondition(flag) 106 | // assert(k < 64) 107 | // assert(0 <= k) 108 | // let mask = UInt64(0b1) << k 109 | // let countingMask = ~(UInt64(0xffff_ffff_ffff_ffff) << k) 110 | // let bitCount = (bitmap & countingMask).nonzeroBitCount 111 | // slots[bitCount] = T.default 112 | // bitmap &= ~mask 113 | // 114 | // if count == 8 { 115 | // 116 | // } 117 | // } 118 | //} 119 | //extension CT64B: Equatable where T: Equatable {} 120 | // 121 | //final class Pool { 122 | // static let shared = Pool() 123 | // 124 | // private var ptr = UnsafeMutableRawPointer.allocate(byteCount: 1024*1024*1024, alignment: 0) 125 | // private var consumedBytes = 0 126 | // deinit { 127 | // ptr.deallocate() 128 | // } 129 | // func first() -> UnsafeMutablePointer { 130 | // let x = MemoryLayout.stride 131 | // let n = 1024*1024*1024 / x 132 | // let ptr1 = ptr.bindMemory(to: T.self, capacity: n) 133 | // return ptr1 134 | // } 135 | // func alloc() -> UnsafeMutablePointer { 136 | // let x = MemoryLayout.stride 137 | // let n = 1024*1024*1024 / x 138 | // let ptr1 = ptr.bindMemory(to: T.self, capacity: n) 139 | // let ptr2 = ptr1 + consumedBytes / x 140 | // consumedBytes += x * 64 141 | // return ptr2 142 | // } 143 | // func dealloc(_ a: UnsafeMutablePointer) { 144 | // 145 | // } 146 | //} 147 | // 148 | //final class MultiSizeMemoryPool { 149 | // static let dummy = UnsafeMutableRawPointer.allocate(byteCount: 128, alignment: 0) 150 | // static let shared = MultiSizeMemoryPool() 151 | // private var map = [Int: UnitMemoryPool]() 152 | // subscript(forUnitStrideInBytes uz: Int) -> UnitMemoryPool { 153 | // get { 154 | // return map[uz, default: UnitMemoryPool(unitStrideInBytes: uz, unitCount: 1024*1024)] 155 | // } 156 | // set(v) { 157 | // map[uz] = v.isEmpty ? nil : v 158 | // } 159 | // } 160 | //} 161 | // 162 | //final class UnitMemoryPool { 163 | // private let unitStrideInBytes: Int 164 | // private let unitCount: Int 165 | // private let ptr: UnsafeMutableRawPointer 166 | // private var freeIndices = IndexSet() 167 | // private let lock = NSLock() 168 | // init(unitStrideInBytes z: Int, unitCount c: Int) { 169 | // unitStrideInBytes = z 170 | // unitCount = c 171 | // ptr = UnsafeMutableRawPointer.allocate(byteCount: z*c, alignment: z) 172 | // freeIndices.insert(integersIn: 0.. UnsafeMutableRawPointer { 181 | // lock.lock() 182 | // defer { lock.unlock() } 183 | // guard let i = freeIndices.first else { fatalError("No more space.") } 184 | // let offset = i * unitStrideInBytes 185 | // let ptr1 = ptr.advanced(by: offset) 186 | // return ptr1 187 | // } 188 | // func dealloc(_ ptr1: UnsafeMutableRawPointer) { 189 | // let dt = ptr1 - ptr 190 | // let i = dt / unitStrideInBytes 191 | // lock.lock() 192 | // freeIndices.insert(i) 193 | // lock.unlock() 194 | // } 195 | //} 196 | -------------------------------------------------------------------------------- /HAMT/BetterLocalityConcept2WorksLittle/DefaultProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DefaultProtocol.swift 3 | // HAMT 4 | // 5 | // Created by Henry on 2019/05/24. 6 | // 7 | 8 | import Foundation 9 | 10 | protocol DefaultProtocol { 11 | static var `default`: Self { get } 12 | } 13 | -------------------------------------------------------------------------------- /HAMT/BetterLocalityConcept2WorksLittle/ValuePool.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// ValuePool.swift 3 | //// HAMT 4 | //// 5 | //// Created by Henry on 2019/05/24. 6 | //// 7 | // 8 | //import Foundation 9 | // 10 | //final class MultiTypeValuePool { 11 | // static let shared = MultiTypeValuePool() 12 | // 13 | // private var map = [ObjectIdentifier: AnyObject]() 14 | // subscript(_ :T.Type) -> ValuePool { 15 | // get { 16 | // return (map[ObjectIdentifier(T.self)] as! ValuePool?) 17 | // ?? ValuePool(elementCountInUnit: 64, unitCount: 1024*1024) 18 | // } 19 | // } 20 | //} 21 | //final class ValuePool where T: DefaultProtocol { 22 | // private let elementCountInUnit: Int 23 | // private let unitCount: Int 24 | // private var arr: ContiguousArray 25 | // private var freeIndices = IndexSet() 26 | // private let lock = NSLock() 27 | // init(elementCountInUnit z: Int, unitCount c: Int) { 28 | // elementCountInUnit = z 29 | // unitCount = c 30 | // arr = ContiguousArray(repeating: .default, count: z*c) 31 | // freeIndices.insert(integersIn: 0.. ArraySlice { 39 | // lock.lock() 40 | // let i = freeIndices.first 41 | // lock.unlock() 42 | // guard let i1 = i else { fatalError("No more space.") } 43 | // let start = i1 * elementCountInUnit 44 | // let end = start + elementCountInUnit 45 | // let slice = arr[start..) { 49 | // let i = s.startIndex / elementCountInUnit 50 | // lock.lock() 51 | // freeIndices.insert(i) 52 | // lock.unlock() 53 | // } 54 | //} 55 | -------------------------------------------------------------------------------- /HAMT/HAMT.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HAMT.swift 3 | // HAMT 4 | // 5 | // Created by Henry on 2019/05/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /// 11 | /// HAMT(Hash Array Mapped Trie, Bagwell). 12 | /// 13 | /// `HAMT` provides near constant time (`O(10)`) performance up to 14 | /// hash resolution limit (`(2^6)^10` items) for read/write/copy regardless of item count 15 | /// where copying `Swift.Dictionary` takes linearly increased time. 16 | /// 17 | /// Base read performance of `HAMT` is about 2x-50x times slower 18 | /// than ephemeral `Swift.Dictionary`. 19 | /// 20 | public struct HAMT where Key: Hashable { 21 | private var root = PD5Bucket64,Value>() 22 | private var sum = 0 23 | 24 | @inlinable 25 | public init() {} 26 | 27 | public var isEmpty: Bool { 28 | return root.count == 0 29 | } 30 | 31 | public var count: Int { 32 | return sum 33 | } 34 | 35 | public subscript(_ key: Key) -> Value? { 36 | get { 37 | let k1 = PD5Key(key) 38 | return root[k1] 39 | } 40 | set(v) { 41 | let k1 = PD5Key(key) 42 | if let v = v { 43 | switch root.insertOrReplace(k1.hashBits, k1, v) { 44 | case .inserted: sum += 1 45 | case .replaced: break 46 | } 47 | } 48 | else { 49 | switch root.removeOrIgnore(k1.hashBits, k1) { 50 | case .removed: sum -= 1 51 | case .ignored: break 52 | } 53 | } 54 | } 55 | } 56 | public subscript(_ key: Key, default defaultValue: @autoclosure() -> Value) -> Value { 57 | get { return self[key] ?? defaultValue() } 58 | set(v) { self[key] = v } 59 | } 60 | } 61 | 62 | extension HAMT: Sequence { 63 | public func makeIterator() -> Iterator { 64 | let it = root.pairs.makeIterator() 65 | return Iterator(source: it) 66 | } 67 | public struct Iterator: IteratorProtocol { 68 | fileprivate private(set) var source: PD5Bucket64,Value>.PairSequence.Iterator 69 | public mutating func next() -> (key: Key, value: Value)? { 70 | guard let n = source.next() else { return nil } 71 | return (n.key.source,n.value) 72 | } 73 | } 74 | public var keys: KeySequence { 75 | return KeySequence(source: self) 76 | } 77 | public struct KeySequence: Sequence { 78 | private(set) var source: HAMT 79 | public func makeIterator() -> Iterator { 80 | return Iterator(source: source.makeIterator()) 81 | } 82 | public struct Iterator: IteratorProtocol { 83 | var source: HAMT.Iterator 84 | public mutating func next() -> Key? { 85 | return source.next()?.key 86 | } 87 | } 88 | } 89 | 90 | public var values: ValueSequence { 91 | return ValueSequence(source: self) 92 | } 93 | public struct ValueSequence: Sequence { 94 | private(set) var source: HAMT 95 | public func makeIterator() -> Iterator { 96 | return Iterator(source: source.makeIterator()) 97 | } 98 | public struct Iterator: IteratorProtocol { 99 | var source: HAMT.Iterator 100 | public mutating func next() -> Value? { 101 | return source.next()?.value 102 | } 103 | } 104 | } 105 | } 106 | 107 | public extension HAMT { 108 | mutating func removeAll() { 109 | self = HAMT() 110 | } 111 | } 112 | 113 | /// A zero-cost wrapper to route system hash value 114 | /// to `PD5Hashable` protocol. 115 | private struct PD5Key: PD5Hashable where K: Hashable { 116 | let source: K 117 | init(_ k: K) { 118 | source = k 119 | } 120 | var hashBits: UInt { 121 | return UInt(bitPattern: source.hashValue) 122 | } 123 | } 124 | 125 | private extension PD5Pair { 126 | /// Strips off `PD5Pair` wrapper. 127 | var tuplized: (key: K, value: V) { 128 | return (key,value) 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /HAMT/LegacyNaiveHashTrie/PD4.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD4.swift 3 | // PD4 4 | // 5 | // Created by Henry on 2019/05/21. 6 | // Copyright © 2019 Eonil. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// 12 | /// An implementation of *hash-trie*. 13 | /// 14 | /// This an associative array optimized for persistent data 15 | /// structure. Therefore, read/write/copy performances are 16 | /// all important. 17 | /// 18 | /// Internally this uses *hash-trie* structure. Which is somewhat 19 | /// like B-Tree except keys are `Hashable`s rather than `Comparable`. 20 | /// Also as this library is based on hash function, this shows 21 | /// similar performance characteristic with hash-table. 22 | /// For single read/write/copy this takes O(1) at best, and O(n) 23 | /// at worst with regarding O(word size) and O(bucket size) as 24 | /// O(1). 25 | /// If you need sorted associative array or even more predictable 26 | /// & stable performance characteristics, check out B-Tree 27 | /// instead of. 28 | /// 29 | /// - SeeAlso: 30 | /// [BTree by Károly Lőrentey](https://github.com/attaswift/BTree) 31 | /// 32 | /// - Complexity: 33 | /// Get at best: O(1) if no hashes collide with regarding O(word size) as O(1). 34 | /// Get at worst: O(n) if all hashes collide. 35 | /// 36 | /// Performance is up to size of dataset and distribution of hash function. 37 | /// 38 | /// If your dataset is small enough and hash function is well distributed, 39 | /// it'll show O(1) performance in average. Default hash algorithm of Swift 40 | /// standard library work well. 41 | /// 42 | public struct PD4 where K: Hashable { 43 | private var b = PD4Bucket,V>.topLevel16384Bytes() 44 | 45 | public init() {} 46 | public var count: Int { 47 | return b.count 48 | } 49 | public subscript(_ k: K) -> V? { 50 | get { return b.get(PD4Key(source: k)) } 51 | set(v) { b.set(PD4Key(source: k),v) } 52 | } 53 | } 54 | 55 | private struct PD4Key: PD4Hashable where K: Hashable { 56 | let source: K 57 | var hashBits: Int { 58 | return source.hashValue 59 | } 60 | } 61 | 62 | -------------------------------------------------------------------------------- /HAMT/LegacyNaiveHashTrie/PD4BinaryExponentiation.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD4BinaryExponentiation.swift 3 | // PD4UnitTests 4 | // 5 | // Created by Henry on 2019/05/23. 6 | // 7 | 8 | /// 9 | /// Compressed form of 2^N number in 8 bit. 10 | /// Valid evaluation can result one of these values. 11 | /// 12 | /// 1...2^256 13 | /// 14 | struct PD4BinaryExponentiation { 15 | var exponent: UInt8 16 | /// Returns `2 ^ self`. 17 | var eval: UInt { 18 | return 0b1 << exponent 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /HAMT/LegacyNaiveHashTrie/PD4BitArray.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD4BitArray.swift 3 | // PD4 4 | // 5 | // Created by Henry on 2019/05/21. 6 | // Copyright © 2019 Eonil. All rights reserved. 7 | // 8 | 9 | struct PD4BitArray: Equatable where N: BinaryInteger { 10 | let bits: N 11 | init(_ bits: N) { 12 | self.bits = bits 13 | } 14 | /// Captures bits in the range and move it to 15 | /// LSB side and return. 16 | func capture(_ r: Range) -> PD4BitArray { 17 | let bc = MemoryLayout.size * 8 18 | let a = bits << r.lowerBound 19 | let b = a >> (bc - r.count) 20 | return PD4BitArray(b) 21 | } 22 | @inline(__always) 23 | func capture(offset: Int, length: Int) -> PD4BitArray { 24 | let bc = MemoryLayout.size * 8 25 | let a = bits << offset 26 | let b = a >> (bc - length) 27 | return PD4BitArray(b) 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /HAMT/LegacyNaiveHashTrie/PD4Bucket.preset.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD4Bucket.preset.swift 3 | // PD4 4 | // 5 | // Created by Henry on 2019/05/22. 6 | // Copyright © 2019 Eonil. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | // 12 | // 2^14 = 16384 13 | // 2^12 = 4096 14 | // 2^10 = 1024 15 | // 2^8 = 256 16 | // 2^6 = 64 17 | // 18 | 19 | extension PD4Bucket { 20 | static func topLevel16384Bytes() -> PD4Bucket { 21 | let x = Config( 22 | bucketCapInBytes: 0b1 << 14, 23 | slotStrideInBytes: UInt16(MemoryLayout.stride)) 24 | return PD4Bucket(config: x) 25 | } 26 | static func topLevel4096Bytes() -> PD4Bucket { 27 | let x = Config( 28 | bucketCapInBytes: 0b1 << 12, 29 | slotStrideInBytes: UInt16(MemoryLayout.stride)) 30 | return PD4Bucket(config: x) 31 | } 32 | static func topLevel1024Bytes() -> PD4Bucket { 33 | let x = Config( 34 | bucketCapInBytes: 0b1 << 10, 35 | slotStrideInBytes: UInt16(MemoryLayout.stride)) 36 | return PD4Bucket(config: x) 37 | } 38 | static func topLevel256Bytes() -> PD4Bucket { 39 | let x = Config( 40 | bucketCapInBytes: 0b1 << 8, 41 | slotStrideInBytes: UInt16(MemoryLayout.stride)) 42 | return PD4Bucket(config: x) 43 | } 44 | static func topLevel64Bytes() -> PD4Bucket { 45 | let x = Config( 46 | bucketCapInBytes: 0b1 << 6, 47 | slotStrideInBytes: UInt16(MemoryLayout.stride)) 48 | return PD4Bucket(config: x) 49 | } 50 | } 51 | 52 | private func findBitsForSlotCap(slotCap: Int) -> UInt8 { 53 | let z = MemoryLayout.size * 8 // machine word size. 54 | for i in 0...size) 62 | } 63 | -------------------------------------------------------------------------------- /HAMT/LegacyNaiveHashTrie/PD4Bucket.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD4Bucket.swift 3 | // PD4 4 | // 5 | // Created by Henry on 2019/05/21. 6 | // Copyright © 2019 Eonil. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | 12 | /// 13 | /// A bucket of a *hash-trie*. 14 | /// 15 | /// DO NOT use this type in user code directly. 16 | /// Exposed internally only for testing. 17 | /// 18 | struct PD4Bucket where K: PD4Hashable { 19 | typealias Config = PD4BucketConfig 20 | typealias Path = PD4HashBitPath 21 | typealias Slot = PD4BucketSlot 22 | 23 | typealias Array = Swift.Array 24 | typealias Pair = (K,V) 25 | 26 | let config: Config 27 | private(set) var count = 0 28 | private(set) var slots: Array 29 | 30 | init(config x: Config) { 31 | config = x 32 | let c = Int(config.slotCapInBucket) 33 | slots = Array(repeating: .none, count: c) 34 | assert(slotCap >= 1) 35 | assert(slotCap <= Int.max) 36 | } 37 | var slotCap: UInt16 { 38 | return config.slotCapInBucket 39 | } 40 | func slotIndex(for k: K) -> Int { 41 | let h = UInt(bitPattern: k.hashBits) 42 | let h1 = PD4HashBitPath(h).nextLevel(config: config, times: config.currentLevel) 43 | return h1.index(config: config) 44 | } 45 | func get(_ k: K) -> V? { 46 | let i = slotIndex(for: k) 47 | switch slots[i] { 48 | case .none: 49 | return nil 50 | case .unique(let kv): 51 | return kv.0 == k ? kv.1 : nil 52 | case .leaf(let a): 53 | // Worst case. Hash collision occured. 54 | return a.first(where: { kv in kv.0 == k })?.1 55 | case .branch(let bu): return 56 | bu.get(k) 57 | } 58 | } 59 | // func get(_ h: Path, _ k: K) -> V? { 60 | // let i = h.index(config: config) 61 | // switch slots[i] { 62 | // case .none: 63 | // return nil 64 | // case .unique(let kv): 65 | // return kv.0 == k ? kv.1 : nil 66 | // case .leaf(let a): 67 | // // Worst case. Hash collision occured. 68 | // return a.first(where: { kv in kv.0 == k })?.1 69 | // case .branch(let bu): return 70 | // bu.get(h.nextLevel(config: config), k) 71 | // } 72 | // } 73 | mutating func set(_ k: K, _ v: V?) { 74 | let h = k.pathify() 75 | set(h, k, v) 76 | } 77 | mutating func set(_ h: Path, _ k: K, _ v: V?) { 78 | if let v = v { 79 | insertOrReplace(h, k, v) 80 | } 81 | else { 82 | removeOrIgnore(h, k) 83 | } 84 | } 85 | 86 | /// Returns `true` if new one has been inserted. 87 | /// Returns `false` if existing one has been replaced. 88 | enum InsertOrReplaceResult { 89 | case inserted 90 | case replaced 91 | } 92 | @discardableResult 93 | mutating func insertOrReplace(_ h: Path, _ k: K, _ v: V) -> InsertOrReplaceResult { 94 | let i = slotIndex(for: k) 95 | switch slots[i] { 96 | case .none: 97 | precondition(count < .max, "Out of space.") 98 | let kv = (k,v) 99 | slots[i] = .unique(kv) 100 | count += 1 101 | return .inserted 102 | case .unique(let kv): 103 | if kv.0 == k { 104 | // Unique replacement. 105 | let kv1 = (k, v) 106 | slots[i] = .unique(kv1) 107 | return .replaced 108 | } 109 | else { 110 | if config.isFinalBranch { 111 | // Hash collided. 112 | // No more depth can be created. 113 | // Promote to leaf and insert. 114 | precondition(count < .max, "Out of space.") 115 | let kv1 = (k,v) 116 | slots[i] = .leaf([kv, kv1]) 117 | count += 1 118 | return .inserted 119 | } 120 | else { 121 | // Promote unique to branch and insert. 122 | let h1 = h.nextLevel(config: config) 123 | var bu = PD4Bucket(config: config.nextLevel()) 124 | assert(bu.config.currentLevel < bu.config.maxLevel()) 125 | bu.insertOrReplace(h1, kv.0, kv.1) 126 | bu.insertOrReplace(h1, k, v) 127 | slots[i] = .branch(bu) 128 | count += 1 129 | return .inserted 130 | } 131 | } 132 | 133 | case .leaf(var a): 134 | for j in 0.. RemoveOrIgnoreResult { 171 | let i = slotIndex(for: k) 172 | switch slots[i] { 173 | case .none: 174 | return .ignored 175 | case .unique(let kv): 176 | if kv.0 == k { 177 | count -= 1 178 | slots[i] = .none 179 | return .removed 180 | } 181 | else { 182 | // Ignore. 183 | return .ignored 184 | } 185 | case .leaf(var a): 186 | for j in a.indices { 187 | if a[j].0 == k { 188 | a.remove(at: j) 189 | if a.count == 1 { 190 | // Promote to unique slot. 191 | count -= 1 192 | slots[i] = .unique(a[0]) 193 | return .removed 194 | } 195 | else { 196 | count -= 1 197 | slots[i] = .leaf(a) 198 | return .removed 199 | } 200 | } 201 | } 202 | return .ignored 203 | case .branch(var b): 204 | let h1 = h.nextLevel(config: config) 205 | switch b.removeOrIgnore(h1, k) { 206 | case .ignored: 207 | return .ignored 208 | case .removed: 209 | count -= 1 210 | switch b.count { 211 | case 0: 212 | slots[i] = .none 213 | case 1: 214 | // Promote to unique slot. 215 | // TODO: Make up a regular iterator. 216 | var kv: Pair? 217 | b.iterate({ kv1 in 218 | kv = kv1 219 | }) 220 | slots[i] = .unique(kv!) 221 | default: 222 | slots[i] = .branch(b) 223 | break 224 | } 225 | return .removed 226 | } 227 | } 228 | } 229 | } 230 | 231 | extension PD4Bucket { 232 | func iterate(_ fx: (Pair) -> Void) { 233 | for slot in slots { 234 | switch slot { 235 | case .none: 236 | break 237 | case .unique(let kv): 238 | fx(kv) 239 | case .leaf(let a): 240 | for kv in a { 241 | fx(kv) 242 | } 243 | case .branch(let bu): 244 | bu.iterate(fx) 245 | } 246 | } 247 | } 248 | } 249 | 250 | 251 | -------------------------------------------------------------------------------- /HAMT/LegacyNaiveHashTrie/PD4BucketConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD4Config.swift 3 | // PD4 4 | // 5 | // Created by Henry on 2019/05/22. 6 | // Copyright © 2019 Eonil. All rights reserved. 7 | // 8 | 9 | /// 10 | /// 11 | /// 12 | struct PD4BucketConfig { 13 | /// A bucket size is 64KiB at maximum. 14 | let bucketCapInBytes: UInt16 15 | 16 | /// Single element can be sized up to 64KiB at maximum. 17 | let slotCapInBucket: UInt16 18 | let bitMaskForSlotCapFromLSB: UInt16 19 | 20 | /// Hash bit count used for each level. 21 | /// This value is cached to gain a little performance gain. 22 | let bitCountPerLevel: UInt8 23 | private(set) var currentLevel: UInt8 24 | 25 | init(bucketCapInBytes z: UInt16, slotStrideInBytes: UInt16) { 26 | assert(z > 0) 27 | assert(slotStrideInBytes > 0) 28 | bucketCapInBytes = z 29 | slotCapInBucket = bucketCapInBytes / slotStrideInBytes 30 | bitCountPerLevel = findBitsForSlotCap(slotCap: slotCapInBucket) 31 | 32 | let a = UInt16(0b0) 33 | let b = ~a 34 | let c = b << bitCountPerLevel 35 | let d = ~c 36 | bitMaskForSlotCapFromLSB = d 37 | currentLevel = 0 38 | } 39 | func maxLevel() -> Int { 40 | let max_bits = MemoryLayout.size * 8 41 | return max_bits / Int(bitCountPerLevel) 42 | } 43 | var isFinalBranch: Bool { 44 | return currentLevel + 1 == maxLevel() 45 | } 46 | func nextLevel() -> PD4BucketConfig { 47 | var x = self 48 | x.currentLevel += 1 49 | return x 50 | } 51 | } 52 | 53 | private extension UInt8 { 54 | /// Returns `2 ^ self`. 55 | func binaryExponentiation() -> Int { 56 | return 0b1 << self 57 | } 58 | } 59 | 60 | private func findBitsForSlotCap(slotCap: UInt16) -> UInt8 { 61 | let z = MemoryLayout.size * 8 // machine word size. 62 | for i in 0.. where K: PD4Hashable { 11 | // typealias Config = PD4BucketConfig 12 | // typealias Slot = PD4BucketSlot 13 | // 14 | // typealias Array = Swift.ContiguousArray 15 | // typealias Pair = (K,V) 16 | // 17 | // private var ref: PD4BucketRef 18 | // init(config x: PD4BucketConfig) { 19 | // ref = PD4BucketRef(config: x) 20 | // } 21 | // var config: Config { 22 | // return ref.config 23 | // } 24 | // var count: Int { 25 | // return ref.count 26 | // } 27 | // var slots: Array { 28 | // return ref.slots 29 | // } 30 | // func get(_ k: K) -> V? { 31 | // return ref.get(k) 32 | // } 33 | // mutating func set(_ k: K, _ v: V?) { 34 | // if !isKnownUniquelyReferenced(&ref) { 35 | // ref = PD4BucketRef(copying: ref) 36 | // } 37 | // ref.set(k, v) 38 | // } 39 | // mutating func insertOrReplace(_ k: K, _ v: V) -> PD4BucketRef.InsertOrReplaceResult { 40 | // if !isKnownUniquelyReferenced(&ref) { 41 | // ref = PD4BucketRef(copying: ref) 42 | // } 43 | // return ref.insertOrReplace(k, v) 44 | // } 45 | // mutating func removeOrIgnore(_ k: K) -> PD4BucketRef.RemoveOrIgnoreResult { 46 | // if !isKnownUniquelyReferenced(&ref) { 47 | // ref = PD4BucketRef(copying: ref) 48 | // } 49 | // return ref.removeOrIgnore(k) 50 | // } 51 | //} 52 | // 53 | ///// 54 | ///// A bucket of a *hash-trie*. 55 | ///// 56 | ///// DO NOT use this type in user code directly. 57 | ///// Exposed internally only for testing. 58 | ///// 59 | //final class PD4BucketRef where K: PD4Hashable { 60 | // typealias Config = PD4BucketConfig 61 | // typealias Slot = PD4BucketSlot 62 | // 63 | // typealias Array = Swift.ContiguousArray 64 | // typealias Pair = (K,V) 65 | // 66 | // let config: Config 67 | // private(set) var count = 0 68 | // private(set) var slots: Array 69 | // 70 | // init(copying src: PD4BucketRef) { 71 | // config = src.config 72 | // count = src.count 73 | // slots = src.slots 74 | // } 75 | // init(config x: Config) { 76 | // config = x 77 | // let c = config.bucketCapInBytes / MemoryLayout.stride 78 | // slots = Array(repeating: .none, count: c) 79 | // assert(slotCap >= 1) 80 | // assert(slotCap <= Int.max) 81 | // } 82 | // 83 | // var slotCap: Int { 84 | // return config.bucketCapInBytes / MemoryLayout.stride 85 | // } 86 | // 87 | // var pairCap: Int { 88 | // return config.bucketCapInBytes / MemoryLayout.stride 89 | // } 90 | // 91 | // func slotIndex(for k: K) -> Int { 92 | // let h = UInt(bitPattern: k.hashBits) 93 | // let n = Int(config.currentLevel * config.bitCountPerLevel) 94 | // let m = BitArray(h).capture((n - Int(config.bitCountPerLevel)).. V? { 99 | // let i = slotIndex(for: k) 100 | // switch slots[i] { 101 | // case .none: return nil 102 | // case .leaf(let a): return a.first(where: { kv in kv.0 == k })?.1 103 | // case .branch(let bu): return bu.get(k) 104 | // } 105 | // } 106 | // func set(_ k: K, _ v: V?) { 107 | // if let v = v { 108 | // insertOrReplace(k, v) 109 | // } 110 | // else { 111 | // removeOrIgnore(k) 112 | // } 113 | // } 114 | // 115 | // /// Returns `true` if new one has been inserted. 116 | // /// Returns `false` if existing one has been replaced. 117 | // enum InsertOrReplaceResult { 118 | // case inserted 119 | // case replaced 120 | // } 121 | // @discardableResult 122 | // func insertOrReplace(_ k: K, _ v: V) -> InsertOrReplaceResult { 123 | // let i = slotIndex(for: k) 124 | // switch slots[i] { 125 | // case .none: 126 | // precondition(count < .max, "Out of space.") 127 | // let kv = (k,v) 128 | // slots[i] = .leaf([kv]) 129 | // count += 1 130 | // return .inserted 131 | // case .leaf(var a): 132 | // for j in 0..(config: config.nextLevel()) 156 | // assert(bu.config.currentLevel < bu.config.maxLevel()) 157 | // for kv in a { 158 | // bu.insertOrReplace(kv.0, kv.1) 159 | // } 160 | // bu.insertOrReplace(k, v) 161 | // slots[i] = .branch(bu) 162 | // count += 1 163 | // return .inserted 164 | // } 165 | // case .branch(var bu): 166 | // switch bu.insertOrReplace(k, v) { 167 | // case .inserted: 168 | // slots[i] = .branch(bu) 169 | // count += 1 170 | // return .inserted 171 | // case .replaced: 172 | // slots[i] = .branch(bu) 173 | // return .replaced 174 | // } 175 | // } 176 | // } 177 | // 178 | // enum RemoveOrIgnoreResult { 179 | // case removed 180 | // case ignored 181 | // } 182 | // @discardableResult 183 | // func removeOrIgnore(_ k: K) -> RemoveOrIgnoreResult { 184 | // let i = slotIndex(for: k) 185 | // switch slots[i] { 186 | // case .none: 187 | // return .ignored 188 | // case .leaf(var a): 189 | // for j in a.indices { 190 | // if a[j].0 == k { 191 | // a.remove(at: j) 192 | // if a.isEmpty { 193 | // count -= 1 194 | // slots[i] = .none 195 | // return .removed 196 | // } 197 | // else { 198 | // count -= 1 199 | // slots[i] = .leaf(a) 200 | // return .removed 201 | // } 202 | // } 203 | // } 204 | // return .ignored 205 | // case .branch(var bu): 206 | // // if bu.count <= (pairCap / 2) { 207 | // if bu.count <= pairCap { 208 | // // Convert to in-cap. 209 | // // Always convert regardless of remove or ignore. 210 | // var a = Array() 211 | // a.reserveCapacity(pairCap) 212 | // bu.iterate({ kv in 213 | // if kv.0 != k { 214 | // a.append(kv) 215 | // } 216 | // }) 217 | // 218 | // if a.count < bu.count { 219 | // // Removed. 220 | // slots[i] = .leaf(a) 221 | // count -= 1 222 | // return .removed 223 | // } 224 | // else { 225 | // // Ignored. 226 | // slots[i] = .leaf(a) 227 | // return .ignored 228 | // } 229 | // } 230 | // else { 231 | // switch bu.removeOrIgnore(k) { 232 | // case .removed: 233 | // slots[i] = .branch(bu) 234 | // count -= 1 235 | // return .removed 236 | // case .ignored: 237 | // return .ignored 238 | // } 239 | // } 240 | // 241 | // } 242 | // } 243 | //} 244 | // 245 | //extension PD4Bucket { 246 | // func iterate(_ fx: (Pair) -> Void) { 247 | // for slot in slots { 248 | // switch slot { 249 | // case .none: 250 | // break 251 | // case .leaf(let a): 252 | // for kv in a { 253 | // fx(kv) 254 | // } 255 | // case .branch(let bu): 256 | // bu.iterate(fx) 257 | // } 258 | // } 259 | // } 260 | //} 261 | -------------------------------------------------------------------------------- /HAMT/LegacyNaiveHashTrie/PD4BucketSlot.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD4BucketSlot.swift 3 | // PD4 4 | // 5 | // Created by Henry on 2019/05/22. 6 | // Copyright © 2019 Eonil. All rights reserved. 7 | // 8 | 9 | enum PD4BucketSlot where K: PD4Hashable { 10 | typealias Bucket = PD4Bucket 11 | typealias Pair = Bucket.Pair 12 | /// 13 | /// This means there's no key-vaue pair 14 | /// for the hash bits. 15 | /// 16 | case none 17 | /// 18 | /// Unique hash key-value node. 19 | /// 20 | /// If there's only one key-value pair for 21 | /// for a hash bits, it can be stored inline 22 | /// in slot list. This provides best 23 | /// performance as it does not require linear 24 | /// search. 25 | /// 26 | case unique(Pair) 27 | /// 28 | /// Branch node. 29 | /// 30 | /// This contains sub-bucket for hash bits 31 | /// at the level. 32 | /// 33 | case branch(Bucket) 34 | /// 35 | /// Leaf node. Contains hash-collided 36 | /// key-value pairs. 37 | /// 38 | /// This contains key-value pairs for same 39 | /// hash bits. This node should exist only 40 | /// at maximum level. 41 | /// 42 | /// Normal leafs always keep their capacity 43 | /// in limit. But leaf node at maximum depth 44 | /// can contain arbitrary elements. 45 | /// This is because we don't have any more 46 | /// hash bits, therefore having more levels 47 | /// meaningless. 48 | /// 49 | case leaf(Bucket.Array) 50 | } 51 | -------------------------------------------------------------------------------- /HAMT/LegacyNaiveHashTrie/PD4FixedSizedArray.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD4FixedSizedArray.swift 3 | // HAMT 4 | // 5 | // Created by Henry on 2019/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | struct PD4FixedSizedArray: RandomAccessCollection { 11 | private var buffer: FixedSizedBuffer 12 | 13 | init(repeating v: T, count c: Int) { 14 | buffer = FixedSizedBuffer(capacity: c, default: v) 15 | } 16 | 17 | var startIndex: Int { 18 | return 0 19 | } 20 | 21 | var endIndex: Int { 22 | return buffer.capacity 23 | } 24 | 25 | subscript(_ i: Int) -> T { 26 | get { return buffer[i] } 27 | set(v) { 28 | if !isKnownUniquelyReferenced(&buffer) { 29 | buffer = buffer.copy() 30 | } 31 | buffer[i] = v 32 | } 33 | } 34 | } 35 | private final class FixedSizedBuffer: RandomAccessCollection { 36 | let capacity: Int 37 | private var ptr: UnsafeMutablePointer 38 | init(capacity c: Int, default v: T) { 39 | capacity = c 40 | ptr = UnsafeMutablePointer.allocate(capacity: c) 41 | ptr.initialize(repeating: v, count: c) 42 | } 43 | init(copying src: FixedSizedBuffer) { 44 | capacity = src.capacity 45 | ptr = UnsafeMutablePointer.allocate(capacity: src.capacity) 46 | ptr.initialize(from: src.ptr, count: src.capacity) 47 | } 48 | var startIndex: Int { 49 | return 0 50 | } 51 | var endIndex: Int { 52 | return capacity 53 | } 54 | subscript(_ i: Int) -> T { 55 | get { 56 | return ptr[i] 57 | } 58 | set(v) { 59 | ptr[i] = v 60 | } 61 | } 62 | deinit { 63 | ptr.deinitialize(count: capacity) 64 | ptr.deallocate() 65 | } 66 | func copy() -> FixedSizedBuffer { 67 | return FixedSizedBuffer(copying: self) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /HAMT/LegacyNaiveHashTrie/PD4HashBitPath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HashPath.swift 3 | // PD5UnitTests 4 | // 5 | // Created by Henry on 2019/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | extension PD4Hashable { 11 | func pathify() -> PD4HashBitPath { 12 | return PD4HashBitPath(UInt(bitPattern: hashBits)) 13 | } 14 | } 15 | struct PD4HashBitPath { 16 | private let bits: UInt 17 | init(_ n: UInt) { 18 | bits = n 19 | } 20 | func index(config x: PD4BucketConfig) -> Int { 21 | let m = UInt(x.bitMaskForSlotCapFromLSB) 22 | let n = bits & m % UInt(x.slotCapInBucket) 23 | return Int(n) 24 | } 25 | func nextLevel(config x: PD4BucketConfig) -> PD4HashBitPath { 26 | let bits1 = bits >> x.bitCountPerLevel 27 | return PD4HashBitPath(bits1) 28 | } 29 | func nextLevel(config x: PD4BucketConfig, times n: UInt8) -> PD4HashBitPath { 30 | let bits1 = bits >> (UInt(x.bitCountPerLevel) * UInt(n)) 31 | return PD4HashBitPath(bits1) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /HAMT/LegacyNaiveHashTrie/PD4Hashable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD4Hashable.swift 3 | // PD4 4 | // 5 | // Created by Henry on 2019/05/21. 6 | // Copyright © 2019 Eonil. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// 12 | /// Swift's standard `Hashable` implementation is not deterministic 13 | /// and does not provide reproducibility, and can be different for 14 | /// different executions. 15 | /// 16 | /// I need reproducible hash value for deterministic test and 17 | /// have to avoid Swift standard hashings. Hence I defined this 18 | /// type. 19 | /// 20 | /// It's unclear how I can avoid Swift standard hashing. 21 | /// I just define this protocol to avoid them completely. 22 | /// 23 | /// For test, you can provide some deterministic hash bits. 24 | /// For production, it doesn't have to be deterministic or 25 | /// reproducible over exeuctions. You can route to Swift standard 26 | /// hashing. 27 | /// 28 | protocol PD4Hashable: Equatable { 29 | var hashBits: Int { get } 30 | } 31 | 32 | -------------------------------------------------------------------------------- /HAMT/PD5Bucket.swift: -------------------------------------------------------------------------------- 1 | //// 2 | //// PD5Bucket.swift 3 | //// HAMT 4 | //// 5 | //// Created by Henry on 2019/05/23. 6 | //// 7 | // 8 | //import Foundation 9 | // 10 | //protocol PD5BucketProtocol { 11 | // associatedtype Slot 12 | // associatedtype Pair 13 | // associatedtype SlotCollection: Sequence where SlotCollection.Element == Slot 14 | // var config: PD5BucketConfig { get } 15 | // /// Total count of all elements in this subtree. 16 | // var slots: SlotCollection { get } 17 | // var count: Int { get } 18 | // 19 | //} 20 | -------------------------------------------------------------------------------- /HAMT/PD5Bucket64.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD5Bucket64.swift 3 | // PD5UnitTests 4 | // 5 | // Created by Henry on 2019/05/22. 6 | // 7 | 8 | private let hashBitCountPerLevel = findHashBitCountPerLevel() 9 | private let hashBitMaskPerLevel = makeHashBitMaskPerLevel() 10 | private let maxLevelCount = UInt8(UInt(MemoryLayout.size * 8) / hashBitCountPerLevel) 11 | private let maxLevelIndex = UInt8(UInt(MemoryLayout.size * 8) / hashBitCountPerLevel - 1) 12 | private func findHashBitCountPerLevel() -> UInt { 13 | let wordsz = MemoryLayout.size * 8 14 | switch wordsz { 15 | case 16: return 4 16 | case 32: return 5 17 | case 64: return 6 18 | default: fatalError("Unsupported platform.") 19 | } 20 | } 21 | private func makeHashBitMaskPerLevel() -> UInt { 22 | let bc = findHashBitCountPerLevel() 23 | return ~(~UInt(0b0) << bc) 24 | } 25 | 26 | /// 27 | /// A 64-bit HAMT node. 28 | /// 29 | /// One 64-bit HAMT node has 64 slots. There's no way to 30 | /// adjust this size dynamically. 31 | /// 32 | struct PD5Bucket64 where K: PD5Hashable { 33 | typealias Slot = PD5Slot64 34 | typealias SlotCollection = PD5CompressedTable64 35 | typealias Pair = PD5Pair 36 | private(set) var config = PD5BucketConfig() 37 | /// Total count of all elements in this subtree. 38 | private(set) var sum = 0 39 | private(set) var slots = SlotCollection() 40 | 41 | @inlinable 42 | init() {} 43 | 44 | private init(config x: PD5BucketConfig) { 45 | precondition(x.level < maxLevelCount) 46 | config = x 47 | } 48 | 49 | @inlinable 50 | var count: Int { 51 | return sum 52 | } 53 | @inlinable 54 | subscript(_ k: K) -> V? { 55 | get { 56 | let h = k.hashBits 57 | return find(h, k) 58 | } 59 | set(v) { 60 | let h = k.hashBits 61 | if let v = v { 62 | insertOrReplace(h, k, v) 63 | } 64 | else { 65 | removeOrIgnore(h, k) 66 | } 67 | } 68 | } 69 | 70 | @inlinable 71 | subscript(_ k: K, default defv: @autoclosure() -> V) -> V { 72 | get { 73 | let h = k.hashBits 74 | return find(h, k) ?? defv() 75 | } 76 | set(v) { 77 | let h = k.hashBits 78 | insertOrReplace(h, k, v) 79 | } 80 | } 81 | 82 | @inlinable 83 | func slotIndex(for h: UInt) -> UInt { 84 | let h1 = h >> (hashBitCountPerLevel * UInt(config.level)) 85 | let ik = h1 & hashBitMaskPerLevel 86 | return ik 87 | } 88 | @inlinable 89 | func find(_ h: UInt, _ k: K) -> V? { 90 | // let ik = slotIndex(for: h) 91 | // let slot = slots.get(index: ik, default: .none) 92 | // switch slot { 93 | // case .none: return nil 94 | // case .unique(let kv): return kv.key == k ? kv.value : nil 95 | // case .branch(let b): return b.find(h, k) 96 | // case .leaf(let a): return a.first(where: { kv in kv.key == k })?.value 97 | // } 98 | return findWithPreshiftedHashBits(h, k) 99 | } 100 | private func findWithPreshiftedHashBits(_ h: UInt, _ k: K) -> V? { 101 | var h1 = h 102 | var b = self 103 | while true { 104 | let ik = h1 & hashBitMaskPerLevel 105 | let slot = b.slots.get(index: ik, default: .none) 106 | switch slot { 107 | case .none: return nil 108 | case .unique(let kv): return kv.key == k ? kv.value : nil 109 | case .branch(let b1): 110 | b = b1 111 | h1 = h1 >> hashBitCountPerLevel 112 | case .leaf(let a): return a.first(where: { kv in kv.key == k })?.value 113 | } 114 | } 115 | } 116 | 117 | enum InsertOrReplaceResult { 118 | case inserted 119 | case replaced(V) 120 | } 121 | @inlinable 122 | @discardableResult 123 | mutating func insertOrReplace(_ h: UInt, _ k: K, _ v: V) -> InsertOrReplaceResult { 124 | precondition(count < .max) 125 | let ik = slotIndex(for: h) 126 | let s = slots.get(index: ik, default: .none) 127 | switch s { 128 | case .none: 129 | slots.set(index: ik, .unique(Pair(k,v))) 130 | sum += 1 131 | return .inserted 132 | case .unique(let kv): 133 | if kv.key == k { 134 | // Replace. 135 | slots.set(index: ik, .unique(Pair(k,v))) 136 | return .replaced(kv.value) 137 | } 138 | else { 139 | // Insert. 140 | if config.level < maxLevelIndex { 141 | // Branch down. 142 | var x = config 143 | x.level += 1 144 | var b = PD5Bucket64(config: x) 145 | b.insertOrReplace(kv.key.hashBits, kv.key, kv.value) // Take care that we need to pass correct hash here. 146 | b.insertOrReplace(h, k, v) 147 | slots.set(index: ik, .branch(b)) 148 | } 149 | else { 150 | // Reached at max level. 151 | // Put them into a leaf. 152 | slots.set(index: ik, .leaf([kv, Pair(k,v)])) 153 | } 154 | sum += 1 155 | return .inserted 156 | } 157 | case .branch(var b): 158 | let r = b.insertOrReplace(h, k, v) 159 | slots.set(index: ik, .branch(b)) 160 | switch r { 161 | case .inserted: sum += 1 162 | case .replaced(_): break 163 | } 164 | return r 165 | case .leaf(var a): 166 | for i in a.indices { 167 | if a[i].key == k { 168 | // Replace. 169 | let v1 = a[i].value 170 | a[i].value = v 171 | slots.set(index: ik, .leaf(a)) 172 | return .replaced(v1) 173 | } 174 | } 175 | // Insert. 176 | a.append(Pair(k,v)) 177 | slots.set(index: ik, .leaf(a)) 178 | sum += 1 179 | return .inserted 180 | } 181 | } 182 | 183 | enum RemoveOrIgnoreResult { 184 | case removed(V) 185 | case ignored 186 | } 187 | @inlinable 188 | @discardableResult 189 | mutating func removeOrIgnore(_ h: UInt, _ k: K) -> RemoveOrIgnoreResult { 190 | let ik = slotIndex(for: h) 191 | let s = slots.get(index: ik, default: .none) 192 | switch s { 193 | case .none: 194 | return .ignored 195 | case .unique(let kv): 196 | if kv.key == k { 197 | slots.set(index: ik, .none) 198 | sum -= 1 199 | return .removed(kv.value) 200 | } 201 | else { 202 | return .ignored 203 | } 204 | case .branch(var b): 205 | let r = b.removeOrIgnore(h, k) 206 | switch r { 207 | case .removed(let v): 208 | sum -= 1 209 | switch b.sum { 210 | case 0: slots.set(index: ik, .none) 211 | case 1: slots.set(index: ik, .unique(b.ADHOC_collectOne())) 212 | default: slots.set(index: ik, .branch(b)) 213 | } 214 | return .removed(v) 215 | case .ignored: 216 | return .ignored 217 | } 218 | case .leaf(var a): 219 | if let i = a.firstIndex(where: { kv in kv.key == k }) { 220 | let v = a[i].value 221 | sum -= 1 222 | a.remove(at: i) 223 | switch a.count { 224 | case 0: slots.set(index: ik, .none) 225 | case 1: slots.set(index: ik, .unique(a[0])) 226 | default: slots.set(index: ik, .leaf(a)) 227 | } 228 | return .removed(v) 229 | } 230 | else { 231 | return .ignored 232 | } 233 | } 234 | } 235 | 236 | private func ADHOC_collectOne() -> Pair { 237 | precondition(sum == 1) 238 | for s in slots { 239 | switch s { 240 | case .unique(let kv): return kv 241 | default: break 242 | } 243 | } 244 | fatalError() 245 | } 246 | } 247 | 248 | extension PD5Bucket64: Equatable where V: Equatable { 249 | /// Two buckets are equal if they contains equal elements. 250 | /// Order of elements doesn't matter. 251 | static func == (_ a: PD5Bucket64, _ b: PD5Bucket64) -> Bool { 252 | guard a.count == b.count else { return false } 253 | for p in a.pairs { 254 | guard b[p.key] == p.value else { return false } 255 | } 256 | for p in b.pairs { 257 | guard a[p.key] == p.value else { return false } 258 | } 259 | return true 260 | } 261 | } 262 | 263 | -------------------------------------------------------------------------------- /HAMT/PD5BucketConfig.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD5BucketConfig.swift 3 | // HAMT 4 | // 5 | // Created by Henry on 2019/05/23. 6 | // 7 | 8 | struct PD5BucketConfig: Equatable { 9 | /// Level of current bucket. 10 | /// Level 0 means root-level bucket. 11 | /// Maximum level for 64-bit bucket is 9. 12 | /// Bucket level 10 is invald. 13 | var level = UInt8(0) 14 | } 15 | -------------------------------------------------------------------------------- /HAMT/PD5CompressedTable64.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD5CompressedArray64.swift 3 | // PD5UnitTests 4 | // 5 | // Created by Henry on 2019/05/23. 6 | // 7 | 8 | struct PD5CompressedTable64: Sequence { 9 | typealias Element = T 10 | private var bitmap = UInt64(0b0) 11 | private var slots = PD5ImmutableArray() 12 | 13 | @inlinable 14 | var capacity: Int { 15 | return 64 16 | } 17 | @inlinable 18 | var count: Int { 19 | return bitmap.nonzeroBitCount 20 | } 21 | @inlinable 22 | func makeIterator() -> PD5ImmutableArray.Iterator { 23 | return slots.makeIterator() 24 | } 25 | 26 | @inlinable 27 | func get(index k: UInt, default defv: @autoclosure() -> T) -> T { 28 | assert(k < 64) 29 | assert(0 <= k) 30 | let mask = UInt64(0b1) << k 31 | if (bitmap & mask).nonzeroBitCount == 0 { 32 | // No value at index. 33 | return defv() 34 | } 35 | else { 36 | let countingMask = ~(UInt64(0xffff_ffff_ffff_ffff) << k) 37 | let bitCount = (bitmap & countingMask).nonzeroBitCount 38 | return slots[bitCount] 39 | } 40 | } 41 | @inlinable 42 | func get1(index k: UInt) -> T? { 43 | assert(k < 64) 44 | assert(0 <= k) 45 | let mask = UInt64(0b1) << k 46 | if (bitmap & mask).nonzeroBitCount == 0 { 47 | // No value at index. 48 | return nil 49 | } 50 | else { 51 | let countingMask = ~(UInt64(0xffff_ffff_ffff_ffff) << k) 52 | let bitCount = (bitmap & countingMask).nonzeroBitCount 53 | return slots[bitCount] 54 | } 55 | } 56 | 57 | @inlinable 58 | mutating func set(index k: UInt, _ v: T) { 59 | assert(k < 64) 60 | assert(0 <= k) 61 | let mask = UInt64(0b1) << k 62 | let countingMask = ~(UInt64(0xffff_ffff_ffff_ffff) << k) 63 | let bitCount = (bitmap & countingMask).nonzeroBitCount 64 | if (bitmap & mask).nonzeroBitCount == 0 { 65 | // No value at index. 66 | bitmap |= mask 67 | slots.insert(v, at: bitCount) 68 | } 69 | else { 70 | slots[bitCount] = v 71 | } 72 | } 73 | @inlinable 74 | mutating func unset(index k: UInt) { 75 | assert(k < 64) 76 | assert(0 <= k) 77 | let mask = UInt64(0b1) << k 78 | let countingMask = ~(UInt64(0xffff_ffff_ffff_ffff) << k) 79 | let bitCount = (bitmap & countingMask).nonzeroBitCount 80 | if (bitmap & mask).nonzeroBitCount == 0 { 81 | // No value at index. 82 | // Nothing to do. 83 | } 84 | else { 85 | slots.remove(at: bitCount) 86 | bitmap &= ~mask 87 | } 88 | } 89 | } 90 | 91 | /// Two tables are equal if all elements at same positions are equal. 92 | extension PD5CompressedTable64: Equatable where T: Equatable {} 93 | -------------------------------------------------------------------------------- /HAMT/PD5Hashable.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD5Hashable.swift 3 | // PD5UnitTests 4 | // 5 | // Created by Henry on 2019/05/23. 6 | // 7 | 8 | import Foundation 9 | 10 | /// 11 | /// Every state has to be reproducible to write precise stable test code. 12 | /// 13 | /// Swift's system default `Hashable.hashValue` implementation does not 14 | /// guarantee reproduction of same value for same input on different 15 | /// sessions, therefore should be avoided. 16 | /// 17 | /// Instead of directly using `Hashable`, I defined another route to 18 | /// get hash value. With this type, I can guarantee certain hash values, 19 | /// and can observe & test state change for specific hash values. 20 | /// 21 | protocol PD5Hashable: Equatable { 22 | /// 23 | /// Provides custom hash values. 24 | /// 25 | /// For user's production code, it is recommended using 26 | /// Swift's default `hashValue` implementation. 27 | /// 28 | /// For test code, you can return specific value that fits 29 | /// to your test needs. 30 | /// 31 | @inlinable 32 | var hashBits: UInt { get } 33 | } 34 | -------------------------------------------------------------------------------- /HAMT/PD5ImmutableArray.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD5SlotArray.swift 3 | // HAMT-macOS 4 | // 5 | // Created by Henry on 2019/06/01. 6 | // 7 | 8 | import Foundation 9 | 10 | /// An array-like collection that creates a new copied instance 11 | /// for all mutations. 12 | /// 13 | /// There's no code path that can potentially write to a shared 14 | /// buffer, therefore, safe for multi-threaded read/write/copy 15 | /// scenario. 16 | /// 17 | /// As I suspect Swift CoW cannot guarantee isolation 18 | /// under multi-threaded envinronment, I enfoce to copy 19 | /// slots before every time I change it. 20 | /// 21 | struct PD5ImmutableArray: RandomAccessCollection, MutableCollection { 22 | private let core: ContiguousArray 23 | /// Initialized an empty collection. 24 | init() { 25 | core = [] 26 | } 27 | init(inserting e: T, at i: Int, from c: PD5ImmutableArray) { 28 | var a = ContiguousArray() 29 | a.reserveCapacity(c.count + 1) 30 | for j in 0..() 41 | a.reserveCapacity(c.count) 42 | for j in 0..() 53 | a.reserveCapacity(c.count - 1) 54 | for j in 0.. T { 70 | get { return read(at: i) } 71 | set(v) { write(v, at: i) } 72 | } 73 | private func read(at i: Int) -> T { 74 | return core[i] 75 | } 76 | private mutating func write(_ e: T, at i: Int) { 77 | self = PD5ImmutableArray(updatingAt: i, with: e, from: self) 78 | } 79 | 80 | mutating func insert(_ e: T, at i: Int) { 81 | self = PD5ImmutableArray(inserting: e, at: i, from: self) 82 | } 83 | mutating func remove(at i: Int) { 84 | self = PD5ImmutableArray(removingAt: i, from: self) 85 | } 86 | } 87 | 88 | extension PD5ImmutableArray: Equatable where T: Equatable {} 89 | -------------------------------------------------------------------------------- /HAMT/PD5Iterator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD5Iterator.swift 3 | // PD5UnitTests 4 | // 5 | // Created by Henry on 2019/05/23. 6 | // 7 | 8 | extension PD5Bucket64 { 9 | /// All pairs in this bucket subtree. 10 | var pairs: PairSequence { 11 | return PairSequence(source: dfsSlots.lazy.flatMap({ s in s.currentLevelPairs })) 12 | } 13 | struct PairSequence: Sequence { 14 | var source: LazySequence>> 15 | func makeIterator() -> Iterator { 16 | return Iterator(source: source.makeIterator()) 17 | } 18 | struct Iterator: IteratorProtocol { 19 | var source: LazySequence>>.Iterator 20 | mutating func next() -> PD5Pair? { 21 | return source.next() 22 | } 23 | } 24 | } 25 | } 26 | 27 | extension PD5Slot64 { 28 | var currentLevelPairs: CurrentLevelPairs { 29 | switch self { 30 | case .none: return .none 31 | case .unique(let kv): return .single(kv) 32 | case .branch(_): return .none // Skip as this one should performs shallow iteration... 33 | case .leaf(let a): return .multiple(a) 34 | } 35 | } 36 | enum CurrentLevelPairs: Sequence { 37 | case none 38 | case single(Pair) 39 | case multiple(ContiguousArray) 40 | 41 | func makeIterator() -> Iterator { 42 | return Iterator(of: self) 43 | } 44 | struct Iterator: IteratorProtocol { 45 | private var source: CurrentLevelPairs 46 | private var index = 0 47 | init(of s: CurrentLevelPairs) { 48 | source = s 49 | } 50 | mutating func next() -> Pair? { 51 | switch source { 52 | case .none: 53 | return nil 54 | case .single(let p): 55 | guard index == 0 else { return nil } 56 | index += 1 57 | return p 58 | case .multiple(let ps): 59 | guard index < ps.count else { return nil } 60 | index += 1 61 | return ps[index] 62 | } 63 | } 64 | } 65 | } 66 | } 67 | 68 | extension PD5Bucket64 { 69 | var dfsSlots: DFSSlotSequence { 70 | return DFSSlotSequence(of: self) 71 | } 72 | struct DFSSlotSequence: Sequence { 73 | private var source: PD5Bucket64 74 | init(of b: PD5Bucket64) { 75 | source = b 76 | } 77 | func makeIterator() -> DFSSlotIterator { 78 | return DFSSlotIterator(of: source) 79 | } 80 | } 81 | struct DFSSlotIterator: IteratorProtocol { 82 | private var stack = [PD5CompressedTable64.Iterator]() 83 | init(of b: PD5Bucket64) { 84 | stack.append(b.slots.makeIterator()) 85 | } 86 | mutating func next() -> Slot? { 87 | while var previewit = stack.last { 88 | if let s = previewit.next() { 89 | switch s { 90 | case .branch(let b): 91 | let it1 = b.slots.makeIterator() 92 | stack[stack.count - 1] = previewit // Write back. 93 | stack.append(it1) // Stack up. 94 | return s 95 | default: 96 | stack[stack.count - 1] = previewit // Write back. 97 | return s 98 | } 99 | } 100 | else { 101 | stack.removeLast() 102 | } 103 | } 104 | return nil 105 | } 106 | } 107 | } 108 | 109 | extension PD5Bucket64.DFSSlotSequence: Equatable where V: Equatable {} 110 | extension PD5Slot64.CurrentLevelPairs: Equatable where V: Equatable {} 111 | -------------------------------------------------------------------------------- /HAMT/PD5Pair.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD5Pair.swift 3 | // HAMT 4 | // 5 | // Created by Henry on 2019/05/23. 6 | // 7 | 8 | /// Simpler explicit composition of key and value 9 | /// to provide explicit protocol conformation. 10 | struct PD5Pair { 11 | var key: K 12 | var value: V 13 | 14 | @inlinable 15 | init(_ k: K, _ v: V) { 16 | key = k 17 | value = v 18 | } 19 | } 20 | 21 | extension PD5Pair: Equatable where K: Equatable, V: Equatable {} 22 | -------------------------------------------------------------------------------- /HAMT/PD5Slot64.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD5Slot64.swift 3 | // PD5UnitTests 4 | // 5 | // Created by Henry on 2019/05/23. 6 | // 7 | 8 | enum PD5Slot64 where K: PD5Hashable { 9 | typealias Bucket = PD5Bucket64 10 | typealias Pair = PD5Pair 11 | case none 12 | case unique(Pair) 13 | case branch(Bucket) 14 | /// - Note: 15 | /// This extra indirect jump. 16 | /// As leaf node is for hash-collided 17 | /// keys, I afford slowness here 18 | /// for better performance of non-collided 19 | /// keys. 20 | case leaf(ContiguousArray) 21 | } 22 | extension PD5Slot64: Equatable where V: Equatable { 23 | static func == (_ a: PD5Slot64, _ b: PD5Slot64) -> Bool { 24 | switch (a, b) { 25 | case (.none, .none): return true 26 | case (.unique(let a1), .unique(let b1)): return a1 == b1 27 | case (.branch(let a1), .branch(let b1)): return a1 == b1 28 | case (.leaf(let a1), .leaf(let b1)): return a1 == b1 29 | default: return false 30 | } 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /HAMTFuzz/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Henry Hathaway on 10/4/19. 6 | // 7 | 8 | import Foundation 9 | import HAMT 10 | import GameKit 11 | 12 | // This test is too slow without optimization... 13 | // Uses irreproducible random, therefore this test 14 | // is not reproducible. 15 | let c = 1_00_000 16 | var h = HAMT() 17 | var d = [Int:Int]() 18 | let ns = Array(0..(0..(_ a:T, _ b:T) { 12 | precondition(a == b) 13 | } 14 | 15 | func == (_ a: HAMT, _ b: [K:V]) -> Bool where K: Hashable, V: Equatable { 16 | for k in a.keys { 17 | guard b[k] == a[k] else { return false } 18 | } 19 | for k in b.keys { 20 | guard a[k] == b[k] else { return false } 21 | } 22 | return true 23 | } 24 | -------------------------------------------------------------------------------- /HAMTTests/HAMTTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // File.swift 3 | // 4 | // 5 | // Created by Henry Hathaway on 10/4/19. 6 | // 7 | 8 | import Foundation 9 | import XCTest 10 | import GameKit 11 | @testable import HAMT 12 | 13 | final class HAMTTests: XCTestCase { 14 | func testRemoveAll() { 15 | var x = HAMT() 16 | x[111] = 222 17 | x[222] = 444 18 | x[333] = 666 19 | XCTAssertEqual(x.count, 3) 20 | x.removeAll() 21 | XCTAssertEqual(x.count, 0) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Eonil, Hoon H.. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /PD4UnitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PD4UnitTests/Int.pd4.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Int.pd4.swift 3 | // PD4UnitTests 4 | // 5 | // Created by Henry on 2019/05/21. 6 | // Copyright © 2019 Eonil. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import HAMT 11 | 12 | extension Int: PD4Hashable { 13 | public var hashBits: Int { 14 | return self 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /PD4UnitTests/PD4.utility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD4.utility.swift 3 | // PD4UnitTests 4 | // 5 | // Created by Henry on 2019/05/21. 6 | // Copyright © 2019 Eonil. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | @testable import HAMT 11 | 12 | extension PD4Bucket { 13 | // func indexPath(of k: K) -> IndexPath? { 14 | // let i = slotIndex(for: k) 15 | // switch slots[i] { 16 | // case .none: 17 | // return nil 18 | // case .unique(_): 19 | // return [i] 20 | // case .branch(let bu): 21 | // guard let idxp1 = bu.indexPath(of: k) else { return nil } 22 | // return IndexPath(index: i).appending(idxp1) 23 | // case .leaf(let a): 24 | // guard let j = a.firstIndex(where: { kv in kv.0 == k }) else { return nil } 25 | // return [i,j] 26 | // } 27 | // } 28 | 29 | func countAllBranchBuckets() -> Int { 30 | var c = 1 31 | for s in slots { 32 | switch s { 33 | case .none: break 34 | case .unique(_): break 35 | case .branch(let b): c += b.countAllBranchBuckets() 36 | case .leaf(_): break 37 | } 38 | } 39 | return c 40 | } 41 | func countAllLeafNode() -> Int { 42 | var c = 0 43 | for s in slots { 44 | switch s { 45 | case .none: break 46 | case .unique(_): break 47 | case .branch(let b): c += b.countAllLeafNode() 48 | case .leaf(_): c += 1 49 | } 50 | } 51 | return c 52 | } 53 | func countAllElements() -> Int { 54 | var c = 0 55 | for s in slots { 56 | switch s { 57 | case .none: break 58 | case .unique(_): c += 1 59 | case .branch(let b): c += b.countAllElements() 60 | case .leaf(let a): c += a.count 61 | } 62 | } 63 | return c 64 | } 65 | func countMaxDepth() -> Int { 66 | var d = 1 67 | for s in slots { 68 | switch s { 69 | case .none: break 70 | case .unique(_): break 71 | case .branch(let b): d = max(d,1 + b.countMaxDepth()) 72 | case .leaf(_): d = max(d,2) 73 | } 74 | } 75 | return d 76 | } 77 | 78 | 79 | 80 | func iterateAllBuckets(_ fx: (PD4Bucket) -> Void) { 81 | fx(self) 82 | for slot in slots { 83 | switch slot { 84 | case .none: break 85 | case .unique(_): break 86 | case .branch(let b): b.iterateAllBuckets(fx) 87 | case .leaf(_): break 88 | } 89 | } 90 | } 91 | func iterateAllLeafNodeHashCollidedArrays(_ fx: (Array) -> Void) { 92 | for slot in slots { 93 | switch slot { 94 | case .none: break 95 | case .unique(_): break 96 | case .branch(let b): b.iterateAllLeafNodeHashCollidedArrays(fx) 97 | case .leaf(let a): fx(a) 98 | } 99 | } 100 | } 101 | func iterateAllBranchNodeSlotArrays(_ fx: (Array) -> Void) { 102 | fx(slots) 103 | for slot in slots { 104 | switch slot { 105 | case .none: break 106 | case .unique(_): break 107 | case .branch(let b): b.iterateAllBranchNodeSlotArrays(fx) 108 | case .leaf(_): break 109 | } 110 | } 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /PD4UnitTests/PD4BucketMock1.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD4BucketMock1.swift 3 | // PD4UnitTests 4 | // 5 | // Created by Henry on 2019/05/21. 6 | // Copyright © 2019 Eonil. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import GameKit 11 | @testable import HAMT 12 | 13 | /// Insert-only mock. Inserts sequential keys. 14 | final class PD4BucketMock1 { 15 | typealias PDB = PD4Bucket 16 | 17 | private var r = GKMersenneTwisterRandomSource(seed: 0) 18 | private(set) var source = [Mock1Key:String]() 19 | private(set) var target = PDB.topLevel256Bytes() 20 | private(set) var steppingCount = 0 21 | private(set) var oldVersionTarget: PDB 22 | 23 | init() { 24 | oldVersionTarget = target 25 | } 26 | 27 | func runRandom(_ n: Int) { 28 | for _ in 0.. 17 | // var b = PDB.topLevel256Bytes() 18 | // var d = [Int:String]() 19 | // let r = GKMersenneTwisterRandomSource(seed: 0) 20 | // for i in 0..<2 { 21 | // let k = r.nextInt() 22 | // let v = "value \(k) at \(i) iteration." 23 | // d[k] = v 24 | // b.set(k, v) 25 | // let v1 = b.get(k) 26 | // XCTAssertEqual(v, v1) 27 | // XCTAssertEqual(d.count, b.count) 28 | // } 29 | // let k = r.nextInt() 30 | // let v = "last value" 31 | // d[k] = v 32 | // b.set(k, v) 33 | // let v1 = b.get(k) 34 | // XCTAssertEqual(v, v1) 35 | // XCTAssertEqual(d.count, b.count) 36 | // } 37 | // func testMonteCarlo2() { 38 | // typealias PDB = PD4Bucket 39 | // var b = PDB.topLevel256Bytes() 40 | // var d = [Int:String]() 41 | // let r = GKMersenneTwisterRandomSource(seed: 0) 42 | // for i in 0..<11 { 43 | // let k = r.nextInt() 44 | // let v = "value \(k) at \(i) iteration." 45 | // d[k] = v 46 | // b.set(k, v) 47 | // let v1 = b.get(k) 48 | // XCTAssertEqual(v, v1) 49 | // XCTAssertEqual(d.count, b.count) 50 | // } 51 | // let k = r.nextInt() 52 | // let v = "last value" 53 | // d[k] = v 54 | // b.set(k, v) 55 | // let v1 = b.get(k) 56 | // XCTAssertEqual(v, v1) 57 | // XCTAssertEqual(d.count, b.count) 58 | // } 59 | // func testMonteCarlo3() { 60 | // typealias Mock = PD4BucketMock1 61 | // let mock = Mock() 62 | // mock.runRandom(0xffff) 63 | // mock.stepRandom() 64 | // } 65 | // func testMonteCarlo4() { 66 | // typealias Mock = PD4BucketMock1 67 | // let mock = Mock() 68 | // let n = 0xfff 69 | // for i in 0...stride) bytes") 84 | // mock.runRandom(163_000) 85 | // let scap = mock.target.slotCap 86 | // mock.target.iterateAllBuckets({ b in 87 | // XCTAssertEqual(b.slotCap, scap) 88 | // }) 89 | // } 90 | // func testMonteCarlo7() { 91 | // typealias Mock = PD4BucketMock1 92 | // let mock = Mock() 93 | // print("config: \(mock.target.config)") 94 | // print("max depth: \(mock.target.config.maxLevel())") 95 | // print("Slot stride: \(MemoryLayout.stride) bytes") 96 | // // about 1 million. 97 | // let n = 0x100 98 | // let m = 0x1000 99 | // for i in 0.. 15 | 16 | private var r = GKMersenneTwisterRandomSource(seed: 0) 17 | private(set) var source = [Mock2Key:String]() 18 | private(set) var target = PDB.topLevel256Bytes() 19 | private(set) var keyStack = [Mock2Key]() 20 | private(set) var oldVersionTarget: PDB 21 | 22 | enum Command { 23 | case insertRandom 24 | case updateRandom 25 | case removeRandom 26 | static let all = [ 27 | .insertRandom, 28 | .updateRandom, 29 | .removeRandom, 30 | ] as [Command] 31 | } 32 | 33 | init() { 34 | oldVersionTarget = target 35 | } 36 | 37 | func runRandom(_ n: Int) { 38 | for I in 0...stride) bytes") 64 | mock.runRandom(163_000) 65 | let scap = mock.target.slotCap 66 | mock.target.iterateAllBuckets({ b in 67 | XCTAssertEqual(b.slotCap, scap) 68 | }) 69 | } 70 | func testMonteCarlo7() { 71 | typealias Mock = PD4BucketMock2 72 | let mock = Mock() 73 | print("config: \(mock.target.config)") 74 | print("max depth: \(mock.target.config.maxLevel())") 75 | print("Slot stride: \(MemoryLayout.stride) bytes") 76 | // n * m = 1 Mi 77 | let n = 0x100 78 | let m = 0x1000 79 | for i in 0...stride) bytes") 112 | for i in 0..<100 { 113 | mock.insertMany(10_000) 114 | print("#\(i): insert many count \(mock.target.count.metricPrefixed)") 115 | } 116 | for i in 0..<100 { 117 | mock.runRandom(10_000) 118 | print("#\(i): run random count \(mock.target.count.metricPrefixed)") 119 | } 120 | 121 | mock.removeAll() 122 | } 123 | func testMonteCarlo9() { 124 | typealias Mock = PD4BucketMock2 125 | let mock = Mock() 126 | print("config: \(mock.target.config)") 127 | print("max depth: \(mock.target.config.maxLevel())") 128 | print("Slot stride: \(MemoryLayout.stride) bytes") 129 | for i in 0..<100 { 130 | mock.insertMany(10_000) 131 | print("#\(i): count \(mock.target.count.metricPrefixed)") 132 | } 133 | 134 | // n * m = 1 Mi 135 | let n = 0x100 136 | let m = 0x1000 137 | for i in 0..>.size, MemoryLayout.size) 16 | // } 17 | 18 | func testCase2() { 19 | typealias PDB = PD4Bucket 20 | var b = PDB.topLevel256Bytes() 21 | b.set(111, "a") 22 | XCTAssertEqual(b.get(111), "a") 23 | XCTAssertEqual(b.count, 1) 24 | b.set(111, nil) 25 | XCTAssertEqual(b.get(111), nil) 26 | XCTAssertEqual(b.count, 0) 27 | 28 | b.set(111, "a") 29 | b.set(222, "b") 30 | b.set(333, "c") 31 | b.set(444, "d") 32 | b.set(555, "e") 33 | XCTAssertEqual(b.get(111), "a") 34 | XCTAssertEqual(b.get(222), "b") 35 | XCTAssertEqual(b.get(333), "c") 36 | XCTAssertEqual(b.get(444), "d") 37 | XCTAssertEqual(b.get(555), "e") 38 | b.set(111, nil) 39 | b.set(222, nil) 40 | b.set(333, nil) 41 | b.set(444, nil) 42 | b.set(555, nil) 43 | XCTAssertEqual(b.count, 0) 44 | XCTAssertEqual(b.get(111), nil) 45 | XCTAssertEqual(b.get(222), nil) 46 | XCTAssertEqual(b.get(333), nil) 47 | XCTAssertEqual(b.get(444), nil) 48 | XCTAssertEqual(b.get(555), nil) 49 | } 50 | 51 | func testMonteCarlo1() { 52 | typealias PDB = PD4Bucket 53 | var b = PDB.topLevel256Bytes() 54 | var d = [Int:String]() 55 | let r = GKMersenneTwisterRandomSource(seed: 0) 56 | for i in 0..<2 { 57 | let k = r.nextInt() 58 | let v = "value \(k) at \(i) iteration." 59 | d[k] = v 60 | b.set(k, v) 61 | let v1 = b.get(k) 62 | XCTAssertEqual(v, v1) 63 | XCTAssertEqual(d.count, b.count) 64 | } 65 | let k = r.nextInt() 66 | let v = "last value" 67 | d[k] = v 68 | b.set(k, v) 69 | let v1 = b.get(k) 70 | XCTAssertEqual(v, v1) 71 | XCTAssertEqual(d.count, b.count) 72 | } 73 | func testMonteCarlo2() { 74 | typealias PDB = PD4Bucket 75 | var b = PDB.topLevel256Bytes() 76 | var d = [Int:String]() 77 | let r = GKMersenneTwisterRandomSource(seed: 0) 78 | for i in 0..<11 { 79 | let k = r.nextInt() 80 | let v = "value \(k) at \(i) iteration." 81 | d[k] = v 82 | b.set(k, v) 83 | let v1 = b.get(k) 84 | XCTAssertEqual(v, v1) 85 | XCTAssertEqual(d.count, b.count) 86 | } 87 | let k = r.nextInt() 88 | let v = "last value" 89 | d[k] = v 90 | b.set(k, v) 91 | let v1 = b.get(k) 92 | XCTAssertEqual(v, v1) 93 | XCTAssertEqual(d.count, b.count) 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /PD4UnitTests/PD4Stat.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD4Stat.swift 3 | // PD4UnitTests 4 | // 5 | // Created by Henry on 2019/05/23. 6 | // 7 | 8 | import Foundation 9 | @testable import HAMT 10 | 11 | struct PD4Stat: CustomStringConvertible { 12 | var config = PD4BucketConfig(bucketCapInBytes: 4096, slotStrideInBytes: 64) 13 | var slotStride = 0 14 | var pairStride = 0 15 | 16 | var emptySlotCount = 0 17 | var uniqueSlotCount = 0 18 | var branchSlotCount = 0 19 | var leafSlotCount = 0 20 | 21 | var hashCollisionCount = 0 22 | 23 | var description: String { 24 | return "empty/unique/branch/leaf(collision): \(emptySlotCount.metricPrefixed)/\(uniqueSlotCount.metricPrefixed)/\(branchSlotCount.metricPrefixed)/\(leafSlotCount.metricPrefixed)(\(hashCollisionCount))" 25 | } 26 | } 27 | 28 | extension PD4Bucket { 29 | func collectStat() -> PD4Stat { 30 | var stat = PD4Stat() 31 | stat.config = config 32 | stat.slotStride = MemoryLayout.stride 33 | stat.pairStride = MemoryLayout.stride 34 | iterateAllBranchNodeSlotArrays { (a) in 35 | for s in a { 36 | switch s { 37 | case .none: stat.emptySlotCount += 1 38 | case .unique(_): stat.uniqueSlotCount += 1 39 | case .branch(_): stat.branchSlotCount += 1 40 | case .leaf(let a): 41 | stat.leafSlotCount += 1 42 | stat.hashCollisionCount += a.count 43 | } 44 | } 45 | } 46 | return stat 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /PD4UnitTests/Utilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Utilities.swift 3 | // PD4UnitTests 4 | // 5 | // Created by Henry on 2019/05/21. 6 | // Copyright © 2019 Eonil. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Int { 12 | var metricPrefixed: String { 13 | let s = 1000 14 | var a = self 15 | if a < s { return "\(a)" } 16 | a /= s 17 | if a < s { return "\(a)k" } 18 | a /= s 19 | if a < s { return "\(a)M" } 20 | a /= s 21 | if a < s { return "\(a)G" } 22 | a /= s 23 | if a < s { return "\(a)T" } 24 | a /= s 25 | if a < s { return "\(a)P" } 26 | a /= s 27 | if a < s { return "\(a)E" } 28 | a /= s 29 | if a < s { return "\(a)Z" } 30 | a /= s 31 | return "\(a)Y" 32 | } 33 | func percent(in denominator: Int) -> String { 34 | let a = Double(self) / Double(denominator) * 100 35 | return "\(Int(a))%" 36 | } 37 | func metricPrefixedNanoSeconds() -> String { 38 | let s = 1000 39 | var a = self 40 | if a < s { return "\(a)s" } 41 | a /= s 42 | if a < s { return "\(a)μs" } 43 | a /= s 44 | if a < s { return "\(a)ms" } 45 | a /= s 46 | return "\(a)s" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /PD5UnitTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /PD5UnitTests/PD5CompressedTableUnitTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD5CompressedTableUnitTests.swift 3 | // PD5UnitTests 4 | // 5 | // Created by Henry on 2019/05/24. 6 | // 7 | 8 | import Foundation 9 | import XCTest 10 | @testable import HAMT 11 | 12 | class PD5CompressedTableUnitTests: XCTestCase { 13 | func test1() { 14 | var ct = PD5CompressedTable64() 15 | for i in 0..<64 { 16 | XCTAssertEqual(Array(ct), Array(0.. 16 | typealias Pair = PD5Pair 17 | var b = PDB() 18 | XCTAssertEqual(Array(b.dfsSlots), []) 19 | b.insertOrReplace(K(1).hashBits, K(1), 1) 20 | XCTAssertEqual(Array(b.dfsSlots), [.unique(Pair(K(1),1))]) 21 | b.insertOrReplace(K(1).hashBits, K(1), 1) 22 | XCTAssertEqual(Array(b.dfsSlots), [.unique(Pair(K(1),1))]) 23 | b.insertOrReplace(K(2).hashBits, K(2), 2) 24 | XCTAssertEqual(Array(b.dfsSlots), [.unique(Pair(K(1),1)), .unique(Pair(K(2),2))]) 25 | } 26 | func test3() { 27 | typealias K = PD5MocklessTestKey 28 | typealias PDB = PD5Bucket64 29 | typealias Pair = PD5Pair 30 | var b = PDB() 31 | for i in 0..<64 { 32 | b.insertOrReplace(K(i).hashBits, K(i), i) 33 | } 34 | XCTAssertEqual(Array(b.dfsSlots).compactMap({ s in s.unique?.value }), Array(0..<64)) 35 | b.insertOrReplace(K(111).hashBits, K(111), 111) 36 | // 111 = 0b0_0110_1111 37 | // last 6 bits is 0b0_10_1111 = 47 38 | let s = b.slots.get1(index: 47) 39 | XCTAssertNotNil(s) 40 | XCTAssertNotNil(s!.branch) 41 | XCTAssertEqual(s!.branch!.slots.count, 2) 42 | let ss = s!.branch!.slots 43 | // key/value 47 moved into higher level. 44 | XCTAssertEqual(ss.get1(index: 0)?.unique?.value, 47) 45 | XCTAssertEqual(ss.get1(index: 1)?.unique?.value, 111) 46 | 47 | var it = b.dfsSlots.makeIterator() 48 | for i in 0..<47 { 49 | let s = it.next() 50 | XCTAssertEqual(s?.unique?.value, i) 51 | } 52 | do { 53 | let s = it.next() 54 | XCTAssertNotNil(s?.branch) 55 | } 56 | // Nove it should move up to first child. 57 | do { 58 | let s1 = it.next() 59 | XCTAssertEqual(s1?.unique?.value, 47) 60 | let s2 = it.next() 61 | XCTAssertEqual(s2?.unique?.value, 111) 62 | } 63 | // Now all children has been iterated. 64 | // Move down to the lower level. 65 | for i in 48..<64 { 66 | let s = it.next() 67 | XCTAssertEqual(s?.unique?.value, i) 68 | } 69 | do { 70 | let a = Array(b.dfsSlots).compactMap({ s in s.unique?.value }) 71 | let b = Array(0..<47) + [47,111] + Array(48..<64) 72 | XCTAssertEqual(a, b) 73 | } 74 | do { 75 | let a = Array(b.dfsSlots) 76 | let b = a.map({ s in s.currentLevelPairs }) 77 | do { 78 | let x = b[0] 79 | let y = Array(x) 80 | XCTAssertEqual(y.count, 1) 81 | } 82 | let c = Array(b.map({ clp in Array(clp) })) 83 | XCTAssertEqual(a.count, 64+2) 84 | XCTAssertEqual(b.count, 64+2) 85 | XCTAssertEqual(c.count, 64+2) 86 | } 87 | do { 88 | let a = Array(b.pairs).map({ $0.value }) 89 | let b = Array(0..<47) + [47,111] + Array(48..<64) 90 | XCTAssertEqual(a, b) 91 | } 92 | } 93 | func test4() { 94 | typealias K = PD5MocklessTestKey 95 | typealias SD = [K:Int] 96 | typealias PDB = PD5Bucket64 97 | var b = PDB() 98 | b[K(111)] = 222 99 | XCTAssertEqual(Array(b.pairs), [PDB.Pair(K(111), 222)]) 100 | } 101 | 102 | } 103 | 104 | private struct PD5MocklessTestKey: PD5Hashable, Hashable { 105 | var num: Int 106 | init(_ n: Int) { 107 | num = n 108 | } 109 | var hashBits: UInt { 110 | return UInt(bitPattern: num) 111 | } 112 | func hash(into h: inout Hasher) { 113 | h.combine(num) 114 | } 115 | } 116 | 117 | private extension PD5Slot64 { 118 | var unique: Pair? { 119 | switch self { 120 | case .unique(let p): return p 121 | default: return nil 122 | } 123 | } 124 | var branch: Bucket? { 125 | switch self { 126 | case .branch(let b): return b 127 | default: return nil 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /PD5UnitTests/PD5UnitTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PD5UnitTests.swift 3 | // PD5UnitTests 4 | // 5 | // Created by Henry on 2019/05/22. 6 | // 7 | 8 | import XCTest 9 | import GameKit 10 | @testable import HAMT 11 | 12 | final class PD5TestCase: XCTestCase { 13 | func test1InsertOneWithCorrectBucketStructure() { 14 | typealias K = PD5MocklessTestKey 15 | typealias SD = [K:Int] 16 | typealias PDB = PD5Bucket64 17 | var b = PDB() 18 | b[K(111)] = 222 19 | XCTAssertEqual(K(111).hashBits, 111) 20 | let ik = b.slotIndex(for: 111) 21 | XCTAssertEqual(b.slots.count, 1) 22 | XCTAssertEqual(b.slots.capacity, 64) 23 | XCTAssertEqual(b.slots.get(index: ik, default: .none), .unique(PDB.Pair(K(111),222))) 24 | for i in 0..<64 { 25 | if ik != i { 26 | XCTAssertEqual(b.slots.get(index: UInt(i), default: .none), .none) 27 | } 28 | } 29 | } 30 | 31 | func test2InsertRemoveWithCorrectBucketStructure() { 32 | typealias K = PD5MocklessTestKey 33 | typealias SD = [K:Int] 34 | typealias PDB = PD5Bucket64 35 | var d = SD() 36 | var b = PDB() 37 | for i in 0..<64 { 38 | let k = K(i) 39 | let v = i 40 | b[k] = v 41 | d[k] = v 42 | XCTAssertEqual(b.count, d.count) 43 | XCTAssertEqual(d[k], v) 44 | XCTAssertEqual(b[k], v) 45 | } 46 | do { 47 | let k = K(64) 48 | let v = 64 49 | b[k] = v 50 | d[k] = v 51 | XCTAssertEqual(b.count, d.count) 52 | XCTAssertEqual(d[k], v) 53 | XCTAssertEqual(b[k], v) 54 | switch b.slots.get(index: 0, default: .none) { 55 | case .branch(let b1): 56 | XCTAssertEqual(b1.slots.count, 2) 57 | XCTAssertEqual(b1.slots.get(index: 0, default: .none), .unique(PDB.Pair(K(0),0))) 58 | XCTAssertEqual(b1.slots.get(index: 1, default: .none), .unique(PDB.Pair(K(64),64))) 59 | default: 60 | XCTFail() 61 | } 62 | } 63 | do { 64 | let k = K(0) 65 | b[k] = nil 66 | d[k] = nil 67 | XCTAssertEqual(b.count, d.count) 68 | XCTAssertEqual(d[k], nil) 69 | XCTAssertEqual(b[k], nil) 70 | XCTAssertEqual(b.slots.get(index: 0, default: .none), .unique(PDB.Pair(K(64),64))) 71 | } 72 | } 73 | 74 | func test3InsertUpdateRemove() { 75 | typealias K = PD5MocklessTestKey 76 | typealias SD = [K:Int] 77 | typealias PDB = PD5Bucket64 78 | let r = GKMersenneTwisterRandomSource(seed: 0) 79 | var d = SD() 80 | var b = PDB() 81 | for i in 0..<10_000 { 82 | let k = K(i) 83 | let v = i 84 | b[k] = v 85 | d[k] = v 86 | XCTAssertEqual(b.count, d.count) 87 | XCTAssertEqual(d[k], v) 88 | XCTAssertEqual(b[k], v) 89 | } 90 | for i in 0..<10_000 { 91 | let k = K(r.nextInt(upperBound: 10_000)) 92 | b[k] = i 93 | d[k] = i 94 | XCTAssertEqual(b.count, d.count) 95 | XCTAssertEqual(d[k], i) 96 | XCTAssertEqual(b[k], i) 97 | } 98 | for i in 0..<10_000 { 99 | let k = K(i) 100 | b[k] = nil 101 | d[k] = nil 102 | XCTAssertEqual(b.count, d.count) 103 | XCTAssertEqual(d[k], nil) 104 | XCTAssertEqual(b[k], nil) 105 | } 106 | } 107 | func test4HAMTCounting() { 108 | 109 | } 110 | } 111 | 112 | private struct PD5MocklessTestKey: PD5Hashable, Hashable { 113 | var num: Int 114 | init(_ n: Int) { 115 | num = n 116 | } 117 | var hashBits: UInt { 118 | return UInt(bitPattern: num) 119 | } 120 | func hash(into h: inout Hasher) { 121 | h.combine(num) 122 | } 123 | } 124 | 125 | private func == (_ a: HAMT, _ b: [K:V]) -> Bool where K: Hashable, V: Equatable { 126 | for k in a.keys { 127 | guard b[k] == a[k] else { return false } 128 | } 129 | for k in b.keys { 130 | guard a[k] == b[k] else { return false } 131 | } 132 | return true 133 | } 134 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // The swift-tools-version declares the minimum version of Swift required to build this package. 3 | 4 | import PackageDescription 5 | 6 | let package = Package( 7 | name: "HAMT", 8 | platforms: [ 9 | .macOS(.v10_11), 10 | ], 11 | products: [ 12 | .library(name: "HAMT", targets: ["HAMT"]), 13 | .executable(name: "HAMTFuzz", targets: ["HAMTFuzz"]) 14 | ], 15 | dependencies: [ 16 | ], 17 | targets: [ 18 | .target(name: "HAMT", dependencies: [], path: "HAMT"), 19 | .testTarget(name: "HAMTTests", dependencies: ["HAMT"], path: "HAMTTests"), 20 | .testTarget(name: "PD5UnitTests", dependencies: ["HAMT"], path: "PD5UnitTests"), 21 | .target(name: "HAMTFuzz", dependencies: ["HAMT"], path: "HAMTFuzz"), 22 | ] 23 | ) 24 | 25 | -------------------------------------------------------------------------------- /PerfTool/BTree/Sources/BTreeBuilder.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BTreeBuilder.swift 3 | // BTree 4 | // 5 | // Created by Károly Lőrentey on 2016-02-28. 6 | // Copyright © 2016–2017 Károly Lőrentey. 7 | // 8 | 9 | 10 | extension BTree { 11 | //MARK: Bulk loading initializers 12 | 13 | /// Create a new B-tree from elements of an unsorted sequence, using a stable sort algorithm. 14 | /// 15 | /// - Parameter elements: An unsorted sequence of arbitrary length. 16 | /// - Parameter order: The desired B-tree order. If not specified (recommended), the default order is used. 17 | /// - Complexity: O(count * log(`count`)) 18 | /// - SeeAlso: `init(sortedElements:order:fillFactor:)` for a (faster) variant that can be used if the sequence is already sorted. 19 | public init(_ elements: S, dropDuplicates: Bool = false, order: Int? = nil) 20 | where S.Element == Element { 21 | let order = order ?? Node.defaultOrder 22 | self.init(Node(order: order)) 23 | withCursorAtEnd { cursor in 24 | for element in elements { 25 | cursor.move(to: element.0, choosing: .last) 26 | let match = !cursor.isAtEnd && cursor.key == element.0 27 | if match { 28 | if dropDuplicates { 29 | cursor.element = element 30 | } 31 | else { 32 | cursor.insertAfter(element) 33 | } 34 | } 35 | else { 36 | cursor.insert(element) 37 | } 38 | } 39 | } 40 | } 41 | 42 | /// Create a new B-tree from elements of a sequence sorted by key. 43 | /// 44 | /// - Parameter sortedElements: A sequence of arbitrary length, sorted by key. 45 | /// - Parameter order: The desired B-tree order. If not specified (recommended), the default order is used. 46 | /// - Parameter fillFactor: The desired fill factor in each node of the new tree. Must be between 0.5 and 1.0. 47 | /// If not specified, a value of 1.0 is used, i.e., nodes will be loaded with as many elements as possible. 48 | /// - Complexity: O(count) 49 | /// - SeeAlso: `init(elements:order:fillFactor:)` for a (slower) unsorted variant. 50 | public init(sortedElements elements: S, dropDuplicates: Bool = false, order: Int? = nil, fillFactor: Double = 1) where S.Element == Element { 51 | var iterator = elements.makeIterator() 52 | self.init(order: order ?? Node.defaultOrder, fillFactor: fillFactor, dropDuplicates: dropDuplicates, next: { iterator.next() }) 53 | } 54 | 55 | internal init(order: Int, fillFactor: Double = 1, dropDuplicates: Bool = false, next: () -> Element?) { 56 | precondition(order > 1) 57 | precondition(fillFactor >= 0.5 && fillFactor <= 1) 58 | let keysPerNode = Int(fillFactor * Double(order - 1) + 0.5) 59 | assert(keysPerNode >= (order - 1) / 2 && keysPerNode <= order - 1) 60 | 61 | var builder = BTreeBuilder(order: order, keysPerNode: keysPerNode) 62 | if dropDuplicates { 63 | guard var buffer = next() else { 64 | self.init(Node(order: order)) 65 | return 66 | } 67 | while let element = next() { 68 | precondition(buffer.0 <= element.0) 69 | if buffer.0 < element.0 { 70 | builder.append(buffer) 71 | } 72 | buffer = element 73 | } 74 | builder.append(buffer) 75 | } 76 | else { 77 | var lastKey: Key? = nil 78 | while let element = next() { 79 | precondition(lastKey == nil || lastKey! <= element.0) 80 | lastKey = element.0 81 | builder.append(element) 82 | } 83 | } 84 | self.init(builder.finish()) 85 | } 86 | } 87 | 88 | private enum BuilderState { 89 | /// The builder needs a separator element. 90 | case separator 91 | /// The builder is filling up a seedling node. 92 | case element 93 | } 94 | 95 | /// A construct for efficiently building a fully loaded B-tree from a series of elements. 96 | /// 97 | /// The bulk loading algorithm works growing a line of perfectly loaded saplings, in order of decreasing depth, 98 | /// with a separator element between each of them. 99 | /// 100 | /// Added elements are collected into a separator and a new leaf node (called the "seedling"). 101 | /// When the seedling becomes full it is appended to or recursively merged into the list of saplings. 102 | /// 103 | /// When `finish` is called, the final list of saplings plus the last partial seedling is joined 104 | /// into a single tree, which becomes the root. 105 | internal struct BTreeBuilder { 106 | typealias Node = BTreeNode 107 | typealias Element = Node.Element 108 | typealias Splinter = Node.Splinter 109 | 110 | private let order: Int 111 | private let keysPerNode: Int 112 | private var saplings: [Node] 113 | private var separators: [Element] 114 | private var seedling: Node 115 | private var state: BuilderState 116 | 117 | init(order: Int) { 118 | self.init(order: order, keysPerNode: order - 1) 119 | } 120 | 121 | init(order: Int, keysPerNode: Int) { 122 | precondition(order > 1) 123 | precondition(keysPerNode >= (order - 1) / 2 && keysPerNode <= order - 1) 124 | 125 | self.order = order 126 | self.keysPerNode = keysPerNode 127 | self.saplings = [] 128 | self.separators = [] 129 | self.seedling = Node(order: order) 130 | self.state = .element 131 | } 132 | 133 | var lastKey: Key? { 134 | switch state { 135 | case .separator: 136 | return saplings.last?.last?.0 137 | case .element: 138 | return seedling.last?.0 ?? separators.last?.0 139 | } 140 | } 141 | 142 | func isValidNextKey(_ key: Key) -> Bool { 143 | guard let last = lastKey else { return true } 144 | return last <= key 145 | } 146 | 147 | mutating func append(_ element: Element) { 148 | assert(isValidNextKey(element.0)) 149 | switch state { 150 | case .separator: 151 | separators.append(element) 152 | state = .element 153 | case .element: 154 | seedling.append(element) 155 | if seedling.count == keysPerNode { 156 | closeSeedling() 157 | state = .separator 158 | } 159 | } 160 | } 161 | 162 | private mutating func closeSeedling() { 163 | append(sapling: seedling) 164 | seedling = Node(order: order) 165 | } 166 | 167 | mutating func append(_ node: Node) { 168 | appendWithoutCloning(node.clone()) 169 | } 170 | 171 | mutating func appendWithoutCloning(_ node: Node) { 172 | assert(node.order == order) 173 | if node.isEmpty { return } 174 | assert(isValidNextKey(node.first!.0)) 175 | if node.depth == 0 { 176 | if state == .separator { 177 | assert(seedling.isEmpty) 178 | separators.append(node.elements.removeFirst()) 179 | node.count -= 1 180 | state = .element 181 | if node.isEmpty { return } 182 | seedling = node 183 | } 184 | else if seedling.count > 0 { 185 | let sep = seedling.elements.removeLast() 186 | seedling.count -= 1 187 | if let splinter = seedling.shiftSlots(separator: sep, node: node, target: keysPerNode) { 188 | closeSeedling() 189 | separators.append(splinter.separator) 190 | seedling = splinter.node 191 | } 192 | } 193 | else { 194 | seedling = node 195 | } 196 | if seedling.count >= keysPerNode { 197 | closeSeedling() 198 | state = .separator 199 | } 200 | return 201 | } 202 | 203 | if state == .element && seedling.count > 0 { 204 | let sep = seedling.elements.removeLast() 205 | seedling.count -= 1 206 | closeSeedling() 207 | separators.append(sep) 208 | } 209 | if state == .separator { 210 | let cursor = BTreeCursor(BTreeCursorPath(endOf: saplings.removeLast())) 211 | cursor.moveBackward() 212 | let separator = cursor.remove() 213 | saplings.append(cursor.finish()) 214 | separators.append(separator) 215 | } 216 | assert(seedling.isEmpty) 217 | append(sapling: node) 218 | state = .separator 219 | } 220 | 221 | private mutating func append(sapling: Node) { 222 | var sapling = sapling 223 | while !saplings.isEmpty { 224 | assert(saplings.count == separators.count) 225 | var previous = saplings.removeLast() 226 | let separator = separators.removeLast() 227 | 228 | // Join previous saplings together until they grow at least as deep as the new one. 229 | while previous.depth < sapling.depth { 230 | if saplings.isEmpty { 231 | // If the single remaining sapling is too shallow, just join it to the new sapling and call it a day. 232 | saplings.append(Node.join(left: previous, separator: separator, right: sapling)) 233 | return 234 | } 235 | previous = Node.join(left: saplings.removeLast(), separator: separators.removeLast(), right: previous) 236 | } 237 | 238 | let fullPrevious = previous.elements.count >= keysPerNode 239 | let fullSapling = sapling.elements.count >= keysPerNode 240 | 241 | if previous.depth == sapling.depth + 1 && !fullPrevious && fullSapling { 242 | // Graft node under the last sapling, as a new child branch. 243 | previous.elements.append(separator) 244 | previous.children.append(sapling) 245 | previous.count += sapling.count + 1 246 | sapling = previous 247 | } 248 | else if previous.depth == sapling.depth && fullPrevious && fullSapling { 249 | // We have two full nodes; add them as two branches of a new, deeper node. 250 | sapling = Node(left: previous, separator: separator, right: sapling) 251 | } 252 | else if previous.depth > sapling.depth || fullPrevious { 253 | // The new sapling can be appended to the line and we're done. 254 | saplings.append(previous) 255 | separators.append(separator) 256 | break 257 | } 258 | else if let splinter = previous.shiftSlots(separator: separator, node: sapling, target: keysPerNode) { 259 | // We have made the previous sapling full; add it as a new one before trying again with the remainder. 260 | assert(previous.elements.count == keysPerNode) 261 | append(sapling: previous) 262 | separators.append(splinter.separator) 263 | sapling = splinter.node 264 | } 265 | else { 266 | // We've combined the two saplings; try again with the result. 267 | sapling = previous 268 | } 269 | } 270 | saplings.append(sapling) 271 | } 272 | 273 | mutating func finish() -> Node { 274 | // Merge all saplings and the seedling into a single tree. 275 | var root: Node 276 | if separators.count == saplings.count - 1 { 277 | assert(seedling.count == 0) 278 | root = saplings.removeLast() 279 | } 280 | else { 281 | root = seedling 282 | } 283 | assert(separators.count == saplings.count) 284 | while !saplings.isEmpty { 285 | root = Node.join(left: saplings.removeLast(), separator: separators.removeLast(), right: root) 286 | } 287 | state = .element 288 | return root 289 | } 290 | } 291 | -------------------------------------------------------------------------------- /PerfTool/BTree/Sources/BTreeComparisons.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BTreeComparator.swift 3 | // BTree 4 | // 5 | // Created by Károly Lőrentey on 2016-03-04. 6 | // Copyright © 2016–2017 Károly Lőrentey. 7 | // 8 | 9 | extension BTree { 10 | //MARK: Comparison 11 | 12 | /// Return `true` iff `self` and `other` contain equivalent elements, using `isEquivalent` as the equivalence test. 13 | /// 14 | /// This method skips over shared subtrees when possible; this can drastically improve performance when the 15 | /// two trees are divergent mutations originating from the same value. 16 | /// 17 | /// - Requires: `isEquivalent` is an [equivalence relation]. 18 | /// - Complexity: O(`count`) 19 | /// 20 | /// [equivalence relation]: https://en.wikipedia.org/wiki/Equivalence_relation 21 | public func elementsEqual(_ other: BTree, by isEquivalent: (Element, Element) throws -> Bool) rethrows -> Bool { 22 | if self.root === other.root { return true } 23 | if self.count != other.count { return false } 24 | 25 | var a = BTreeStrongPath(startOf: self.root) 26 | var b = BTreeStrongPath(startOf: other.root) 27 | while !a.isAtEnd { // No need to check b: the trees have the same length, and each iteration moves equal steps in both trees. 28 | if a.node === b.node && a.slot == b.slot { 29 | // Ascend to first ancestor that isn't shared. 30 | repeat { 31 | a.ascendOneLevel() 32 | b.ascendOneLevel() 33 | } while !a.isAtEnd && a.node === b.node && a.slot == b.slot 34 | if a.isAtEnd { break } 35 | a.ascendToKey() 36 | b.ascendToKey() 37 | } 38 | if try !isEquivalent(a.element, b.element) { 39 | return false 40 | } 41 | a.moveForward() 42 | b.moveForward() 43 | } 44 | return true 45 | } 46 | } 47 | 48 | extension BTree where Value: Equatable { 49 | /// Return `true` iff `self` and `other` contain equal elements. 50 | /// 51 | /// This method skips over shared subtrees when possible; this can drastically improve performance when the 52 | /// two trees are divergent mutations originating from the same value. 53 | /// 54 | /// - Complexity: O(`count`) 55 | public func elementsEqual(_ other: BTree) -> Bool { 56 | return self.elementsEqual(other, by: { $0.0 == $1.0 && $0.1 == $1.1 }) 57 | } 58 | 59 | /// Return `true` iff `a` and `b` contain equal elements. 60 | /// 61 | /// This method skips over shared subtrees when possible; this can drastically improve performance when the 62 | /// two trees are divergent mutations originating from the same value. 63 | /// 64 | /// - Complexity: O(`count`) 65 | public static func == (a: BTree, b: BTree) -> Bool { 66 | return a.elementsEqual(b) 67 | } 68 | 69 | /// Return `true` iff `a` and `b` do not contain equal elements. 70 | /// 71 | /// This method skips over shared subtrees when possible; this can drastically improve performance when the 72 | /// two trees are divergent mutations originating from the same value. 73 | /// 74 | /// - Complexity: O(`count`) 75 | public static func != (a: BTree, b: BTree) -> Bool { 76 | return !(a == b) 77 | } 78 | } 79 | 80 | extension BTree { 81 | /// Returns true iff this tree has no elements whose keys are also in `tree`. 82 | /// 83 | /// - Complexity: 84 | /// - O(min(`self.count`, `tree.count`)) in general. 85 | /// - O(log(`self.count` + `tree.count`)) if there are only a constant amount of interleaving element runs. 86 | public func isDisjoint(with tree: BTree) -> Bool { 87 | var a = BTreeStrongPath(startOf: self.root) 88 | var b = BTreeStrongPath(startOf: tree.root) 89 | if !a.isAtEnd && !b.isAtEnd { 90 | outer: while true { 91 | if a.key == b.key { 92 | return false 93 | } 94 | while a.key < b.key { 95 | a.nextPart(until: .excluding(b.key)) 96 | if a.isAtEnd { break outer } 97 | } 98 | while b.key < a.key { 99 | b.nextPart(until: .excluding(a.key)) 100 | if b.isAtEnd { break outer } 101 | } 102 | } 103 | } 104 | return true 105 | } 106 | 107 | /// Returns true iff all keys in `self` are also in `tree`. 108 | /// 109 | /// - Complexity: 110 | /// - O(min(`self.count`, `tree.count`)) in general. 111 | /// - O(log(`self.count` + `tree.count`)) if there are only a constant amount of interleaving element runs. 112 | public func isSubset(of tree: BTree, by strategy: BTreeMatchingStrategy) -> Bool { 113 | return isSubset(of: tree, by: strategy, strict: false) 114 | } 115 | 116 | /// Returns true iff all keys in `self` are also in `tree`, 117 | /// but `tree` contains at least one key that isn't in `self`. 118 | /// 119 | /// - Complexity: 120 | /// - O(min(`self.count`, `tree.count`)) in general. 121 | /// - O(log(`self.count` + `tree.count`)) if there are only a constant amount of interleaving element runs. 122 | public func isStrictSubset(of tree: BTree, by strategy: BTreeMatchingStrategy) -> Bool { 123 | return isSubset(of: tree, by: strategy, strict: true) 124 | } 125 | 126 | /// Returns true iff all keys in `tree` are also in `self`. 127 | /// 128 | /// - Complexity: 129 | /// - O(min(`self.count`, `tree.count`)) in general. 130 | /// - O(log(`self.count` + `tree.count`)) if there are only a constant amount of interleaving element runs. 131 | public func isSuperset(of tree: BTree, by strategy: BTreeMatchingStrategy) -> Bool { 132 | return tree.isSubset(of: self, by: strategy, strict: false) 133 | } 134 | 135 | /// Returns true iff all keys in `tree` are also in `self`, 136 | /// but `self` contains at least one key that isn't in `tree`. 137 | /// 138 | /// - Complexity: 139 | /// - O(min(`self.count`, `tree.count`)) in general. 140 | /// - O(log(`self.count` + `tree.count`)) if there are only a constant amount of interleaving element runs. 141 | public func isStrictSuperset(of tree: BTree, by strategy: BTreeMatchingStrategy) -> Bool { 142 | return tree.isSubset(of: self, by: strategy, strict: true) 143 | } 144 | 145 | internal func isSubset(of tree: BTree, by strategy: BTreeMatchingStrategy, strict: Bool) -> Bool { 146 | var a = BTreeStrongPath(startOf: self.root) 147 | var b = BTreeStrongPath(startOf: tree.root) 148 | var knownStrict = false 149 | outer: while !a.isAtEnd && !b.isAtEnd { 150 | while a.key == b.key { 151 | if a.node === b.node && a.slot == b.slot { 152 | // Ascend to first ancestor that isn't shared. 153 | repeat { 154 | a.ascendOneLevel() 155 | b.ascendOneLevel() 156 | } while !a.isAtEnd && a.node === b.node && a.slot == b.slot 157 | if a.isAtEnd || b.isAtEnd { break outer } 158 | a.ascendToKey() 159 | b.ascendToKey() 160 | } 161 | let key = a.key 162 | switch strategy { 163 | case .groupingMatches: 164 | while !a.isAtEnd && a.key == key { 165 | a.nextPart(until: .including(key)) 166 | } 167 | while !b.isAtEnd && b.key == key { 168 | b.nextPart(until: .including(key)) 169 | } 170 | if a.isAtEnd || b.isAtEnd { break outer } 171 | case .countingMatches: 172 | var acount = 0 173 | while !a.isAtEnd && a.key == key { 174 | acount += a.nextPart(until: .including(key)).count 175 | } 176 | var bcount = 0 177 | while !b.isAtEnd && b.key == key { 178 | bcount += b.nextPart(until: .including(key)).count 179 | } 180 | if acount > bcount { 181 | return false 182 | } 183 | if acount < bcount { 184 | knownStrict = true 185 | } 186 | if a.isAtEnd || b.isAtEnd { break outer } 187 | } 188 | } 189 | if a.key < b.key { 190 | return false 191 | } 192 | while b.key < a.key { 193 | knownStrict = true 194 | b.nextPart(until: .excluding(a.key)) 195 | if b.isAtEnd { return false } 196 | } 197 | } 198 | 199 | if a.isAtEnd { 200 | if !b.isAtEnd { 201 | return true 202 | } 203 | } 204 | else if b.isAtEnd { 205 | return false 206 | } 207 | return !strict || knownStrict 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /PerfTool/BTree/Sources/BTreeIndex.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BTreeIndex.swift 3 | // BTree 4 | // 5 | // Created by Károly Lőrentey on 2016-02-11. 6 | // Copyright © 2015–2017 Károly Lőrentey. 7 | // 8 | 9 | /// An index into a collection that uses a B-tree for storage. 10 | /// 11 | /// BTree indices belong to a specific tree instance. Trying to use them with any other tree 12 | /// instance (including one holding the exact same elements, or one derived from a mutated version of the 13 | /// original instance) will cause a runtime error. 14 | /// 15 | /// This index satisfies `Collection`'s requirement for O(1) access, but 16 | /// it is only suitable for read-only processing -- most tree mutations will 17 | /// invalidate all existing indexes. 18 | /// 19 | /// - SeeAlso: `BTreeCursor` for an efficient way to modify a batch of values in a B-tree. 20 | public struct BTreeIndex { 21 | typealias Node = BTreeNode 22 | typealias State = BTreeWeakPath 23 | 24 | internal private(set) var state: State 25 | 26 | internal init(_ state: State) { 27 | self.state = state 28 | } 29 | 30 | /// Advance to the next index. 31 | /// 32 | /// - Requires: self is valid and not the end index. 33 | /// - Complexity: Amortized O(1). 34 | mutating func increment() { 35 | state.moveForward() 36 | } 37 | 38 | /// Advance to the previous index. 39 | /// 40 | /// - Requires: self is valid and not the start index. 41 | /// - Complexity: Amortized O(1). 42 | mutating func decrement() { 43 | state.moveBackward() 44 | } 45 | 46 | /// Advance this index by `distance` elements. 47 | /// 48 | /// - Complexity: O(log(*n*)) where *n* is the number of elements in the tree. 49 | mutating func advance(by distance: Int) { 50 | state.move(toOffset: state.offset + distance) 51 | } 52 | 53 | @discardableResult 54 | mutating func advance(by distance: Int, limitedBy limit: BTreeIndex) -> Bool { 55 | let originalDistance = limit.state.offset - state.offset 56 | if (distance >= 0 && originalDistance >= 0 && distance > originalDistance) 57 | || (distance <= 0 && originalDistance <= 0 && distance < originalDistance) { 58 | self = limit 59 | return false 60 | } 61 | state.move(toOffset: state.offset + distance) 62 | return true 63 | } 64 | } 65 | 66 | extension BTreeIndex: Comparable { 67 | /// Return true iff `a` is equal to `b`. 68 | public static func ==(a: BTreeIndex, b: BTreeIndex) -> Bool { 69 | precondition(a.state.root === b.state.root, "Indices to different trees cannot be compared") 70 | return a.state.offset == b.state.offset 71 | } 72 | 73 | /// Return true iff `a` is less than `b`. 74 | public static func <(a: BTreeIndex, b: BTreeIndex) -> Bool { 75 | precondition(a.state.root === b.state.root, "Indices to different trees cannot be compared") 76 | return a.state.offset < b.state.offset 77 | } 78 | } 79 | 80 | /// A mutable path in a B-tree, holding weak references to nodes on the path. 81 | /// This path variant does not support modifying the tree itself; it is suitable for use in indices. 82 | /// 83 | /// After a path of this kind has been created, the original tree might mutated in a way that invalidates 84 | /// the path, setting some of its weak references to nil, or breaking the consistency of its trail of slot indices. 85 | /// The path checks for this during navigation, and traps if it finds itself invalidated. 86 | /// 87 | internal struct BTreeWeakPath: BTreePath { 88 | typealias Node = BTreeNode 89 | 90 | var _root: Weak 91 | var offset: Int 92 | 93 | var _path: [Weak] 94 | var _slots: [Int] 95 | var _node: Weak 96 | var slot: Int? 97 | 98 | init(root: Node) { 99 | self._root = Weak(root) 100 | self.offset = root.count 101 | self._path = [] 102 | self._slots = [] 103 | self._node = Weak(root) 104 | self.slot = nil 105 | } 106 | 107 | var root: Node { 108 | guard let root = _root.value else { invalid() } 109 | return root 110 | } 111 | var count: Int { return root.count } 112 | var length: Int { return _path.count + 1} 113 | 114 | var node: Node { 115 | guard let node = _node.value else { invalid() } 116 | return node 117 | } 118 | 119 | internal func expectRoot(_ root: Node) { 120 | expectValid(_root.value === root) 121 | } 122 | 123 | internal func expectValid(_ expression: @autoclosure () -> Bool, file: StaticString = #file, line: UInt = #line) { 124 | precondition(expression(), "Invalid BTreeIndex", file: file, line: line) 125 | } 126 | 127 | internal func invalid(_ file: StaticString = #file, line: UInt = #line) -> Never { 128 | preconditionFailure("Invalid BTreeIndex", file: file, line: line) 129 | } 130 | 131 | mutating func popFromSlots() { 132 | assert(self.slot != nil) 133 | let node = self.node 134 | offset += node.count - node.offset(ofSlot: slot!) 135 | slot = nil 136 | } 137 | 138 | mutating func popFromPath() { 139 | assert(_path.count > 0 && slot == nil) 140 | let child = node 141 | _node = _path.removeLast() 142 | expectValid(node.children[_slots.last!] === child) 143 | slot = _slots.removeLast() 144 | } 145 | 146 | mutating func pushToPath() { 147 | assert(self.slot != nil) 148 | let child = node.children[slot!] 149 | _path.append(_node) 150 | _node = Weak(child) 151 | _slots.append(slot!) 152 | slot = nil 153 | } 154 | 155 | mutating func pushToSlots(_ slot: Int, offsetOfSlot: Int) { 156 | assert(self.slot == nil) 157 | offset -= node.count - offsetOfSlot 158 | self.slot = slot 159 | } 160 | 161 | func forEach(ascending: Bool, body: (Node, Int) -> Void) { 162 | if ascending { 163 | var child: Node? = node 164 | body(child!, slot!) 165 | for i in (0 ..< _path.count).reversed() { 166 | guard let node = _path[i].value else { invalid() } 167 | let slot = _slots[i] 168 | expectValid(node.children[slot] === child) 169 | child = node 170 | body(node, slot) 171 | } 172 | } 173 | else { 174 | for i in 0 ..< _path.count { 175 | guard let node = _path[i].value else { invalid() } 176 | let slot = _slots[i] 177 | expectValid(node.children[slot] === (i < _path.count - 1 ? _path[i + 1].value : _node.value)) 178 | body(node, slot) 179 | } 180 | body(node, slot!) 181 | } 182 | } 183 | 184 | func forEachSlot(ascending: Bool, body: (Int) -> Void) { 185 | if ascending { 186 | body(slot!) 187 | _slots.reversed().forEach(body) 188 | } 189 | else { 190 | _slots.forEach(body) 191 | body(slot!) 192 | } 193 | } 194 | } 195 | -------------------------------------------------------------------------------- /PerfTool/BTree/Sources/BTreeIterator.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BTreeIterator.swift 3 | // BTree 4 | // 5 | // Created by Károly Lőrentey on 2016-02-11. 6 | // Copyright © 2015–2017 Károly Lőrentey. 7 | // 8 | 9 | /// An iterator for all elements stored in a B-tree, in ascending key order. 10 | public struct BTreeIterator: IteratorProtocol { 11 | public typealias Element = (Key, Value) 12 | typealias Node = BTreeNode 13 | typealias State = BTreeStrongPath 14 | 15 | var state: State 16 | 17 | internal init(_ state: State) { 18 | self.state = state 19 | } 20 | 21 | /// Advance to the next element and return it, or return `nil` if no next element exists. 22 | /// 23 | /// - Complexity: Amortized O(1) 24 | public mutating func next() -> Element? { 25 | if state.isAtEnd { return nil } 26 | let result = state.element 27 | state.moveForward() 28 | return result 29 | } 30 | } 31 | 32 | /// A dummy, zero-size key that is useful in B-trees that don't need key-based lookup. 33 | internal struct EmptyKey: Comparable { 34 | internal static func ==(a: EmptyKey, b: EmptyKey) -> Bool { return true } 35 | internal static func <(a: EmptyKey, b: EmptyKey) -> Bool { return false } 36 | } 37 | 38 | /// An iterator for the values stored in a B-tree with an empty key. 39 | public struct BTreeValueIterator: IteratorProtocol { 40 | internal typealias Base = BTreeIterator 41 | private var base: Base 42 | 43 | internal init(_ base: Base) { 44 | self.base = base 45 | } 46 | 47 | /// Advance to the next element and return it, or return `nil` if no next element exists. 48 | /// 49 | /// - Complexity: Amortized O(1) 50 | public mutating func next() -> Value? { 51 | return base.next()?.1 52 | } 53 | } 54 | 55 | /// An iterator for the keys stored in a B-tree without a value. 56 | public struct BTreeKeyIterator: IteratorProtocol { 57 | internal typealias Base = BTreeIterator 58 | fileprivate var base: Base 59 | 60 | internal init(_ base: Base) { 61 | self.base = base 62 | } 63 | 64 | /// Advance to the next element and return it, or return `nil` if no next element exists. 65 | /// 66 | /// - Complexity: Amortized O(1) 67 | public mutating func next() -> Key? { 68 | return base.next()?.0 69 | } 70 | } 71 | 72 | /// A mutable path in a B-tree, holding strong references to nodes on the path. 73 | /// This path variant does not support modifying the tree itself; it is suitable for use in generators. 74 | internal struct BTreeStrongPath: BTreePath { 75 | typealias Node = BTreeNode 76 | 77 | var root: Node 78 | var offset: Int 79 | 80 | var _path: [Node] 81 | var _slots: [Int] 82 | var node: Node 83 | var slot: Int? 84 | 85 | init(root: Node) { 86 | self.root = root 87 | self.offset = root.count 88 | self._path = [] 89 | self._slots = [] 90 | self.node = root 91 | self.slot = nil 92 | } 93 | 94 | var count: Int { return root.count } 95 | var length: Int { return _path.count + 1 } 96 | 97 | mutating func popFromSlots() { 98 | assert(self.slot != nil) 99 | offset += node.count - node.offset(ofSlot: slot!) 100 | slot = nil 101 | } 102 | 103 | mutating func popFromPath() { 104 | assert(_path.count > 0 && slot == nil) 105 | node = _path.removeLast() 106 | slot = _slots.removeLast() 107 | } 108 | 109 | mutating func pushToPath() { 110 | assert(slot != nil) 111 | let child = node.children[slot!] 112 | _path.append(node) 113 | node = child 114 | _slots.append(slot!) 115 | slot = nil 116 | } 117 | 118 | mutating func pushToSlots(_ slot: Int, offsetOfSlot: Int) { 119 | assert(self.slot == nil) 120 | offset -= node.count - offsetOfSlot 121 | self.slot = slot 122 | } 123 | 124 | func forEach(ascending: Bool, body: (Node, Int) -> Void) { 125 | if ascending { 126 | body(node, slot!) 127 | for i in (0 ..< _path.count).reversed() { 128 | body(_path[i], _slots[i]) 129 | } 130 | } 131 | else { 132 | for i in 0 ..< _path.count { 133 | body(_path[i], _slots[i]) 134 | } 135 | body(node, slot!) 136 | } 137 | } 138 | 139 | func forEachSlot(ascending: Bool, body: (Int) -> Void) { 140 | if ascending { 141 | body(slot!) 142 | _slots.reversed().forEach(body) 143 | } 144 | else { 145 | _slots.forEach(body) 146 | body(slot!) 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /PerfTool/BTree/Sources/BTreePath.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BTreePath.swift 3 | // BTree 4 | // 5 | // Created by Károly Lőrentey on 2016-02-25. 6 | // Copyright © 2016–2017 Károly Lőrentey. 7 | // 8 | 9 | /// A protocol that represents a mutable path from the root of a B-tree to one of its elements. 10 | /// The extension methods defined on `BTreePath` provide a uniform way to navigate around in a B-tree, 11 | /// independent of the details of the path representation. 12 | /// 13 | /// There are three concrete implementations of this protocol: 14 | /// 15 | /// - `BTreeStrongPath` holds strong references and doesn't support modifying the tree. It is used by `BTreeIterator`. 16 | /// - `BTreeWeakPath` holds weak references and doesn't support modifying the tree. It is used by `BTreeIndex`. 17 | /// - `BTreeCursorPath` holds strong references and supports modifying the tree. It is used by `BTreeCursor`. 18 | /// 19 | /// This protocol saves us from having to maintain three slightly different variants of the same navigation methods. 20 | internal protocol BTreePath { 21 | associatedtype Key: Comparable 22 | associatedtype Value 23 | 24 | /// Create a new incomplete path focusing at the root of a tree. 25 | init(root: BTreeNode) 26 | 27 | /// The root node of the underlying B-tree. 28 | var root: BTreeNode { get } 29 | 30 | /// The current offset of this path. (This is a simple stored property. Use `move(to:)` to reposition 31 | /// the path on a different offset.) 32 | var offset: Int { get set } 33 | 34 | /// The number of elements in the tree. 35 | var count: Int { get } 36 | 37 | /// The number of nodes on the path from the root to the node that holds the focused element, including both ends. 38 | var length: Int { get } 39 | 40 | /// The final node on the path; i.e., the node that holds the currently focused element. 41 | var node: BTreeNode { get } 42 | 43 | /// The final slot on the path, or `nil` if the path is currently incomplete. 44 | var slot: Int? { get set } 45 | 46 | /// Pop the last slot in `slots`, creating an incomplete path. 47 | /// The path's `offset` is updated to the offset of the element following the subtree at the last node. 48 | mutating func popFromSlots() 49 | 50 | /// Pop the last node in an incomplete path, focusing the element following its subtree. 51 | /// This restores the path to a completed state. 52 | mutating func popFromPath() 53 | 54 | /// Push the child node before the currently focused element on the path, creating an incomplete path. 55 | mutating func pushToPath() 56 | 57 | /// Push the specified slot onto `slots`, completing the path. 58 | /// The path's `offset` is updated to the offset of the currently focused element. 59 | mutating func pushToSlots(_ slot: Int, offsetOfSlot: Int) 60 | 61 | /// Call `body` for each node and associated slot on the current path. 62 | /// If `ascending` is `true`, the calls proceed upwards, from the deepest node to the root; 63 | /// otherwise nodes are listed starting with the root down to the final path element. 64 | func forEach(ascending: Bool, body: (BTreeNode, Int) -> Void) 65 | 66 | /// Call `body` for each slot index on the way from the currently selected element up to the root node. 67 | /// If `ascending` is `true`, the calls proceed upwards, from the slot of deepest node to the root; 68 | /// otherwise slots are listed starting with the slot of the root down to the final path element. 69 | /// 70 | /// This method must not look at the nodes on the path (if this path uses weak/unowned references, 71 | /// they may have been invalidated). 72 | func forEachSlot(ascending: Bool, body: (Int) -> Void) 73 | 74 | /// Finish working with the path and return the root node. 75 | mutating func finish() -> BTreeNode 76 | } 77 | 78 | extension BTreePath { 79 | internal typealias Element = (Key, Value) 80 | internal typealias Tree = BTree 81 | internal typealias Node = BTreeNode 82 | 83 | init(startOf root: Node) { 84 | self.init(root: root, offset: 0) 85 | } 86 | 87 | init(endOf root: Node) { 88 | // The end offset can be anywhere on the rightmost path of the tree, 89 | // so let's try the spot after the last element of the root. 90 | // This can spare us O(log(n)) steps if this path is only used for reference. 91 | self.init(root: root) 92 | pushToSlots(root.elements.count, offsetOfSlot: root.count) 93 | } 94 | 95 | init(root: Node, offset: Int) { 96 | self.init(root: root) 97 | descend(toOffset: offset) 98 | } 99 | 100 | init(root: Node, key: Key, choosing selector: BTreeKeySelector) { 101 | self.init(root: root) 102 | descend(to: key, choosing: selector) 103 | } 104 | 105 | init(root: Node, slotsFrom path: Path) where Path.Key == Key, Path.Value == Value { 106 | self.init(root: root) 107 | path.forEachSlot(ascending: false) { slot in 108 | if self.slot != nil { 109 | pushToPath() 110 | } 111 | self.pushToSlots(slot) 112 | } 113 | } 114 | 115 | /// Return true iff the path contains at least one node. 116 | var isValid: Bool { return length > 0 } 117 | /// Return true iff the current position is at the start of the tree. 118 | var isAtStart: Bool { return offset == 0 } 119 | /// Return true iff the current position is at the end of the tree. 120 | var isAtEnd: Bool { return offset == count } 121 | 122 | /// Push the specified slot onto `slots`, completing the path. 123 | mutating func pushToSlots(_ slot: Int) { 124 | pushToSlots(slot, offsetOfSlot: node.offset(ofSlot: slot)) 125 | } 126 | 127 | mutating func finish() -> Node { 128 | return root 129 | } 130 | 131 | /// Return the element at the current position. 132 | var element: Element { return node.elements[slot!] } 133 | /// Return the key of the element at the current position. 134 | var key: Key { return element.0 } 135 | /// Return the value of the element at the current position. 136 | var value: Value { return element.1 } 137 | 138 | /// Move to the next element in the B-tree. 139 | /// 140 | /// - Requires: `!isAtEnd` 141 | /// - Complexity: Amortized O(1) 142 | mutating func moveForward() { 143 | precondition(offset < count) 144 | offset += 1 145 | if node.isLeaf { 146 | if slot! < node.elements.count - 1 || offset == count { 147 | slot! += 1 148 | } 149 | else { 150 | // Ascend 151 | repeat { 152 | slot = nil 153 | popFromPath() 154 | } while slot == node.elements.count 155 | } 156 | } 157 | else { 158 | // Descend 159 | slot! += 1 160 | pushToPath() 161 | while !node.isLeaf { 162 | slot = 0 163 | pushToPath() 164 | } 165 | slot = 0 166 | } 167 | } 168 | 169 | /// Move to the previous element in the B-tree. 170 | /// 171 | /// - Requires: `!isAtStart` 172 | /// - Complexity: Amortized O(1) 173 | mutating func moveBackward() { 174 | precondition(!isAtStart) 175 | offset -= 1 176 | if node.isLeaf { 177 | if slot! > 0 { 178 | slot! -= 1 179 | } 180 | else { 181 | // Ascend 182 | repeat { 183 | slot = nil 184 | popFromPath() 185 | } while slot! == 0 186 | slot! -= 1 187 | } 188 | } 189 | else { 190 | // Descend 191 | assert(!node.isLeaf) 192 | pushToPath() 193 | while !node.isLeaf { 194 | slot = node.children.count - 1 195 | pushToPath() 196 | } 197 | slot = node.elements.count - 1 198 | } 199 | } 200 | 201 | /// Move to the start of the B-tree. 202 | /// 203 | /// - Complexity: O(log(`offset`)) 204 | mutating func moveToStart() { 205 | move(toOffset: 0) 206 | } 207 | 208 | /// Move to the end of the B-tree. 209 | /// 210 | /// - Complexity: O(log(`count` - `offset`)) 211 | mutating func moveToEnd() { 212 | popFromSlots() 213 | while self.count > self.offset { 214 | popFromPath() 215 | popFromSlots() 216 | } 217 | self.descend(toOffset: self.count) 218 | } 219 | 220 | /// Move to the specified offset in the B-tree. 221 | /// 222 | /// - Complexity: O(log(*distance*)), where *distance* is the absolute difference between the desired and current 223 | /// offsets. 224 | mutating func move(toOffset offset: Int) { 225 | precondition(offset >= 0 && offset <= count) 226 | if offset == count { 227 | moveToEnd() 228 | return 229 | } 230 | // Pop to ancestor whose subtree contains the desired offset. 231 | popFromSlots() 232 | while offset < self.offset - node.count || offset >= self.offset { 233 | popFromPath() 234 | popFromSlots() 235 | } 236 | self.descend(toOffset: offset) 237 | } 238 | 239 | /// Move to the element with the specified key. 240 | /// If there are no such elements, move to the first element after `key` (or at the end of tree). 241 | /// If there are multiple such elements, `selector` determines which one to find. 242 | /// 243 | /// - Complexity: O(log(`count`)) 244 | mutating func move(to key: Key, choosing selector: BTreeKeySelector = .any) { 245 | popFromSlots() 246 | while length > 1 && !node.contains(key, choosing: selector) { 247 | popFromPath() 248 | popFromSlots() 249 | } 250 | self.descend(to: key, choosing: selector) 251 | } 252 | 253 | /// Starting from an incomplete path, descend to the element at the specified offset. 254 | mutating func descend(toOffset offset: Int) { 255 | assert(offset >= self.offset - node.count && offset <= self.offset) 256 | assert(self.slot == nil) 257 | var slot = node.slot(atOffset: offset - (self.offset - node.count)) 258 | pushToSlots(slot.index, offsetOfSlot: slot.offset) 259 | while !slot.match { 260 | pushToPath() 261 | slot = node.slot(atOffset: offset - (self.offset - node.count)) 262 | pushToSlots(slot.index, offsetOfSlot: slot.offset) 263 | } 264 | assert(self.offset == offset) 265 | assert(self.slot != nil) 266 | } 267 | 268 | /// Starting from an incomplete path, descend to the element with the specified key. 269 | mutating func descend(to key: Key, choosing selector: BTreeKeySelector) { 270 | assert(self.slot == nil) 271 | if count == 0 { 272 | pushToSlots(0) 273 | return 274 | } 275 | 276 | var match: (depth: Int, slot: Int)? = nil 277 | while true { 278 | let slot = node.slot(of: key, choosing: selector) 279 | if let m = slot.match { 280 | if node.isLeaf || selector == .any { 281 | pushToSlots(m) 282 | return 283 | } 284 | match = (depth: length, slot: m) 285 | } 286 | if node.isLeaf { 287 | if let m = match { 288 | for _ in 0 ..< length - m.depth { 289 | popFromPath() 290 | popFromSlots() 291 | } 292 | pushToSlots(m.slot) 293 | } 294 | else if slot.descend < node.elements.count { 295 | pushToSlots(slot.descend) 296 | } 297 | else { 298 | pushToSlots(slot.descend - 1) 299 | moveForward() 300 | } 301 | break 302 | } 303 | pushToSlots(slot.descend) 304 | pushToPath() 305 | } 306 | } 307 | 308 | /// Return a tuple containing a tree with all elements before the current position, 309 | /// the currently focused element, and a tree with all elements after the currrent position. 310 | /// 311 | /// - Complexity: O(log(`count`)) 312 | func split() -> (prefix: Tree, separator: Element, suffix: Tree) { 313 | precondition(!isAtEnd) 314 | var left: Node? = nil 315 | var separator: Element? = nil 316 | var right: Node? = nil 317 | forEach(ascending: true) { node, slot in 318 | if separator == nil { 319 | left = Node(node: node, slotRange: 0 ..< slot) 320 | separator = node.elements[slot] 321 | let c = node.elements.count 322 | right = Node(node: node, slotRange: slot + 1 ..< c) 323 | } 324 | else { 325 | if slot >= 1 { 326 | let l = Node(node: node, slotRange: 0 ..< slot - 1) 327 | let s = node.elements[slot - 1] 328 | left = Node.join(left: l, separator: s, right: left!) 329 | } 330 | let c = node.elements.count 331 | if slot <= c - 1 { 332 | let r = Node(node: node, slotRange: slot + 1 ..< c) 333 | let s = node.elements[slot] 334 | right = Node.join(left: right!, separator: s, right: r) 335 | } 336 | } 337 | } 338 | return (Tree(left!), separator!, Tree(right!)) 339 | } 340 | 341 | /// Return a tree containing all elements before (and not including) the current position. 342 | /// 343 | /// - Complexity: O(log(`count`)) 344 | func prefix() -> Tree { 345 | precondition(!isAtEnd) 346 | var prefix: Node? = nil 347 | forEach(ascending: true) { node, slot in 348 | if prefix == nil { 349 | prefix = Node(node: node, slotRange: 0 ..< slot) 350 | } 351 | else if slot >= 1 { 352 | let l = Node(node: node, slotRange: 0 ..< slot - 1) 353 | let s = node.elements[slot - 1] 354 | prefix = Node.join(left: l, separator: s, right: prefix!) 355 | } 356 | } 357 | return Tree(prefix!) 358 | } 359 | 360 | /// Return a tree containing all elements after (and not including) the current position. 361 | /// 362 | /// - Complexity: O(log(`count`)) 363 | func suffix() -> Tree { 364 | precondition(!isAtEnd) 365 | var suffix: Node? = nil 366 | forEach(ascending: true) { node, slot in 367 | if suffix == nil { 368 | let c = node.elements.count 369 | suffix = Node(node: node, slotRange: slot + 1 ..< c) 370 | return 371 | } 372 | let c = node.elements.count 373 | if slot <= c - 1 { 374 | let r = Node(node: node, slotRange: slot + 1 ..< c) 375 | let s = node.elements[slot] 376 | suffix = Node.join(left: suffix!, separator: s, right: r) 377 | } 378 | } 379 | return Tree(suffix!) 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /PerfTool/BTree/Sources/BridgedList.swift: -------------------------------------------------------------------------------- 1 | // 2 | // BridgedList.swift 3 | // BTree 4 | // 5 | // Created by Károly Lőrentey on 2016-08-10. 6 | // Copyright © 2016–2017 Károly Lőrentey. 7 | // 8 | 9 | import Foundation 10 | 11 | extension List where Element: AnyObject { 12 | /// Return a view of this list as an immutable `NSArray`, without copying elements. 13 | /// This is useful when you want to use `List` values in Objective-C APIs. 14 | /// 15 | /// - Complexity: O(1) 16 | public var arrayView: NSArray { 17 | return BridgedList(self.tree) 18 | } 19 | } 20 | 21 | internal final class BridgedListEnumerator: NSEnumerator { 22 | var iterator: BTree.Iterator 23 | init(iterator: BTree.Iterator) { 24 | self.iterator = iterator 25 | super.init() 26 | } 27 | 28 | public override func nextObject() -> Any? { 29 | return iterator.next()?.1 30 | } 31 | } 32 | 33 | internal class BridgedList: NSArray { 34 | var tree = BTree() 35 | 36 | convenience init(_ tree: BTree) { 37 | self.init() 38 | self.tree = tree 39 | } 40 | 41 | public override func copy(with zone: NSZone? = nil) -> Any { 42 | return self 43 | } 44 | 45 | override var count: Int { 46 | return tree.count 47 | } 48 | 49 | public override func object(at index: Int) -> Any { 50 | return tree.element(atOffset: index).1 51 | } 52 | 53 | public override func objectEnumerator() -> NSEnumerator { 54 | return BridgedListEnumerator(iterator: tree.makeIterator()) 55 | } 56 | 57 | public override func countByEnumerating(with state: UnsafeMutablePointer, objects buffer: AutoreleasingUnsafeMutablePointer, count len: Int) -> Int { 58 | precondition(MemoryLayout<(EmptyKey, Value)>.size == MemoryLayout.size) 59 | precondition(MemoryLayout<(EmptyKey, Value)>.stride == MemoryLayout.stride) 60 | precondition(MemoryLayout<(EmptyKey, Value)>.alignment == MemoryLayout.alignment) 61 | 62 | var s = state.pointee 63 | if s.state >= UInt(tree.count) { 64 | return 0 65 | } 66 | let path = BTreeStrongPath(root: tree.root, offset: Int(s.state)) 67 | let node = path.node 68 | let slot = path.slot! 69 | if node.isLeaf { 70 | precondition(slot != node.elements.count) 71 | let c = node.elements.count - slot 72 | node.elements.withUnsafeBufferPointer { p in 73 | s.itemsPtr = AutoreleasingUnsafeMutablePointer(UnsafeMutablePointer(mutating: p.baseAddress!) + slot) 74 | s.state += UInt(c) 75 | } 76 | state.pointee = s 77 | return c 78 | } 79 | 80 | buffer.pointee = node.elements[slot].1 as AnyObject 81 | s.itemsPtr = buffer 82 | s.state += 1 83 | state.pointee = s 84 | return 1 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /PerfTool/BTree/Sources/Compatibility.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Compatibility.swift 3 | // BTree 4 | // 5 | // Created by Károly Lőrentey on 2016-11-05. 6 | // Copyright © 2016–2017 Károly Lőrentey. 7 | // 8 | 9 | private func unavailable() -> Never { 10 | fatalError("Unavailable function cannot be called") 11 | } 12 | 13 | extension BTree { 14 | @available(*, unavailable, renamed: "union(_:by:)", 15 | message: "Use union with the .groupingMatches strategy instead.") 16 | public func distinctUnion(_ other: BTree) -> BTree { unavailable() } 17 | } 18 | -------------------------------------------------------------------------------- /PerfTool/BTree/Sources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | $(VERSION_STRING) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | NSHumanReadableCopyright 24 | Copyright © 2015–2017 Károly Lőrentey. 25 | NSPrincipalClass 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /PerfTool/BTree/Sources/Weak.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Weak.swift 3 | // BTree 4 | // 5 | // Created by Károly Lőrentey on 2016-02-11. 6 | // Copyright © 2015–2017 Károly Lőrentey. 7 | // 8 | 9 | internal struct Weak { 10 | weak var value: T? 11 | 12 | init(_ value: T) { 13 | self.value = value 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /PerfTool/CRUD1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eonil/swift-hamt/c5f4f787429dca91dbd743fad071a0ce80625fb8/PerfTool/CRUD1.png -------------------------------------------------------------------------------- /PerfTool/DB.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DB.swift 3 | // PD4PerfTool 4 | // 5 | // Created by Henry on 2019/05/22. 6 | // 7 | 8 | import Foundation 9 | 10 | struct MultiSample { 11 | private var samples = [Double]() 12 | mutating func push(_ v: Double) { 13 | samples.append(v) 14 | } 15 | var average: Double? { 16 | guard samples.count > 0 else { return nil } 17 | return samples.reduce(0, +) / Double(samples.count) 18 | } 19 | } 20 | enum DBColumnName: CustomStringConvertible { 21 | case stdGet 22 | case stdInsert 23 | case stdUpdate 24 | case stdRemove 25 | 26 | case btreeInsert 27 | case btreeGet 28 | case btreeUpdate 29 | case btreeRemove 30 | 31 | case pd5Get 32 | case pd5Insert 33 | case pd5Update 34 | case pd5Remove 35 | 36 | var description: String { 37 | switch self { 38 | case .stdGet: return "Swift.Dictionary Get" 39 | case .stdInsert: return "Swift.Dictionary Insert" 40 | case .stdUpdate: return "Swift.Dictionary Update" 41 | case .stdRemove: return "Swift.Dictionary Remove" 42 | case .btreeGet: return "B-Tree Get" 43 | case .btreeInsert: return "B-Tree Insert" 44 | case .btreeUpdate: return "B-Tree Update" 45 | case .btreeRemove: return "B-Tree Remove" 46 | case .pd5Get: return "HAMT Get" 47 | case .pd5Insert: return "HAMT Insert" 48 | case .pd5Update: return "HAMT Update" 49 | case .pd5Remove: return "HAMT Remove" 50 | } 51 | } 52 | } 53 | struct DB { 54 | typealias Name = DBColumnName 55 | private let iterationCount: Int 56 | private var runSamples = [Name: [MultiSample]]() 57 | private var maxSampleCount = 0 58 | 59 | init(iterationCount c: Int) { 60 | iterationCount = c 61 | } 62 | mutating func push(name n: Name, samples newvs: [Double]) { 63 | let oldvs = runSamples[n] ?? [] 64 | var newvs1 = [MultiSample]() 65 | for (i,newv) in newvs.enumerated() { 66 | var newv1 = oldvs.at(i) ?? MultiSample() 67 | if newv >= 0 { 68 | newv1.push(newv) 69 | } 70 | newvs1.append(newv1) 71 | } 72 | runSamples[n] = newvs1 73 | maxSampleCount = max(maxSampleCount,newvs1.count) 74 | } 75 | func print() { 76 | let ns = [ 77 | .stdGet, .stdInsert, .stdUpdate, .stdRemove, 78 | .btreeGet, .btreeInsert, .btreeUpdate, .btreeRemove, 79 | .pd5Get, .pd5Insert, .pd5Update, .pd5Remove, 80 | ] as [Name] 81 | Swift.print(ns.map({ n in "\(n)" }).joined(separator: ",")) 82 | Swift.print((0.. Element? { 93 | guard i < count else { return nil } 94 | return self[i] 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /PerfTool/FormattingUtilities.swift: -------------------------------------------------------------------------------- 1 | // 2 | // FormattingUtility.swift 3 | // PD4PerfTool 4 | // 5 | // Created by Henry on 2019/05/22. 6 | // Copyright © 2019 Eonil. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | extension Int { 12 | var metricPrefixed: String { 13 | let s = 1000 14 | var a = self 15 | if a < s { return "\(a)" } 16 | a /= s 17 | if a < s { return "\(a)k" } 18 | a /= s 19 | if a < s { return "\(a)M" } 20 | a /= s 21 | if a < s { return "\(a)G" } 22 | a /= s 23 | if a < s { return "\(a)T" } 24 | a /= s 25 | if a < s { return "\(a)P" } 26 | a /= s 27 | if a < s { return "\(a)E" } 28 | a /= s 29 | if a < s { return "\(a)Z" } 30 | a /= s 31 | return "\(a)Y" 32 | } 33 | func percent(in denominator: Int) -> String { 34 | let a = Double(self) / Double(denominator) * 100 35 | return "\(Int(a))%" 36 | } 37 | func metricPrefixedNanoSeconds() -> String { 38 | let s = 1000 39 | var a = self 40 | if a < s { return "\(a)ns" } 41 | a /= s 42 | if a < s { return "\(a)μs" } 43 | a /= s 44 | if a < s { return "\(a)ms" } 45 | a /= s 46 | return "\(a)s" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /PerfTool/Get1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eonil/swift-hamt/c5f4f787429dca91dbd743fad071a0ce80625fb8/PerfTool/Get1.png -------------------------------------------------------------------------------- /PerfTool/Perf.numbers: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/eonil/swift-hamt/c5f4f787429dca91dbd743fad071a0ce80625fb8/PerfTool/Perf.numbers -------------------------------------------------------------------------------- /PerfTool/main.swift: -------------------------------------------------------------------------------- 1 | // 2 | // main.swift 3 | // PD4PerfTool 4 | // 5 | // Created by Henry on 2019/05/22. 6 | // Copyright © 2019 Eonil. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import GameKit 11 | 12 | let averageCount = 10 13 | let outerLoopCount = 1_00 14 | let innerLoopCount = 1_000 15 | 16 | /// 17 | /// returns list of nanoseconds for each outer iteration. 18 | /// 19 | func run(_ single_op: (Int) -> Void) -> [Double] { 20 | var data = [Double]() 21 | for i in 0.. 50_000_000 { 37 | print(" iteration takes over 50ms. and too slow. cancel test.") 38 | return data 39 | } 40 | } 41 | return data 42 | } 43 | 44 | func clearCache() { 45 | do { 46 | // Clear cache. 47 | var a = [UInt32](repeating: 0, count: 1024 * 1024 * 128) 48 | for i in 0.. Value? { get set } 67 | } 68 | extension Dictionary: AAPerfMeasuringProtocol {} 69 | extension Map: AAPerfMeasuringProtocol {} 70 | extension HAMT: AAPerfMeasuringProtocol {} 71 | 72 | struct CRUDNames { 73 | var get: DB.Name 74 | var insert: DB.Name 75 | var update: DB.Name 76 | var remove: DB.Name 77 | } 78 | 79 | func runCRUDPackage(_: T.Type, _ ns: CRUDNames) where T: AAPerfMeasuringProtocol, T.Key == Int, T.Value == Int { 80 | for i in 0...self, CRUDNames( 176 | get: .stdGet, 177 | insert: .stdInsert, 178 | update: .stdUpdate, 179 | remove: .stdRemove)) 180 | runCRUDPackage(Map.self, CRUDNames( 181 | get: .btreeGet, 182 | insert: .btreeInsert, 183 | update: .btreeUpdate, 184 | remove: .btreeRemove)) 185 | runCRUDPackage(HAMT.self, CRUDNames( 186 | get: .pd5Get, 187 | insert: .pd5Insert, 188 | update: .pd5Update, 189 | remove: .pd5Remove)) 190 | db.print() 191 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | HAMT (for Swift) 2 | ============= 3 | An implementation of [*HAMT(Hash Array Mapped Trie, Bagwell)*](https://en.wikipedia.org/wiki/Hash_array_mapped_trie) in Swift. 4 | Eonil, May 2019. 5 | 6 | [![Build Status](https://api.travis-ci.org/eonil/swift-hamt.svg)](https://travis-ci.org/eonil/swift-hamt) 7 | 8 | 9 | 10 | Getting Started 11 | ------------------ 12 | Use `HAMT` type. This type provides these features. 13 | 14 | - Hash-based key-value storage. 15 | - All of read/write/copy `amortized O(log(n))` time up to certain number of elements 16 | (see [Performance](#Performance) section), and `O(n)` at worst. 17 | - Full ["Copy-on-Write"](https://en.wikipedia.org/wiki/Copy-on-write) behavior 18 | with minimal amount of copying. 19 | 20 | The type provides these dictionary-like interfaces. 21 | 22 | - Conformance to `Sequence` protocol. 23 | - Conformance to `Equatable` protocol. 24 | - `isEmpty: Bool` 25 | - `count: Int` 26 | - `subscript(Key) -> Value? { get set }` 27 | - `subscript(Key, default: @autoclosure () -> Value) -> Value { get set }` 28 | - `keys: Sequence` 29 | - `values: Sequence` 30 | 31 | These features are not supported (maybe yet). 32 | 33 | - `Index` and index based look-up and iteration. 34 | - Any other collection protocol conformance. 35 | 36 | 37 | Copy-on-Write Persistence 38 | --------------------------------------------- 39 | As like most Swift datastrctures, `HAMT` is also fully CoW compliant. This means 40 | each copy of same HAMT tree shares data as much as much possible. Regardles 41 | of how many copies you make, HAMT shares most portion of tree with all other 42 | copies. 43 | 44 | 45 | 46 | Performance 47 | ---------------- 48 | `HAMT` type in this library is designed to be used as 49 | [*persistent datastructure*](https://en.wikipedia.org/wiki/Persistent_data_structure). 50 | 51 | In copy-persistent scenario under 64-bit environment, 52 | `HAMT` provides near constant time (`O(log(n)) & max depth=10, -> O(10)`) single element read/write/copy 53 | performance up to hash resolution limit (`(2^6)^10` items) regardless of contained item 54 | count if hash function is well distributed. Also new copy does not take extra space unless 55 | gets mutated. Copy with single element mutation takes `O(log(n))` extra time and space. 56 | On the other hand, copying `Swift.Dictionary` takes `O(n)` time and extra space. 57 | 58 | Instead, single element read/write of `HAMT` is about 2x/50x times slower 59 | than ephemeral `Swift.Dictionary` for random 64-bit integer keys and values. 60 | 61 | ![Get Performance](PerfTool/Get1.png) 62 | 63 | Note that "operation count" in above graph is accumulated number. 64 | 65 | Here's another performance comparison with copying B-Tree. 66 | Naive `Swift.Dictionary` is not drawn here because read/write performance 67 | is same with ephemeral one, and copying it takes too much time and didn't finish. 68 | 69 | ![CRUD Performance](PerfTool/CRUD1.png) 70 | 71 | For small dataset, naive copying of `Swift.Dictionary` works better, but as 72 | copying cost increases linearly, it is no longer efficient after 1,000 items. 73 | 74 | Therefore, `HAMT` is better if you need a hash-based persistent associative array 75 | data structure that can grow more than several thousands. 76 | 77 | B-Tree shows better write performance overall. HAMT performs better after 100K 78 | items, but it doesn't seem to be really practical numbers. And it requires keys 79 | to be `Comparable`. By the way, as B-Tree doesn't have hash collision, it'll show 80 | more reliable performance. 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | Maintenance 89 | --------------- 90 | `HAMT` type is implemented using `PD5Bucket64` internally. 91 | `PD5Bucket64` type provides all additional properties for testing and 92 | validation. 93 | `PD4` type was an implementation of hash-trie, and deprecated due to 94 | high rate of wasted memory. `PD5` implements HAMT and shows nearly 95 | same performance with `PD4` with far less memory consumption. 96 | 97 | I used `PD5` prefix for convenience only for internals. Public major type 98 | name is `HAMT`, and internal types all use `PD5` prefixed. If I implement 99 | a next version of algorithm, it'll be named as `PD6`. 100 | 101 | If once implementation gets stabilized, maybe I'll rename all `PDx` prefixes 102 | to `HAMT` someday. 103 | 104 | At this point, only 64-bit platforms are considered. It should work on 105 | 32-bit platforms but less performance and have not been tested. 106 | 107 | 108 | 109 | Caution! 110 | ---------- 111 | If you link this library, you'll notice the performance is not good as shown 112 | in the graph. [As like Károly Lőrentey clarified](https://github.com/attaswift/BTree#generics), 113 | it's because Swift compiler does not inline and optimize over externally 114 | linked functions. 115 | You can compile HAMT source code with your code together in same 116 | module to archive best possible performance. 117 | 118 | 119 | 120 | Credits 121 | --------- 122 | - See also ["B-Tree for Swift" by Károly Lőrentey](https://github.com/attaswift/BTree) 123 | if you need sorted associative array. 124 | 125 | - Here's a [nice explanation of how HAMT works by Marek](https://idea.popcount.org/2012-07-25-introduction-to-hamt/). 126 | 127 | - For more information about HAMT, see 128 | [the paper by Phil Bagwell](https://infoscience.epfl.ch/record/64398/files/idealhashtrees.pdf). 129 | 130 | 131 | 132 | Contribution 133 | --------------- 134 | Sending contribution means implicit agreement to redistribute 135 | your contribution under "MIT License". 136 | 137 | 138 | 139 | License 140 | ---------- 141 | This code is licensed under "MIT License". 142 | Copyright Eonil, Hoon H.. 2019. 143 | All rights reserved. 144 | -------------------------------------------------------------------------------- /test.zsh: -------------------------------------------------------------------------------- 1 | #!/bin/env zsh 2 | 3 | rm -rf .build build 4 | swift package clean 5 | swift build 6 | swift test 7 | 8 | --------------------------------------------------------------------------------