1 /******************************************************************************** 2 3 B-Tree data structure and operations on it. 4 5 Copyright: 6 Copyright (c) 2017 dunnhumby Germany GmbH. All rights reserved. 7 8 License: 9 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 10 Alternatively, this file may be distributed under the terms of the Tango 11 3-Clause BSD License (see LICENSE_BSD.txt for details). 12 13 *******************************************************************************/ 14 15 module ocean.util.container.btree.BTreeMap; 16 17 import ocean.meta.types.Qualifiers; 18 import ocean.util.container.mem.MemManager; 19 20 /****************************************************************************** 21 22 BTree structure. 23 24 25 BTree is a rooted tree, with the following properties: 26 27 - A BTree has characteristic called "degree". This indicates branching 28 factor of the tree, so that maximum number of the subtrees for a single 29 node is `2 * degree`. Having a high degree makes the tree shorter, and, 30 because the node contains large number of elements, it allows prefetching 31 many elements from the memory in one go. 32 33 - The layout of one node of the tree is as follows: 34 35 ----------------------------------------------------------------- 36 | p1 | element1 | p2 | element2 | ... | pn-1 | element n-1 | pn | 37 ----------------------------------------------------------------- 38 39 Every element (a key/value pair) in the node (for the non-leaf nodes) is 40 surrounded by two pointers. We call these pointers child nodes. 41 42 - Each pointer is a root of a subtree, for which the following holds true: 43 all the elements' keys in a tree pointed by p(i-1) (pointer left of the 44 elements) are smaller (in respect to the ordering defined by the key's 45 type) of the element(i), and all the elements' keys in a subtree pointed by 46 p(i) are larger than a element(i). 47 48 - Every node may contain at most 2*degree - 1 elements. Therefore, an internal 49 node may be root for at most 2*degree subtrees. 50 51 - Every node other than root must have at least (degree-1) elements. 52 53 - Every internal node other than the root has at least degree children. 54 55 - Every node has the following attributes: 56 57 node.number_of_elements = number of elements node currently contains 58 (as nodes are statically allocated block, the number of the elements 59 may not necessarily be maximal). 60 node.elements = the elements themselves 61 node.is_leaf = indicator if the node is a leaf. 62 node.child_nodes = number_of_elements+1 pointers to the subtrees whose 63 roots are this node. We refer to these nodes as child-nodes (this 64 node is a direct parent of them). 65 66 - All leaves have the same depth 67 68 Other than insert and delete operations, inorder traversal method is provided, 69 which traverses the tree in inorder order (left subtree is visited first, 70 then the element separating these subtrees, then the right subtree, then 71 the next element, etc.). This provides sequential access to the tree. 72 There's a recursive and range-based implementation of the inorder 73 traversal. 74 75 Range-based traversal is supported through `BTreeMapRange` structure, found in 76 in ocean.util.container.btree.BTreeMapRange. 77 78 CPU complexity of the operations (n is number of the elements, t is degree, 79 h is the tree height): 80 81 - Searching: O(th) = O(t * log_t(n)) 82 - Inserting: O(th) = O(t * log_t(n)) 83 - Deleting: O(th) = O(t * log_t(n)) 84 85 Complexity of the memory access (to read/write the node from/in memory): 86 87 - Searching O(h) = O(log_t(n)) 88 - Inserting O(h) = O(log_t(n)) 89 - Deleting: O(h) = O(log_t(n)) 90 91 References: 92 - https://en.wikipedia.org/wiki/B-tree 93 - Thomas H. Cormen et al. "Introduction to Algorithms, 3rd edition" 94 95 Params: 96 TreeKeyType = type of the key. 97 TreeValueType = type of the element to store. 98 tree_degree = degree of the tree (refer to the documentation) 99 100 ******************************************************************************/ 101 102 struct BTreeMap(TreeKeyType, TreeValueType, int tree_degree) 103 { 104 import ocean.util.container.btree.Implementation; 105 106 static assert (tree_degree > 0); 107 108 /************************************************************************** 109 110 Constructor. 111 112 Params: 113 allocator = memory manager to use to allocate/deallocate memory 114 115 ***************************************************************************/ 116 117 package void initialize (IMemManager allocator = mallocMemManager) 118 { 119 this.impl.initialize(allocator); 120 } 121 122 // Disable constructor, so user always needs to use the makeBTreeMap method 123 @disable this(); 124 125 /************************************************************************** 126 127 Inserts the (key, value) in the tree. This is passing the copy of the 128 value into the tree, so it's not necessary to manually create copy of 129 it. Note that for the reference types, this will just copy the 130 reference. 131 132 Params: 133 key = key to insert 134 value = value associated to the key to insert. 135 136 Returns: 137 true if the element with the given key did not exist and it 138 was inserted, false otherwise 139 140 Complexity: 141 CPU: O(degree * log_degree(n)) 142 Memory:O(log_degree(n)) 143 144 ***************************************************************************/ 145 146 public bool insert (KeyType key, ValueType value) 147 { 148 bool added; 149 this.impl.insert(key, value, added); 150 return added; 151 } 152 153 /*************************************************************************** 154 155 Inserts the (key, value) in the tree. This is passing the copy of the 156 value into the tree, so it's not necessary to manually create copy of 157 it. Note that for the reference types, this will just copy the 158 reference. 159 160 Params: 161 key = key to insert 162 value = value associated to the key to insert. 163 added = indicator if the value has been added, or the value 164 associated with the key was already in the map 165 166 Returns: 167 pointer to the element associated with the given key (either existing 168 or new value). 169 170 Complexity: 171 CPU: O(degree * log_degree(n)) 172 Memory:O(log_degree(n)) 173 174 ***************************************************************************/ 175 176 public ValueType* insert (KeyType key, ValueType value, out bool added) 177 out (ptr) 178 { 179 assert(ptr !is null); 180 } 181 do 182 { 183 return this.impl.insert(key, value, added); 184 } 185 186 187 /****************************************************************************** 188 189 Deletes the element from the BTreeMap. 190 191 Params: 192 key = key of the element to delete. 193 194 Returns: 195 true if the element was found and deleted, false otherwise. 196 197 Complexity: 198 CPU: O(degree * log_degree(n)) 199 Memory:O(log_degree(n)) 200 201 ******************************************************************************/ 202 203 public bool remove (KeyType key) 204 { 205 return this.impl.remove(key); 206 } 207 208 /****************************************************************************** 209 210 Searches the tree for the element with the given key and returns the 211 associated value. 212 213 Params: 214 key = key to find in a tree. 215 found_element = indicator if the element with the given key has been 216 found in the map. 217 218 Returns: 219 copy of the value associated with the key, if found, ValueType.init 220 otherwise. 221 222 Complexity: 223 CPU: O(degree * log_degree(n)) 224 Memory:O(log_degree(n)) 225 226 *******************************************************************************/ 227 228 public ValueType get (KeyType key, out bool found_element) 229 { 230 return this.impl.get(key, found_element); 231 } 232 233 /************************************************************************** 234 235 Searches the tree for the element with the given key and returns the 236 pointer to the associated value. 237 238 Params: 239 key = key to find in a tree. 240 241 Returns: 242 pointer to the value associated with the key, if found, null otherwise. 243 244 Complexity: 245 CPU: O(degree * log_degree(n)) 246 Memory:O(log_degree(n)) 247 248 ***************************************************************************/ 249 250 public ValueType* opBinaryRight (string op : "in") (KeyType key) 251 { 252 return this.impl.get(key); 253 } 254 255 /*********************************************************************** 256 257 Recursive inorder iteration over keys and values. Note that, in case 258 the tree is changed during the iteration, iteration will halt. 259 260 Params: 261 dg = opApply's delegate 262 263 Returns: 264 return value of dg 265 266 ***********************************************************************/ 267 268 public int opApply (scope int delegate (ref KeyType value, ref ValueType) dg) 269 { 270 return this.impl.inorder(dg); 271 } 272 273 /*********************************************************************** 274 275 Recursive inorder iteration over values only. Note that, in case the 276 tree is changed during the iteration, iteration will halt. 277 278 Params: 279 dg = opApply's delegate 280 281 Returns: 282 return value of dg 283 284 ***********************************************************************/ 285 286 public int opApply (scope int delegate (ref ValueType) dg) 287 { 288 return this.impl.inorder(dg); 289 } 290 291 292 /// Convenience alias for the implementation 293 package alias BTreeMapImplementation!(KeyType, ValueType, tree_degree) 294 Implementation; 295 296 /// Convenience alias for the key type 297 package alias TreeKeyType KeyType; 298 299 /// Convenience alias for the element type 300 package alias TreeValueType ValueType; 301 302 /// Private implementation 303 package Implementation impl; 304 } 305 306 /******************************************************************************* 307 308 Constructor-like method. Constructs the BTreeMap and initializes it. 309 310 Params: 311 KeyType = type of the key. 312 ValueType = type of the value to store. 313 tree_degree = degree of the tree (refer to the documentation) 314 memManager = memory management strategy to use 315 316 Returns: 317 empty BTreeMap which maps KeyType to ValueType of a given degree 318 which sues memManager allocation strategy. 319 320 ******************************************************************************/ 321 322 public BTreeMap!(KeyType, ValueType, tree_degree) makeBTreeMap 323 (KeyType, ValueType, int tree_degree)(IMemManager allocator = mallocMemManager) 324 { 325 auto tree = BTreeMap!(KeyType, ValueType, tree_degree).init; 326 tree.initialize(allocator); 327 return tree; 328 } 329 330 version (unittest) 331 { 332 import ocean.util.container.btree.BTreeMapRange; 333 } 334 335 /// 336 unittest 337 { 338 // import ocean.util.container.btree.BTreeMapRange; 339 struct MyVal 340 { 341 int x; 342 } 343 344 auto map = makeBTreeMap!(int, MyVal, 2); 345 346 for (int i = 0; i <= 10; i++) 347 { 348 map.insert(i, MyVal(i*2)); 349 } 350 351 // find the element by key 352 bool found; 353 auto valfive = map.get(5, found); 354 test!("==")(found, true); 355 test!("==")(valfive.x, 10); 356 357 // remove the element 358 map.remove(10); 359 map.get(10, found); 360 test!("==")(found, false); 361 362 // iterate over using opApply 363 foreach (key, val; map) 364 { 365 test!("==")(key * 2, val.x); 366 } 367 368 // iterate over using range 369 void[] buff; 370 for(auto range = byKeyValue(map, &buff); 371 !range.empty; range.popFront()) 372 { 373 test!("==")(range.front.key * 2, range.front.value.x); 374 } 375 376 // Let's check that the memory is ready to be reused 377 test(buff.ptr !is null); 378 379 380 // Use pointer-returning methods 381 auto x = 5 in map; 382 test(x !is null); 383 test!("==")(*x, MyVal(10)); 384 385 // insert overload that returns the flag if the value was added 386 bool added; 387 auto ptr = map.insert(20, MyVal(40), added); 388 test(ptr !is null); 389 test(added); 390 test!("==")(*ptr, MyVal(40)); 391 392 // Doesn't insert duplicate 393 auto old_ptr = map.insert(9, MyVal(60), added); 394 test(old_ptr !is null); 395 test(!added); 396 test!("==")(*old_ptr, MyVal(18)); 397 } 398 399 /* 400 401 Unittests. Compile with -debug=BTreeMapSanity to turn on 402 the invariant checks for the tree. 403 404 */ 405 406 version (unittest) 407 { 408 import ocean.core.Test; 409 } 410 411 unittest 412 { 413 BTreeMap!(int, File, 2) tree = makeBTreeMap!(int, File, 2); 414 bool found_element; 415 416 File f; 417 418 f.setName("5"); 419 tree.insert(5, f); 420 421 f.setName("9"); 422 tree.insert(9, f); 423 424 f.setName("3"); 425 tree.insert(3, f); 426 427 f.setName("7"); 428 tree.insert(7, f); 429 430 size_t index; 431 auto res = tree.get(7, found_element); 432 433 test!("==")(found_element, true); 434 test!("==")(res, f); 435 436 f.setName("1"); 437 tree.insert(1, f); 438 439 f.setName("2"); 440 tree.insert(2, f); 441 442 f.setName("8"); 443 tree.insert(8, f); 444 445 f.setName("6"); 446 tree.insert(6, f); 447 448 f.setName("0"); 449 tree.insert(0, f); 450 451 f.setName("4"); 452 tree.insert(4, f); 453 } 454 455 unittest 456 { 457 bool found_element; 458 auto number_tree = makeBTreeMap!(int, int, 2); 459 460 for (int i = -1; i < 9; i++) 461 { 462 number_tree.insert(i, i); 463 } 464 465 // find "random" element 466 int i = 3; 467 auto xres = number_tree.get(i, found_element); 468 number_tree.remove(i); 469 number_tree.get(i, found_element); 470 test!("==")(found_element, false); 471 472 i = 7; 473 number_tree.remove(i); 474 number_tree.get(i, found_element); 475 test!("==")(found_element, false); 476 477 i = 9; 478 number_tree.remove(i); 479 480 i = 0; 481 number_tree.remove(i); 482 483 i = 1; 484 number_tree.remove(i); 485 486 i = 2; 487 number_tree.remove(i); 488 489 for (i = 0; i < 9; i++) 490 { 491 number_tree.remove(i); 492 number_tree.get(i, found_element); 493 test!("==")(found_element, false); 494 } 495 496 for (i = 0; i < 9; i++) 497 { 498 number_tree.insert(i, i); 499 } 500 501 for (i = 9; i > 0; i--) 502 { 503 number_tree.remove(i); 504 number_tree.get(i, found_element); 505 test!("==")(found_element, false); 506 } 507 508 i = 0; 509 number_tree.insert(i, i); 510 i = 1; 511 number_tree.insert(i, i); 512 i = 2; 513 number_tree.insert(i, i); 514 515 for (i = 0; i < 100000; i++) 516 { 517 number_tree.insert(i, i); 518 } 519 520 i = 0; 521 number_tree.remove(i); 522 523 i = 1; 524 number_tree.remove(i); 525 526 i = 2; 527 number_tree.remove(i); 528 529 for (i = 0; i < 100000; i++) 530 { 531 number_tree.remove(i); 532 number_tree.get(i, found_element); 533 test!("==")(found_element, false); 534 } 535 536 // remove reverse 537 for (i = 0; i < 100000; i++) 538 { 539 number_tree.insert(i, i); 540 } 541 542 for (i = 100000; i >= 0; i--) 543 { 544 number_tree.remove(i); 545 number_tree.get(i, found_element); 546 test!("==")(found_element, false); 547 } 548 549 // remove reverse from half 550 for (i = 0; i < 100000; i++) 551 { 552 number_tree.insert(i, i); 553 } 554 555 556 for (i = 5000; i >= 0; i--) 557 { 558 number_tree.remove(i); 559 number_tree.get(i, found_element); 560 test!("==")(found_element, false); 561 } 562 563 564 // random deletion 565 for (i = 0; i < 5000; i++) 566 { 567 number_tree.insert(i, i); 568 } 569 570 bool started; 571 int previous_value; 572 int counter; 573 foreach (value; number_tree) 574 { 575 counter++; 576 577 if (!started) 578 { 579 previous_value = value; 580 started = true; 581 continue; 582 } 583 584 test!("<")(previous_value, value); 585 previous_value = value; 586 } 587 test!("==")(counter, 100000); 588 589 foreach (key, value; number_tree) 590 { 591 test!("==")(key, value); 592 } 593 594 // Randomized tests 595 596 auto random = new Random(); 597 int to_remove = 5864; 598 number_tree.remove(to_remove); 599 test(!number_tree.get(to_remove, found_element)); 600 601 for (i = 0; i < 100000; i++) 602 { 603 to_remove = random.uniform!(int); 604 number_tree.remove(to_remove); 605 test(!number_tree.get(to_remove, found_element)); 606 } 607 } 608 609 unittest 610 { 611 class X 612 { 613 int x; 614 615 immutable this () {}; 616 } 617 618 // Test immutable support 619 auto const_tree = makeBTreeMap!(void*, const(X), 2); 620 auto a = new immutable X; 621 622 const_tree.insert(cast(void*)&a, a); 623 bool found; 624 auto res = const_tree.get(cast(void*)&a, found); 625 test(found); 626 test(res == a); 627 test(const_tree.remove(cast(void*)&a)); 628 } 629 630 version (unittest) 631 { 632 import ocean.core.Test; 633 import ocean.core.Enforce; 634 import ocean.math.random.Random; 635 636 /// File structure - represents a directory or a file 637 /// For testing if the BTreeMap works with other value types 638 public struct File 639 { 640 /// Name of the directory/file 641 char[48] name_buf; 642 ubyte name_length; 643 644 cstring name () const return 645 { 646 return name_buf[0..name_length]; 647 } 648 649 void setName (cstring name) 650 { 651 //logger.error("setting name: {}", name); 652 enforce (name.length <= ubyte.max); 653 this.name_length = cast(ubyte)name.length; 654 this.name_buf[0..this.name_length] = name[]; 655 } 656 } 657 }