Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
| 2 // for details. All rights reserved. Use of this source code is governed by a | |
| 3 // BSD-style license that can be found in the LICENSE file. | |
| 4 | |
| 5 library index.btree; | |
| 6 | |
| 7 | |
| 8 /** | |
| 9 * A simple B+Tree implementation. | |
|
Brian Wilkerson
2014/06/07 16:34:02
"B+Tree" --> "B-Tree"
scheglov
2014/06/07 21:26:22
It is actually not a mistake.
It is a B+Tree flavo
Brian Wilkerson
2014/06/07 23:30:38
Cool. I learned something new today. Interestingly
| |
| 10 */ | |
| 11 class BTree<K, V> { | |
| 12 /** | |
| 13 * The [Comparator] to compare keys. | |
| 14 */ | |
| 15 final Comparator<K> _comparator; | |
| 16 | |
| 17 /** | |
| 18 * The maximum number of keys in an index node. | |
| 19 */ | |
| 20 final int _maxIndexKeys; | |
| 21 | |
| 22 /** | |
| 23 * The maximum number of keys in a leaf node. | |
| 24 */ | |
| 25 final int _maxLeafKeys; | |
| 26 | |
| 27 /** | |
| 28 * The root node. | |
| 29 */ | |
| 30 _Node<K, V> _root; | |
| 31 | |
| 32 BTree(this._maxIndexKeys, this._maxLeafKeys, this._comparator) { | |
| 33 _root = new _LNode(_maxLeafKeys, _comparator); | |
| 34 } | |
| 35 | |
| 36 /** | |
| 37 * Returns the value for [key] or `null` if [key] is not in the tree. | |
| 38 */ | |
| 39 V find(K key) { | |
| 40 return _root.find(key); | |
| 41 } | |
| 42 | |
| 43 /** | |
| 44 * Associates the [key] with the given [value]. | |
| 45 * | |
| 46 * If the key was already in the tree, its associated value is changed. | |
| 47 * Otherwise the key-value pair is added to the tree. | |
| 48 */ | |
| 49 void insert(K key, V value) { | |
| 50 _Split<K, V> result = _root.insert(key, value); | |
| 51 if (result != null) { | |
| 52 _INode<K, V> newRoot = new _INode<K, V>(_maxIndexKeys, _comparator); | |
| 53 newRoot.keys.add(result.key); | |
| 54 newRoot.children.add(result.left); | |
| 55 newRoot.children.add(result.right); | |
| 56 _root = newRoot; | |
| 57 } | |
| 58 } | |
| 59 | |
| 60 noSuchMethod(Invocation invocation) { | |
| 61 if (invocation.memberName == #debugPrint) { | |
|
Brian Wilkerson
2014/06/07 16:34:02
Why not just make debugPrint a public method? Does
scheglov
2014/06/07 21:26:22
OK, I think we can make it public again.
| |
| 62 _debugPrint(invocation.positionalArguments[0]); | |
| 63 return null; | |
| 64 } | |
| 65 return super.noSuchMethod(invocation); | |
| 66 } | |
| 67 | |
| 68 /** | |
| 69 * Removes the association for the given [key]. | |
| 70 * | |
| 71 * Returns the value associated with [key] in the tree or `null` if [key] is | |
| 72 * not in the tree. | |
| 73 */ | |
| 74 V remove(K key) { | |
| 75 _Remove<K, V> result = _root.remove(key, null, null, null); | |
| 76 if (_root is _INode<K, V>) { | |
| 77 List<_Node<K, V>> children = (_root as _INode<K, V>).children; | |
| 78 if (children.length == 1) { | |
| 79 _root = children[0]; | |
| 80 } | |
| 81 } | |
| 82 return result.value; | |
| 83 } | |
| 84 | |
| 85 /** | |
| 86 * Prints the debug presentation of the tree into [buffer]. | |
| 87 */ | |
| 88 void _debugPrint(StringBuffer buffer) { | |
|
Brian Wilkerson
2014/06/07 16:34:02
Is there a reason why this isn't the implementatio
scheglov
2014/06/07 21:26:22
IMHO toString() is pretty dangerous to override fo
| |
| 89 _root._debugPrint(buffer, ''); | |
| 90 } | |
| 91 } | |
| 92 | |
| 93 | |
| 94 /** | |
| 95 * An index node with keys and children references. | |
| 96 */ | |
| 97 class _INode<K, V> extends _Node<K, V> { | |
|
Brian Wilkerson
2014/06/07 16:34:02
I have a strong preference for using full names, s
scheglov
2014/06/07 21:26:22
Done.
| |
| 98 final List<_Node<K, V>> children = new List<_Node<K, V>>(); | |
| 99 final int maxKeys; | |
| 100 final int minKeys; | |
| 101 | |
| 102 _INode(int maxKeys, Comparator<K> comparator) | |
| 103 : super(comparator), | |
| 104 maxKeys = maxKeys, | |
| 105 minKeys = maxKeys ~/ 2; | |
| 106 | |
| 107 @override | |
| 108 V find(K key) { | |
| 109 int index = findChildIndex(key); | |
| 110 return children[index].find(key); | |
| 111 } | |
| 112 | |
| 113 /** | |
| 114 * Returns the index of the child into which [key] should be inserted. | |
| 115 */ | |
| 116 int findChildIndex(K key) { | |
| 117 for (int i = 0; i < keys.length; i++) { | |
| 118 if (comparator(keys[i], key) > 0) { | |
| 119 return i; | |
| 120 } | |
| 121 } | |
| 122 return keys.length; | |
| 123 } | |
| 124 | |
| 125 _Split insert(K key, V value) { | |
|
Brian Wilkerson
2014/06/07 16:34:02
"_Split" --> "_Split<K, V>"
scheglov
2014/06/07 21:26:22
Done.
| |
| 126 // Early split. | |
| 127 if (keys.length == maxKeys) { | |
| 128 int middle = (maxKeys + 1) ~/ 2; | |
| 129 K splitKey = keys[middle]; | |
| 130 _INode<K, V> sibling = new _INode<K, V>(maxKeys, comparator); | |
| 131 sibling.keys.addAll(keys.getRange(middle + 1, keys.length)); | |
| 132 sibling.children.addAll(children.getRange(middle + 1, children.length)); | |
| 133 keys.length = middle; | |
| 134 children.length = middle + 1; | |
| 135 // Prepare split. | |
| 136 _Split<K, V> result = new _Split<K, V>(splitKey, this, sibling); | |
| 137 if (comparator(key, result.key) < 0) { | |
| 138 insertNotFull(key, value); | |
| 139 } else { | |
| 140 sibling.insertNotFull(key, value); | |
| 141 } | |
| 142 return result; | |
| 143 } | |
| 144 // No split. | |
| 145 insertNotFull(key, value); | |
| 146 return null; | |
| 147 } | |
| 148 | |
| 149 void insertNotFull(K key, V value) { | |
| 150 int index = findChildIndex(key); | |
| 151 _Split<K, V> result = children[index].insert(key, value); | |
| 152 if (result != null) { | |
| 153 keys.insert(index, result.key); | |
| 154 children[index] = result.left; | |
| 155 children.insert(index + 1, result.right); | |
| 156 } | |
| 157 } | |
| 158 | |
| 159 @override | |
| 160 _Remove<K, V> remove(K key, _Node<K, V> left, K anchor, _Node<K, V> right) { | |
| 161 int index = findChildIndex(key); | |
| 162 K thisAnchor = index == 0 ? keys[0] : keys[index - 1]; | |
| 163 _Node<K, V> child = children[index]; | |
| 164 bool hasLeft = index != 0; | |
| 165 bool hasRight = index < children.length - 1; | |
| 166 _Node<K, V> leftChild = hasLeft ? children[index - 1] : null; | |
| 167 _Node<K, V> rightChild = hasRight ? children[index + 1] : null; | |
| 168 // Ask child to remove. | |
| 169 _Remove<K, V> result = child.remove(key, leftChild, thisAnchor, rightChild); | |
| 170 V value = result.value; | |
| 171 if (value == null) { | |
| 172 return new _Remove<K, V>(value); | |
| 173 } | |
| 174 // Update anchor if borrowed. | |
| 175 if (result.leftAnchor != null) { | |
| 176 keys[index - 1] = result.leftAnchor; | |
| 177 } | |
| 178 if (result.rightAnchor != null) { | |
| 179 keys[index] = result.rightAnchor; | |
| 180 } | |
| 181 // Update keys / children if merged. | |
| 182 if (result.mergedLeft) { | |
| 183 keys.removeAt(index - 1); | |
| 184 children.removeAt(index); | |
| 185 } | |
| 186 if (result.mergedRight) { | |
| 187 keys.removeAt(index); | |
| 188 children.removeAt(index); | |
| 189 } | |
| 190 // Perform balancing. | |
| 191 if (keys.length < minKeys) { | |
| 192 // Try left sibling. | |
| 193 if (left is _INode<K, V>) { | |
| 194 // Try to redistribute. | |
| 195 int leftLength = left.keys.length; | |
| 196 if (leftLength > minKeys) { | |
| 197 int halfExcess = (leftLength - minKeys + 1) ~/ 2; | |
| 198 int newLeftLength = leftLength - halfExcess; | |
| 199 keys.insert(0, anchor); | |
| 200 keys.insertAll(0, left.keys.getRange(newLeftLength, leftLength)); | |
| 201 children.insertAll(0, left.children.getRange(newLeftLength, leftLength | |
| 202 + 1)); | |
| 203 K newAnchor = left.keys[newLeftLength - 1]; | |
| 204 left.keys.length = newLeftLength - 1; | |
| 205 left.children.length = newLeftLength; | |
| 206 return new _Remove<K, V>.borrowLeft(value, newAnchor); | |
| 207 } | |
| 208 // Do merge. | |
| 209 left.keys.add(anchor); | |
| 210 left.keys.addAll(keys); | |
| 211 left.children.addAll(children); | |
| 212 return new _Remove<K, V>.mergeLeft(value); | |
| 213 } | |
| 214 // Try right sibling. | |
| 215 if (right is _INode<K, V>) { | |
| 216 // Try to redistribute. | |
| 217 var rightLength = right.keys.length; | |
| 218 if (rightLength > minKeys) { | |
| 219 int halfExcess = (rightLength - minKeys + 1) ~/ 2; | |
| 220 keys.add(anchor); | |
| 221 keys.addAll(right.keys.getRange(0, halfExcess - 1)); | |
| 222 children.addAll(right.children.getRange(0, halfExcess)); | |
| 223 K newAnchor = right.keys[halfExcess - 1]; | |
| 224 right.keys.removeRange(0, halfExcess); | |
| 225 right.children.removeRange(0, halfExcess); | |
| 226 return new _Remove<K, V>.borrowRight(value, newAnchor); | |
| 227 } | |
| 228 // Do merge. | |
| 229 right.keys.insert(0, anchor); | |
| 230 right.keys.insertAll(0, keys); | |
| 231 right.children.insertAll(0, children); | |
| 232 return new _Remove<K, V>.mergeRight(value); | |
| 233 } | |
| 234 } | |
| 235 // No balancing required. | |
| 236 return new _Remove<K, V>(value); | |
| 237 } | |
| 238 | |
| 239 @override | |
| 240 void _debugPrint(StringBuffer buffer, String indent) { | |
| 241 buffer.write(indent); | |
| 242 buffer.write('INode {\n'); | |
| 243 for (int i = 0; i < keys.length; i++) { | |
| 244 children[i]._debugPrint(buffer, indent + ' '); | |
| 245 buffer.write(indent); | |
| 246 buffer.write(' '); | |
| 247 buffer.write(keys[i]); | |
| 248 buffer.write('\n'); | |
| 249 } | |
| 250 children[keys.length]._debugPrint(buffer, indent + ' '); | |
| 251 buffer.write(indent); | |
| 252 buffer.write('}\n'); | |
| 253 } | |
| 254 } | |
| 255 | |
| 256 | |
| 257 /** | |
| 258 * A leaf node with keys and values. | |
| 259 */ | |
| 260 class _LNode<K, V> extends _Node<K, V> { | |
| 261 final int maxKeys; | |
| 262 final int minKeys; | |
| 263 | |
| 264 /** | |
| 265 * The list of values. | |
| 266 */ | |
| 267 final List<V> values = new List<V>(); | |
| 268 | |
| 269 _LNode(int maxKeys, Comparator<K> comparator) | |
| 270 : super(comparator), | |
| 271 maxKeys = maxKeys, | |
| 272 minKeys = maxKeys ~/ 2; | |
| 273 | |
| 274 @override | |
| 275 V find(K key) { | |
| 276 int index = findKeyIndex(key); | |
| 277 if (index < 0) { | |
| 278 return null; | |
| 279 } | |
| 280 if (index >= keys.length) { | |
| 281 return null; | |
| 282 } | |
| 283 if (keys[index] != key) { | |
| 284 return null; | |
| 285 } | |
| 286 return values[index]; | |
| 287 } | |
| 288 | |
| 289 /** | |
| 290 * Returns the index where [key] should be inserted. | |
| 291 */ | |
| 292 int findKeyIndex(K key) { | |
| 293 for (int i = 0; i < keys.length; i++) { | |
| 294 if (comparator(keys[i], key) >= 0) { | |
| 295 return i; | |
| 296 } | |
| 297 } | |
| 298 return keys.length; | |
| 299 } | |
| 300 | |
| 301 _Split<K, V> insert(K key, V value) { | |
| 302 int index = findKeyIndex(key); | |
| 303 // The node is full. | |
| 304 if (keys.length == maxKeys) { | |
| 305 int middle = (maxKeys + 1) ~/ 2; | |
| 306 _LNode<K, V> sibling = new _LNode<K, V>(maxKeys, comparator); | |
| 307 sibling.keys.addAll(keys.getRange(middle, keys.length)); | |
| 308 sibling.values.addAll(values.getRange(middle, values.length)); | |
| 309 keys.length = middle; | |
| 310 values.length = middle; | |
| 311 // Insert into the left / right sibling. | |
| 312 if (index < middle) { | |
| 313 insertNotFull(key, value, index); | |
| 314 } else { | |
| 315 sibling.insertNotFull(key, value, index - middle); | |
| 316 } | |
| 317 // Notify the parent about the split. | |
| 318 return new _Split<K, V>(sibling.keys[0], this, sibling); | |
| 319 } | |
| 320 // The node was not full. | |
| 321 insertNotFull(key, value, index); | |
| 322 return null; | |
| 323 } | |
| 324 | |
| 325 void insertNotFull(K key, V value, int index) { | |
| 326 if (index < keys.length && keys[index] == key) { | |
| 327 values[index] = value; | |
| 328 } else { | |
| 329 keys.insert(index, key); | |
| 330 values.insert(index, value); | |
| 331 } | |
| 332 } | |
| 333 | |
| 334 @override | |
| 335 _Remove<K, V> remove(K key, _Node<K, V> left, K anchor, _Node<K, V> right) { | |
| 336 // Find the key. | |
| 337 int index = keys.indexOf(key); | |
| 338 if (index == -1) { | |
| 339 return new _Remove<K, V>(null); | |
| 340 } | |
| 341 // Key key / value. | |
| 342 keys.removeAt(index); | |
| 343 V value = values.removeAt(index); | |
| 344 // Perform balancing. | |
| 345 if (keys.length < minKeys) { | |
| 346 // Try left sibling. | |
| 347 if (left is _LNode<K, V>) { | |
| 348 // Try to redistribute. | |
| 349 int leftLength = left.keys.length; | |
| 350 if (leftLength > minKeys) { | |
| 351 int halfExcess = (leftLength - minKeys + 1) ~/ 2; | |
| 352 int newLeftLength = leftLength - halfExcess; | |
| 353 keys.insertAll(0, left.keys.getRange(newLeftLength, leftLength)); | |
| 354 values.insertAll(0, left.values.getRange(newLeftLength, leftLength)); | |
| 355 left.keys.length = newLeftLength; | |
| 356 left.values.length = newLeftLength; | |
| 357 return new _Remove<K, V>.borrowLeft(value, keys.first); | |
| 358 } | |
| 359 // Do merge. | |
| 360 left.keys.addAll(keys); | |
| 361 left.values.addAll(values); | |
| 362 return new _Remove<K, V>.mergeLeft(value); | |
| 363 } | |
| 364 // Try right sibling. | |
| 365 if (right is _LNode<K, V>) { | |
| 366 // Try to redistribute. | |
| 367 var rightLength = right.keys.length; | |
| 368 if (rightLength > minKeys) { | |
| 369 int halfExcess = (rightLength - minKeys + 1) ~/ 2; | |
| 370 keys.addAll(right.keys.getRange(0, halfExcess)); | |
| 371 values.addAll(right.values.getRange(0, halfExcess)); | |
| 372 right.keys.removeRange(0, halfExcess); | |
| 373 right.values.removeRange(0, halfExcess); | |
| 374 return new _Remove<K, V>.borrowRight(value, right.keys.first); | |
| 375 } | |
| 376 // Do merge. | |
| 377 right.keys.insertAll(0, keys); | |
| 378 right.values.insertAll(0, values); | |
| 379 return new _Remove<K, V>.mergeRight(value); | |
| 380 } | |
| 381 } | |
| 382 // No balancing required. | |
| 383 return new _Remove<K, V>(value); | |
| 384 } | |
| 385 | |
| 386 @override | |
| 387 void _debugPrint(StringBuffer buffer, String indent) { | |
| 388 buffer.write(indent); | |
| 389 buffer.write('LNode {'); | |
| 390 for (int i = 0; i < keys.length; i++) { | |
| 391 if (i != 0) { | |
| 392 buffer.write(', '); | |
| 393 } | |
| 394 buffer.write(keys[i]); | |
| 395 buffer.write(': '); | |
| 396 buffer.write(values[i]); | |
| 397 } | |
| 398 buffer.write('}\n'); | |
| 399 } | |
| 400 } | |
| 401 | |
| 402 | |
| 403 /** | |
| 404 * An internal or leaf node. | |
| 405 */ | |
| 406 abstract class _Node<K, V> { | |
|
Brian Wilkerson
2014/06/07 16:34:02
It is easier for me to read the code in a Dart fil
scheglov
2014/06/07 21:26:22
I agree.
And there is another problem with sorting
| |
| 407 /** | |
| 408 * The [Comparator] to compare keys. | |
| 409 */ | |
| 410 Comparator<K> comparator; | |
| 411 | |
| 412 /** | |
| 413 * The list of keys. | |
| 414 */ | |
| 415 List<K> keys = new List<K>(); | |
| 416 | |
| 417 _Node(this.comparator); | |
| 418 | |
| 419 /** | |
| 420 * Looks for [key]. | |
| 421 * | |
| 422 * Returns the associated value if found. | |
| 423 * Returns `null` if not found. | |
| 424 */ | |
| 425 V find(K key); | |
| 426 | |
| 427 /** | |
| 428 * Inserts the [key] / [value] pair into this [_Node]. | |
| 429 * | |
| 430 * Returns a [_Split] object if split happens, or `null` otherwise. | |
| 431 */ | |
| 432 _Split<K, V> insert(K key, V value); | |
| 433 | |
| 434 /** | |
| 435 * Removes the association for the given [key]. | |
| 436 * | |
| 437 * Returns the [_Remove] information about an operation performed. | |
| 438 * It may be restructuring or merging, with [left] or [left] siblings. | |
| 439 */ | |
| 440 _Remove<K, V> remove(K key, _Node<K, V> left, K anchor, _Node<K, V> right); | |
| 441 | |
| 442 /** | |
| 443 * Prints the debug presentation of the node into [buffer]. | |
| 444 */ | |
| 445 void _debugPrint(StringBuffer buffer, String indent); | |
| 446 } | |
| 447 | |
| 448 | |
| 449 /** | |
| 450 * A container with information about redistribute / merge. | |
| 451 */ | |
| 452 class _Remove<K, V> { | |
| 453 K leftAnchor; | |
| 454 bool mergedLeft = false; | |
| 455 bool mergedRight = false; | |
| 456 K rightAnchor; | |
| 457 final V value; | |
| 458 _Remove(this.value); | |
| 459 _Remove.borrowLeft(this.value, this.leftAnchor); | |
| 460 _Remove.borrowRight(this.value, this.rightAnchor); | |
| 461 _Remove.mergeLeft(this.value) : mergedLeft = true; | |
| 462 _Remove.mergeRight(this.value) : mergedRight = true; | |
| 463 } | |
| 464 | |
| 465 | |
| 466 /** | |
| 467 * A container with information about split during insert. | |
| 468 */ | |
| 469 class _Split<K, V> { | |
| 470 final K key; | |
| 471 final _Node<K, V> left; | |
| 472 final _Node<K, V> right; | |
| 473 _Split(this.key, this.left, this.right); | |
| 474 } | |
| OLD | NEW |