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 }