├── .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 | [](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 | 
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 | 
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 |
--------------------------------------------------------------------------------