1 /********************************************************************************
2 
3     Range traversal support for BTreeMap.
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.BTreeMapRange;
16 
17 import ocean.meta.types.Qualifiers;
18 import ocean.util.container.btree.BTreeMap;
19 
20 /*******************************************************************************
21 
22     Exception thrown from the BTreeMapRange if a tree is modified during iteration.
23 
24 *******************************************************************************/
25 
26 class RangeInvalidatedException: Exception
27 {
28     import ocean.core.Exception: DefaultExceptionCtor;
29     mixin DefaultExceptionCtor;
30 }
31 
32 // shared between different ranges
33 private RangeInvalidatedException range_exception;
34 
35 static this ()
36 {
37     .range_exception = new RangeInvalidatedException(
38             "Container has been changed during iteration, please restart.");
39 }
40 
41 /*******************************************************************************
42 
43     Returns a range that performs an inorder iteration over the specified tree.
44 
45     If tree has changed during the iteration, range will throw an
46     RangeInvalidatedException. In case user knows that the tree can be changed
47     any time, it is possible to call BTreeMapRange.isValid before performing any
48     action on the range to check if fetching the front element, or moving to
49     the next one is guaranteed to success. This is only true for the
50     single-threaded environment.
51 
52     Params:
53         tree = tree to iterate over
54         buff = buffer for storing the state of the iteration. Useful
55                for the reusable-memory management strategy. The buffer size
56                is proportional to the height of the tree, so in most cases
57                it should be at most the size of the several pointers.
58                If the allocations are not a concern, null can passed for buff,
59                and range will allocate a buffer on its own.
60 
61     Returns:
62         BTreeMapRange performing the full inorder iteration of the tree.
63 
64 *******************************************************************************/
65 
66 public BTreeMapRange!(BTreeMap) byKeyValue (BTreeMap) (ref BTreeMap tree, void[]* buff)
67 {
68     if (buff is null)
69     {
70         // Struct wrapper is used to woraround D inability to allocate slice
71         // itself on heap via new
72         static struct Buffer
73         {
74             void[] data;
75         }
76 
77         auto buf = new Buffer;
78         buff = &buf.data;
79     }
80     else
81     {
82         (*buff).length = 0;
83         assumeSafeAppend(*buff);
84     }
85 
86     auto range = BTreeMapRange!(BTreeMap)(&tree.impl);
87     range.stack = cast(typeof(range.stack))buff;
88 
89     range.start();
90     return range;
91 }
92 
93 
94 /*******************************************************************************
95 
96     key/value pair. Akin to runtime's byKeyValue
97     NOTE: ideally, this should be optional. One might choose not to
98     care about keys at all. However, making this work without copying
99     entire code and without crashing dmd1 seems to be impossible.
100 
101 *******************************************************************************/
102 
103 private struct Pair(KeyType, ValueType)
104 {
105     void* keyp;
106     void* valp;
107 
108     KeyType key () { return *cast(KeyType*)keyp; }
109     ValueType value () { return *cast(ValueType*)valp; }
110 }
111 
112 /*******************************************************************************
113 
114     Structure representing an inorder range over a BTreeMap.
115 
116     If tree has changed during the iteration, range will throw an
117     RangeInvalidatedException. If the user knows that the tree will not change
118     during the range traversal, it is safe to call range's methods - it will throw
119     an exception if this promise is broken. In case user knows that the tree
120     can be changed any time, it is possible to call BTreeMapRange.isValid before
121     performing any action on the range to check if the accessing is safe. This
122     is only true for the single-threaded environment.
123 
124 *******************************************************************************/
125 
126 public struct BTreeMapRange(BTreeMap)
127 {
128     import ocean.core.Verify;
129     import ocean.core.array.Mutation;
130 
131     /// (key, value) pair to return to the caller
132     alias Pair!(BTreeMap.KeyType, BTreeMap.ValueType) KeyValue;
133 
134     /// Root node of a tree
135     private BTreeMap.Implementation* tree;
136 
137 
138     /// Copy of the content_version field of the tree at the time iteration began
139     private ulong tree_version;
140 
141     /// Pair of the node/index for which we've paused iteration and pushed
142     /// to stack
143     private struct NodeElement
144     {
145         BTreeMap.Implementation.BTreeMapNode* node;
146         size_t index;
147 
148         BTreeMap.ValueType* value ()
149         {
150             return &(node.elements[index].value);
151         }
152 
153         BTreeMap.KeyType* key ()
154         {
155             return &(node.elements[index].key);
156         }
157     }
158 
159     /// When IterationStep() moves from a node to iterate over its children,
160     /// the parent node is pushed to this stack, and popped upon completion
161     /// of traversal over the nodes.
162     private NodeElement[]* stack;
163 
164     /// Next (node, element_index) pair we're should walk over
165     private NodeElement item;
166 
167     /// Value of the current key to return with .front
168     private BTreeMap.KeyType* current_key;
169 
170     /// Value of the current value to return with .front
171     private BTreeMap.ValueType* current_value;
172 
173     invariant ()
174     {
175         verify(this.stack !is null);
176     }
177 
178 
179     /***************************************************************************
180 
181         Returns:
182             the current element in the range.
183 
184         Throws:
185            RangeInvalidatedException if the underlying tree has changed
186 
187     ***************************************************************************/
188 
189     public KeyValue front ()
190     {
191         this.enforceValid();
192         return Pair!(BTreeMap.KeyType, BTreeMap.ValueType)(this.current_key, this.current_value);
193     }
194 
195     /***************************************************************************
196 
197         Pops the next element from the tree.
198 
199         Throws:
200            RangeInvalidatedException if the underlying tree has changed
201 
202     ***************************************************************************/
203 
204     public void popFront ()
205     {
206         this.enforceValid();
207         this.iterationStep();
208     }
209 
210     /***************************************************************************
211 
212         Returns:
213             true if there are no more elements to iterate over
214 
215         Throws:
216            RangeInvalidatedException if the underlying tree has changed
217 
218     ***************************************************************************/
219 
220     public bool empty ()
221     {
222         this.enforceValid();
223         return this.tree.root is null ||
224             (this.item.node is null && this.stack.length == 0);
225     }
226 
227     /***************************************************************************
228 
229         Returns:
230             true if the underlying tree has not changed and it's safe
231             to use front/popFront/empty methods.
232 
233     ***************************************************************************/
234 
235     public bool isValid ()
236     {
237         return tree_version == this.tree.content_version;
238     }
239 
240     /***************************************************************************
241 
242         Ensures that a tree has not changed since the creation of this range.
243 
244         Throws:
245            RangeInvalidatedException if the underlying tree has changed
246 
247     ***************************************************************************/
248 
249     private void enforceValid ()
250     {
251         if (!this.isValid())
252         {
253             throw .range_exception;
254         }
255     }
256 
257     /***************************************************************************
258 
259         Prepares the range over the tree.
260 
261     ***************************************************************************/
262 
263     private void start ()
264     {
265         if (this.tree.root !is null)
266         {
267             this.tree_version = this.tree.content_version;
268             this.item = NodeElement(this.tree.root, 0);
269             this.iterationStep();
270         }
271     }
272 
273     /***************************************************************************
274 
275         Goes to the next element in the tree.
276 
277     ***************************************************************************/
278 
279     private void iterationStep ()
280     {
281         while (true)
282         {
283             if (this.item.node.is_leaf)
284             {
285                 // Leaf doesn't have subtrees, we can just iterate over it
286                 if (this.item.index < this.item.node.number_of_elements)
287                 {
288                     this.current_value = this.item.value;
289                     this.current_key = this.item.key;
290                     this.item.index++;
291                     return;
292                 }
293             }
294             else
295             {
296                 // do we have a subtree in the child_elements[index]?
297                 if (this.item.index <= this.item.node.number_of_elements)
298                 {
299                     *this.stack ~= NodeElement(this.item.node, this.item.index);
300                     this.item = NodeElement(item.node.child_nodes[this.item.index], 0);
301                     continue;
302                 }
303             }
304 
305             if ((*this.stack).pop(this.item) == false)
306             {
307                 return;
308             }
309 
310             // We got the item from the stack, and we should see if we
311             // have any more elements to call the delegate on it
312             if (this.item.index < this.item.node.number_of_elements)
313             {
314                 this.current_value = this.item.value;
315                 this.current_key = this.item.key;
316                 this.item.index++;
317                 return;
318             }
319             // There's no more elements, just skip to the next one
320             this.item.index++;
321         }
322     }
323 }
324 
325 version (unittest)
326 {
327     import ocean.math.random.Random;
328     import ocean.core.Test;
329 }
330 
331 unittest
332 {
333     // Build a random tree, and use the range over it
334     auto random = new Random();
335     bool found;
336     int res;
337 
338     BTreeMap!(int, int, 3) random_tree = BTreeMap!(int, int, 3).init;
339     random_tree.initialize();
340 
341     int removed_count;
342     int total_elements;
343     int to_insert = 10_000;
344     size_t my_index;
345 
346     // create completely random tree and remove completely random values
347     for (int i = 0; i < to_insert; i++)
348     {
349         int element = random.uniformR(to_insert);
350         // don't count double elements (they are not inserted)
351         total_elements += random_tree.insert(element, element)? 1 : 0;
352         res = random_tree.get(element, found);
353         test(found);
354         test!("==")(res, element);
355     }
356 
357     for (int i = 0; i < to_insert; i++)
358     {
359         int element = random.uniformR(to_insert);
360         removed_count += random_tree.remove(element)? 1 : 0;
361         res = random_tree.get(element, found);
362         test(!found);
363     }
364 
365     void testRange(void[]* buffer_ptr)
366     {
367         bool started;
368         int previous_value;
369         int remaining_elements;
370 
371         for (
372             auto range = byKeyValue(random_tree, buffer_ptr);
373             !range.empty; range.popFront())
374         {
375             if (!started)
376             {
377                 previous_value = range.front.value;
378                 started = true;
379                 remaining_elements++;
380                 continue;
381             }
382 
383             // enforce that the order is preserved
384             test!(">")(range.front.value, previous_value);
385             previous_value = range.front.value;
386             test!("==")(range.front.value, range.front.key);
387             remaining_elements++;
388         }
389 
390         test!("==")(total_elements - remaining_elements, removed_count);
391     }
392 
393     void[] buff;
394     testRange(&buff);
395     testRange(null);
396 
397     // test invalidation
398     auto range = byKeyValue(random_tree, &buff);
399     auto first = range.front;
400 
401     bool insert_res;
402     do
403     {
404         auto val = random.uniformR(2 * to_insert);
405         insert_res = random_tree.insert(val, val);
406     }
407     while (!insert_res);
408     testThrown!(RangeInvalidatedException)(range.popFront());
409 }