1 module PixelPerfectEngine.system.binarySearchTree; 2 3 /* 4 * Copyright (C) 2015-2018, by Laszlo Szeremi under the Boost license. 5 * 6 * Pixel Perfect Engine, system.transformFunctions module 7 */ 8 9 import core.stdc.stdlib; 10 11 /** 12 * Implements a mostly @nogc-able container format, with relatively fast access times. 13 * Mostly based upon the AVT tree algorithm with small modifications, with the intent to replace D's default associative arrays for GC-free operations. 14 * TODO: Implement ordered tree traversal. 15 */ 16 public struct BinarySearchTree(K, E){ 17 private BSTNode!(K, E)* root; 18 //private sizediff_t bal; 19 private size_t nOfElements; 20 21 /*~this(){ 22 if(root !is null){ 23 BSTNode!(K, E)*[] nodes = collectNodes(root); 24 foreach(n ; nodes){ 25 free(n); 26 } 27 } 28 }*/ 29 /** 30 * Tree traversal for quick access. 31 * TODO: Make it NOGC 32 */ 33 public int opApply(scope int delegate(ref E) dg){ 34 int result; 35 result = treeTraversalByDepth(dg, root); 36 return result; 37 } 38 protected int treeTraversalByDepth(scope int delegate(ref E) dg, BSTNode!(K, E)* root){ 39 int result = dg(root.elem); 40 if(result) 41 return result; 42 else{ 43 if(root.left !is null){ 44 result = treeTraversalByDepth(dg, root.left); 45 if(result) 46 return result; 47 } 48 if(root.right !is null){ 49 result = treeTraversalByDepth(dg, root.right); 50 if(result) 51 return result; 52 } 53 } 54 return result; 55 } 56 /** 57 * Gets an element without allocation. Returns E.init if key not found. 58 */ 59 public @nogc E opIndex(K key){ 60 bool found; 61 BSTNode!(K, E)* crnt = root; 62 E result; 63 do{ 64 if(crnt is null){ 65 found = true; 66 }else if(crnt.key == key){ 67 found = true; 68 result = crnt.elem; 69 }else if(crnt.key > key){ 70 crnt = crnt.left; 71 }else{ 72 crnt = crnt.right; 73 } 74 }while(!found); 75 return result; 76 } 77 78 /** 79 * Inserts a new element with some automatic optimization. 80 * TODO: Make the optimization better. 81 */ 82 public @nogc E opIndexAssign(E value, K i){ 83 if(root is null){ 84 root = cast(BSTNode!(K, E)*)malloc(BSTNode!(K, E).sizeof); 85 *root = BSTNode!(K, E)(i,value); 86 nOfElements++; 87 return value; 88 } 89 if(insertAt(&root, i, value)){ 90 rebalanceTree(&root); 91 } 92 nOfElements++; 93 return value; 94 } 95 /** 96 * Rebalances a tree. 97 */ 98 public @nogc void rebalanceTree(){ 99 rebalanceTree(&root); 100 } 101 protected @nogc void rebalanceTree(BSTNode!(K, E)** node){ 102 if((*node).height > 1){ 103 if((*node).bal > 1 || (*node).bal < -1){ 104 optimize(node); 105 } 106 if((*node).left !is null){ 107 rebalanceTree(&(*node).left); 108 } 109 if((*node).right !is null){ 110 rebalanceTree(&(*node).right); 111 } 112 } 113 } 114 /** 115 * Returns the number of elements in the tree. 116 */ 117 public @nogc @property size_t length(){ 118 return nOfElements; 119 } 120 /** 121 * Inserts an item at the given point. Returns true if the height of the tree have been raised. 122 */ 123 protected @nogc bool insertAt(BSTNode!(K, E)** node, K key, E val){ 124 if(key == (*node).key){ 125 (*node).elem = val; 126 return false; 127 }else if(key < (*node).key){ 128 if((*node).left is null){ 129 (*node).left = cast(BSTNode!(K, E)*)malloc(BSTNode!(K, E).sizeof); 130 *(*node).left = BSTNode!(K, E)(key,val); 131 if((*node).right is null){ 132 return true; 133 }else{ 134 return false; 135 } 136 }else{ 137 return insertAt(&((*node).left), key, val); 138 } 139 }else{ 140 if((*node).right is null){ 141 (*node).right = cast(BSTNode!(K, E)*)malloc(BSTNode!(K, E).sizeof); 142 *(*node).right = BSTNode!(K, E)(key,val); 143 if((*node).left is null){ 144 return true; 145 }else{ 146 return false; 147 } 148 }else{ 149 return insertAt(&((*node).right), key, val); 150 } 151 } 152 } 153 /** 154 * Removes an element by key. 155 */ 156 public @nogc void remove(K key){ 157 nOfElements--; 158 bool handside; 159 BSTNode!(K, E)* crnt = root; 160 BSTNode!(K, E)* prev; 161 while(crnt !is null){ 162 if(crnt.key == key){ 163 if(crnt.left is null && crnt.right is null){ 164 free(crnt); 165 if(prev !is null){ 166 if(handside) 167 prev.right = null; 168 else 169 prev.left = null; 170 } 171 crnt = null; 172 }else if(crnt.left is null){ 173 if(prev is null){ 174 root = crnt.right; 175 }else{ 176 if(handside) 177 prev.right = crnt.right; 178 else 179 prev.left = crnt.right; 180 } 181 free(crnt); 182 crnt = null; 183 }else if(crnt.right is null){ 184 if(prev is null){ 185 root = crnt.left; 186 }else{ 187 if(handside) 188 prev.right = crnt.left; 189 else 190 prev.left = crnt.left; 191 } 192 free(crnt); 193 crnt = null; 194 }else{ 195 BSTNode!(K, E)* temp; 196 if(handside){ 197 temp = findMin(prev.right); 198 }else{ 199 temp = findMax(prev.right); 200 } 201 K tempKey = temp.key; 202 E tempVal = temp.elem; 203 BSTNode!(K, E)* tempLHS = temp.left; 204 BSTNode!(K, E)* tempRHS = temp.right; 205 remove(tempKey); 206 crnt.key = tempKey; 207 crnt.elem = tempVal; 208 crnt.left = tempLHS; 209 crnt.right = tempRHS; 210 return; 211 } 212 }else if(crnt.key > key){ 213 prev = crnt; 214 handside = false; 215 crnt = crnt.left; 216 }else{ 217 prev = crnt; 218 handside = true; 219 crnt = crnt.right; 220 } 221 } 222 } 223 /** 224 * Rotates the subtree to the left by one. 225 */ 226 protected @nogc void rotateLeft(BSTNode!(K, E)** node){ 227 /*BSTNode!(K, E)* temp = (*node).right; 228 (*node).right = temp.left; 229 temp.left = *node; 230 *node = temp; 231 (*node).bal = 0; 232 temp.bal = 0;*/ 233 BSTNode!(K, E)* temp = *node; //save current node 234 *node = temp.right; //assign the first RHS to the root 235 temp.right = (*node).left; //assign the new root's LHS to the saved node's RHS 236 (*node).left = temp; //assign the saved node to the new root's LHS 237 //(*node).bal = 0; //reset new root's balance 238 //temp.bal = 0; //reset the new LHS's balance 239 } 240 /** 241 * Rotates the subtree to the right by one. 242 */ 243 protected @nogc void rotateRight(BSTNode!(K, E)** node){ 244 /*BSTNode!(K, E)* temp = (*node).left; 245 (*node).left = temp.right; 246 temp.right = *node; 247 *node = temp; 248 (*node).bal = 0; 249 temp.bal = 0;*/ 250 BSTNode!(K, E)* temp = *node; //save current node 251 *node = temp.left; //assign the first LHS to the root 252 temp.left = (*node).right; //assign the new root's RHS to the saved node's LHS 253 (*node).right = temp; //assign the saved node to the new root's RHS 254 //(*node).bal = 0; //reset new root's balance 255 //temp.bal = 0; //reset the new RHS's balance 256 } 257 /** 258 * Rotates the subtree to the right then to the left. 259 */ 260 protected @nogc void rotateRightLeft(BSTNode!(K, E)** node){ 261 BSTNode!(K, E)* temp = (*node).right.left; //save root.RHS.LHS 262 (*node).right.left = temp.right; //assign temp.RHS to root.RHS.LHS 263 temp.right = (*node).right; //assign root.RHS to temp.RHS 264 (*node).right = temp; //assign temp to root.RHS 265 //temp.right.bal = 0; //reset balance of temp.RHS 266 rotateLeft(node); //rotate the root to the left 267 } 268 /** 269 * Rotates the subtree to the right then to the left. 270 */ 271 protected @nogc void rotateLeftRight(BSTNode!(K, E)** node){ 272 BSTNode!(K, E)* temp = (*node).left.right; //save root.LHS.RHS 273 (*node).left.right = temp.left; //assign temp.LHS to root.LHS.RHS 274 temp.left = (*node).left; //assign root.LHS to temp.LHS 275 (*node).left = temp; //assign temp to root.LHS 276 //temp.left.bal = 0; //reset balance of temp.LHS 277 rotateRight(node); //rotate the root to the right 278 } 279 /** 280 * Optimizes a BinarySearchTree by distributing nodes evenly. 281 */ 282 protected @nogc void optimize(BSTNode!(K, E)** node){ 283 if((*node).bal >= 2){ 284 if((*node).right.bal >= 1){ 285 rotateLeft(node); 286 }else if((*node).right.bal <= -1){ 287 rotateRightLeft(node); 288 } 289 }else if((*node).bal <= -2){ 290 if((*node).left.bal >= 1){ 291 rotateLeftRight(node); 292 }else if((*node).left.bal <= -1){ 293 rotateRight(node); 294 } 295 } 296 297 } 298 299 public BSTNode!(K, E)*[] collectNodes(BSTNode!(K, E)* source){ 300 BSTNode!(K, E)*[] result; 301 if(source !is null){ 302 result ~= source; 303 if(source.left !is null) 304 result ~= collectNodes(source.left); 305 if(source.right !is null) 306 result ~= collectNodes(source.right); 307 } 308 return result; 309 } 310 311 public @nogc BSTNode!(K, E)* findMin(){ 312 bool found; 313 BSTNode!(K, E)* result = root; 314 do{ 315 if(result.left is null) 316 found = true; 317 else 318 result = result.left; 319 }while(!found); 320 return result; 321 } 322 323 public @nogc BSTNode!(K, E)* findMin(BSTNode!(K, E)* from){ 324 bool found; 325 BSTNode!(K, E)* result = from; 326 do{ 327 if(result.left is null) 328 found = true; 329 else 330 result = result.left; 331 }while(!found); 332 return result; 333 } 334 335 public @nogc BSTNode!(K, E)* findMax(){ 336 bool found; 337 BSTNode!(K, E)* result = root; 338 do{ 339 if(result.right is null) 340 found = true; 341 else 342 result = result.right; 343 }while(!found); 344 return result; 345 } 346 347 public @nogc BSTNode!(K, E)* findMax(BSTNode!(K, E)* from){ 348 bool found; 349 BSTNode!(K, E)* result = from; 350 do{ 351 if(result.right is null) 352 found = true; 353 else 354 result = result.right; 355 }while(!found); 356 return result; 357 } 358 359 public string toString(){ 360 import std.conv; 361 string result = "Key: " ~ K.stringof ~ " : Elem: " ~ E.stringof; 362 if(root !is null) 363 result ~= root.toString(); 364 return result; 365 } 366 } 367 368 public struct BSTNode(K, E){ 369 K key; 370 E elem; 371 BSTNode!(K, E)* left; //lesser elements 372 BSTNode!(K, E)* right; //greater elements 373 374 @nogc this(K key, E elem, BSTNode!(K, E)* left = null, BSTNode!(K, E)* right = null){ 375 this.key = key; 376 this.elem = elem; 377 this.left = left; 378 this.right = right; 379 } 380 381 @nogc int opCmp(ref BSTNode!(K, E) rhs){ 382 return key - rhs.key; 383 } 384 /** 385 * Returns the total height. 386 */ 387 @nogc @property size_t height(){ 388 if(left is null && right is null){ 389 return 1; 390 }else{ 391 size_t l = 1, r = 1; 392 if(left !is null){ 393 l += left.height; 394 } 395 if(right !is null){ 396 r += right.height; 397 } 398 if(l >= r){ 399 return l; 400 }else{ 401 return r; 402 } 403 } 404 } 405 /** 406 * Returns the balance of the node. Minus if LHS is heavier, plus if RHS is heavier. 407 */ 408 @nogc @property sizediff_t bal(){ 409 if(left is null && right is null){ 410 return 0; 411 }else{ 412 size_t l, r; 413 if(left !is null){ 414 l = left.height; 415 } 416 if(right !is null){ 417 r = right.height; 418 } 419 return r - l; 420 } 421 } 422 /** 423 * String representation for debugging. 424 */ 425 public string toString(){ 426 import std.conv; 427 string result = "[" ~ to!string(key) ~ ":" ~ to!string(elem) ~ ";" ~ to!string(bal) ~ ";"; 428 if(left is null){ 429 result ~= "lhs: null "; 430 }else{ 431 result ~= " lhs: " ~ left.toString; 432 } 433 if(right is null){ 434 result ~= "rhs: null "; 435 }else{ 436 result ~= " rhs: " ~ right.toString; 437 } 438 return result ~ "]"; 439 } 440 }