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 moduleocean.util.container.btree.BTreeMapRange;
16 17 importocean.meta.types.Qualifiers;
18 importocean.util.container.btree.BTreeMap;
19 20 /*******************************************************************************
21 22 Exception thrown from the BTreeMapRange if a tree is modified during iteration.
23 24 *******************************************************************************/25 26 classRangeInvalidatedException: Exception27 {
28 importocean.core.Exception: DefaultExceptionCtor;
29 mixinDefaultExceptionCtor;
30 }
31 32 // shared between different ranges33 privateRangeInvalidatedExceptionrange_exception;
34 35 staticthis ()
36 {
37 .range_exception = newRangeInvalidatedException(
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 publicBTreeMapRange!(BTreeMap) byKeyValue (BTreeMap) (refBTreeMaptree, void[]* buff)
67 {
68 if (buffisnull)
69 {
70 // Struct wrapper is used to woraround D inability to allocate slice71 // itself on heap via new72 staticstructBuffer73 {
74 void[] data;
75 }
76 77 autobuf = newBuffer;
78 buff = &buf.data;
79 }
80 else81 {
82 (*buff).length = 0;
83 assumeSafeAppend(*buff);
84 }
85 86 autorange = BTreeMapRange!(BTreeMap)(&tree.impl);
87 range.stack = cast(typeof(range.stack))buff;
88 89 range.start();
90 returnrange;
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 privatestructPair(KeyType, ValueType)
104 {
105 void* keyp;
106 void* valp;
107 108 KeyTypekey () { return *cast(KeyType*)keyp; }
109 ValueTypevalue () { 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 publicstructBTreeMapRange(BTreeMap)
127 {
128 importocean.core.Verify;
129 importocean.core.array.Mutation;
130 131 /// (key, value) pair to return to the caller132 aliasPair!(BTreeMap.KeyType, BTreeMap.ValueType) KeyValue;
133 134 /// Root node of a tree135 privateBTreeMap.Implementation* tree;
136 137 138 /// Copy of the content_version field of the tree at the time iteration began139 privateulongtree_version;
140 141 /// Pair of the node/index for which we've paused iteration and pushed142 /// to stack143 privatestructNodeElement144 {
145 BTreeMap.Implementation.BTreeMapNode* node;
146 size_tindex;
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 completion161 /// of traversal over the nodes.162 privateNodeElement[]* stack;
163 164 /// Next (node, element_index) pair we're should walk over165 privateNodeElementitem;
166 167 /// Value of the current key to return with .front168 privateBTreeMap.KeyType* current_key;
169 170 /// Value of the current value to return with .front171 privateBTreeMap.ValueType* current_value;
172 173 invariant ()
174 {
175 verify(this.stack !isnull);
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 publicKeyValuefront ()
190 {
191 this.enforceValid();
192 returnPair!(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 publicvoidpopFront ()
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 publicboolempty ()
221 {
222 this.enforceValid();
223 returnthis.tree.rootisnull ||
224 (this.item.nodeisnull && 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 publicboolisValid ()
236 {
237 returntree_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 privatevoidenforceValid ()
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 privatevoidstart ()
264 {
265 if (this.tree.root !isnull)
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 privatevoiditerationStep ()
280 {
281 while (true)
282 {
283 if (this.item.node.is_leaf)
284 {
285 // Leaf doesn't have subtrees, we can just iterate over it286 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 else295 {
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 we311 // have any more elements to call the delegate on it312 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 one320 this.item.index++;
321 }
322 }
323 }
324 325 version (unittest)
326 {
327 importocean.math.random.Random;
328 importocean.core.Test;
329 }
330 331 unittest332 {
333 // Build a random tree, and use the range over it334 autorandom = newRandom();
335 boolfound;
336 intres;
337 338 BTreeMap!(int, int, 3) random_tree = BTreeMap!(int, int, 3).init;
339 random_tree.initialize();
340 341 intremoved_count;
342 inttotal_elements;
343 intto_insert = 10_000;
344 size_tmy_index;
345 346 // create completely random tree and remove completely random values347 for (inti = 0; i < to_insert; i++)
348 {
349 intelement = 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 (inti = 0; i < to_insert; i++)
358 {
359 intelement = 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 voidtestRange(void[]* buffer_ptr)
366 {
367 boolstarted;
368 intprevious_value;
369 intremaining_elements;
370 371 for (
372 autorange = 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 preserved384 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 invalidation398 autorange = byKeyValue(random_tree, &buff);
399 autofirst = range.front;
400 401 boolinsert_res;
402 do403 {
404 autoval = random.uniformR(2 * to_insert);
405 insert_res = random_tree.insert(val, val);
406 }
407 while (!insert_res);
408 testThrown!(RangeInvalidatedException)(range.popFront());
409 }