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 }