1 /** 2 * Arrays tooling 3 * 4 * Authors: Tristan Brice Velloza Kildaire (deavmi) 5 */ 6 module niknaks.arrays; 7 8 import niknaks.functional : Predicate; 9 10 /** 11 * Checks if the given value is present in 12 * the given array 13 * 14 * Params: 15 * array = the array to check against 16 * value = the value to check prescence 17 * for 18 * Returns: `true` if present, `false` 19 * otherwise 20 */ 21 @safe @nogc 22 public bool isPresent(T)(T[] array, T value) 23 { 24 if(array.length == 0) 25 { 26 return false; 27 } 28 else 29 { 30 foreach(T cur; array) 31 { 32 if(cur == value) 33 { 34 return true; 35 } 36 } 37 38 return false; 39 } 40 } 41 42 /** 43 * Tests the `isPresent!(T)(T[], T)` function 44 * 45 * Case: Non-empty array 46 */ 47 unittest 48 { 49 ubyte[] values = [1,2,3]; 50 foreach(ubyte value; values) 51 { 52 assert(isPresent(values, value)); 53 } 54 assert(isPresent(values, 0) == false); 55 assert(isPresent(values, 5) == false); 56 } 57 58 /** 59 * Tests the `isPresent!(T)(T[], T)` function 60 * 61 * Case: Empty array 62 */ 63 unittest 64 { 65 assert(isPresent([], 1) == false); 66 } 67 68 /** 69 * Given an array of values this tries to find 70 * the next free value of which is NOT present 71 * within the given array. 72 * 73 * If the array provided is emptied then a value 74 * will always be found and will be that of `T.init`. 75 * 76 * Params: 77 * used = the array of values 78 * found = the found free value 79 * Returns: `true` if a free value was found 80 * otherwise `false` 81 */ 82 @safe @nogc 83 public bool findNextFree(T)(T[] used, ref T found) if(__traits(isIntegral, T)) 84 { 85 // Temporary value used for searching 86 T tmp = T.init; 87 88 // If the array is empty then the first 89 // ... found value may as well be T.init 90 if(used.length == 0) 91 { 92 found = tmp; 93 return true; 94 } 95 else 96 { 97 // Is the starting value in use? 98 // If it is increment it such that 99 // ... looping infinite rule in the 100 // ... upcoming while loop works 101 // ... as expected 102 if(isPresent(used, tmp)) 103 { 104 tmp++; 105 } 106 // If not, then we found it 107 else 108 { 109 found = tmp; 110 return true; 111 } 112 113 // Loop till we hit starting value 114 while(tmp != T.init) 115 { 116 if(isPresent(used, tmp)) 117 { 118 tmp++; 119 } 120 else 121 { 122 found = tmp; 123 return true; 124 } 125 } 126 127 // We exited loop because we exhausted all possible values 128 return false; 129 } 130 } 131 132 /** 133 * Tests the `findNextFree!(T)(T[], ref T)` function 134 * 135 * Case: First value is free + non-empty array 136 */ 137 unittest 138 { 139 ubyte[] values = [1,2,3]; 140 141 ubyte free; 142 bool status = findNextFree(values, free); 143 assert(status == true); 144 assert(isPresent(values, free) == false); 145 } 146 147 /** 148 * Tests the `findNextFree!(T)(T[], ref T)` function 149 * 150 * Case: First value is unfree + non-empty array 151 */ 152 unittest 153 { 154 ubyte[] values = [0,2,3]; 155 156 ubyte free; 157 bool status = findNextFree(values, free); 158 assert(status == true); 159 assert(isPresent(values, free) == false); 160 } 161 162 /** 163 * Tests the `findNextFree!(T)(T[], ref T)` function 164 * 165 * Case: Array is empty, first value should be T.init 166 */ 167 unittest 168 { 169 ubyte[] values = []; 170 171 ubyte free; 172 bool status = findNextFree(values, free); 173 assert(status == true); 174 assert(free == ubyte.init); 175 assert(isPresent(values, free) == false); 176 } 177 178 version(unittest) 179 { 180 import std.stdio : writeln; 181 } 182 183 /** 184 * Tests the `findNextFree!(T)(T[], ref T)` function 185 * 186 * Case: All values are unfree 187 */ 188 unittest 189 { 190 // Populate entire array with 0 through 255 191 ubyte[] values; 192 static foreach(ushort val; 0..256) 193 { 194 values~=val; 195 } 196 writeln(values); 197 198 ubyte free; 199 bool status = findNextFree(values, free); 200 assert(status == false); 201 202 // Ensure none of the values are present 203 foreach(ubyte i; values) 204 { 205 assert(isPresent(values, i) == true); 206 } 207 } 208 209 /** 210 * Filters items by the given predicate 211 * 212 * Params: 213 * filterIn = the array to filer 214 * predicate = the predicate to use 215 * filterOut = output array 216 */ 217 public void filter(T)(T[] filterIn, Predicate!(T) predicate, ref T[] filterOut) 218 { 219 foreach(T t; filterIn) 220 { 221 if(predicate(t)) 222 { 223 filterOut ~= t; 224 } 225 } 226 } 227 228 version(unittest) 229 { 230 import niknaks.functional : predicateOf; 231 } 232 233 /** 234 * Tests the array filtering method 235 */ 236 unittest 237 { 238 bool onlyEven(int i) 239 { 240 return i % 2 == 0; 241 } 242 243 int[] vals = [0, 1, 2, 3]; 244 245 int[] vals_expected = [0, 2]; 246 int[] vals_got; 247 248 // TODO: See why not auto detecting the array type 249 filter!(int)(vals, predicateOf!(onlyEven), vals_got); 250 assert(vals_got == vals_expected); 251 } 252 253 /** 254 * Shifts a subset of the elements of 255 * the given array to a given position 256 * either from the left or right. 257 * 258 * Optionally allowing the shrinking 259 * of the array after the process, 260 * otherwise the last element shifted's 261 * previous value will be set to the 262 * value specified. 263 * 264 * Params: 265 * array = the input array 266 * position = the position to shift 267 * onto 268 * rightwards = if `true` then shift 269 * elements into the position rightwards, 270 * else leftwards (which is also the default) 271 * shrink = if set to `true` then 272 * the array will be resized to exclude 273 * the now "empty" element 274 * filler = the value to place in 275 * the space where the last element 276 * shifted no longer occupies, by default 277 * this is `T.init` 278 * Returns: the shifted array 279 */ 280 public T[] shiftInto(T)(T[] array, size_t position, bool rightwards = false, bool shrink = false, T filler = T.init) 281 { 282 // Out of range 283 if(position >= array.length) 284 { 285 return array; 286 } 287 288 // if rightwards 289 if(rightwards) 290 { 291 // nothing further left than index 0 292 if(!position) 293 { 294 return array; 295 } 296 297 for(size_t i = position; i > 0; i--) 298 { 299 array[i] = array[i-1]; 300 } 301 302 // no shrink, then fill with filler 303 if(!shrink) 304 { 305 array[0] = filler; 306 } 307 // chomp left-hand side 308 else 309 { 310 array = array[1..$]; 311 } 312 } 313 // if leftwards 314 else 315 { 316 // nothing furtherright 317 if(position == array.length-1) 318 { 319 return array; 320 } 321 322 for(size_t i = position; i < array.length-1; i++) 323 { 324 array[i] = array[i+1]; 325 } 326 327 // no shrink, then fill with filler 328 if(!shrink) 329 { 330 array[$-1] = filler; 331 } 332 // chomp right-hand side 333 else 334 { 335 array = array[0..$-1]; 336 } 337 } 338 339 340 return array; 341 } 342 343 /** 344 * Rightwards shifting into 345 * 346 * See_Also: `shiftInto` 347 */ 348 public T[] shiftIntoRightwards(T)(T[] array, size_t position, bool shrink = false) 349 { 350 return shiftInto(array, position, true, shrink); 351 } 352 353 /** 354 * Tests the rightwards shifting 355 */ 356 unittest 357 { 358 int[] numbas = [1, 5, 2]; 359 numbas = numbas.shiftIntoRightwards(1); 360 361 // should now be [0, 1, 2] 362 writeln(numbas); 363 assert(numbas == [0, 1, 2]); 364 365 numbas = [1, 5, 2]; 366 numbas = numbas.shiftIntoRightwards(0); 367 368 // should now be [1, 5, 2] 369 writeln(numbas); 370 assert(numbas == [1, 5, 2]); 371 372 numbas = [1, 5, 2]; 373 numbas = numbas.shiftIntoRightwards(2); 374 375 // should now be [0, 1, 5] 376 writeln(numbas); 377 assert(numbas == [0, 1, 5]); 378 379 numbas = [1, 2]; 380 numbas = numbas.shiftIntoRightwards(1); 381 382 // should now be [0, 1] 383 writeln(numbas); 384 assert(numbas == [0, 1]); 385 386 numbas = [1, 2]; 387 numbas = numbas.shiftIntoRightwards(0); 388 389 // should now be [1, 2] 390 writeln(numbas); 391 assert(numbas == [1, 2]); 392 393 numbas = []; 394 numbas = numbas.shiftIntoRightwards(0); 395 396 // should now be [] 397 writeln(numbas); 398 assert(numbas == []); 399 400 numbas = [1, 5, 2]; 401 numbas = numbas.shiftIntoRightwards(1, true); 402 403 // should now be [1, 2] 404 writeln(numbas); 405 assert(numbas == [1, 2]); 406 } 407 408 /** 409 * Leftwards shifting into 410 * 411 * See_Also: `shiftInto` 412 */ 413 public T[] shiftIntoLeftwards(T)(T[] array, size_t position, bool shrink = false) 414 { 415 return shiftInto(array, position, false, shrink); 416 } 417 418 /** 419 * Tests the leftwards shifting 420 */ 421 unittest 422 { 423 int[] numbas = [1, 5, 2]; 424 numbas = numbas.shiftIntoLeftwards(1); 425 426 // should now be [1, 2, 0] 427 writeln(numbas); 428 assert(numbas == [1, 2, 0]); 429 430 numbas = [1, 5, 2]; 431 numbas = numbas.shiftIntoLeftwards(0); 432 433 // should now be [5, 2, 0] 434 writeln(numbas); 435 assert(numbas == [5, 2, 0]); 436 437 numbas = [1, 5, 2]; 438 numbas = numbas.shiftIntoLeftwards(2); 439 440 // should now be [1, 5, 2] 441 writeln(numbas); 442 assert(numbas == [1, 5, 2]); 443 444 numbas = []; 445 numbas = numbas.shiftIntoLeftwards(0); 446 447 // should now be [] 448 writeln(numbas); 449 assert(numbas == []); 450 451 numbas = [1, 5, 2]; 452 numbas = numbas.shiftIntoLeftwards(1, true); 453 454 // should now be [1, 2] 455 writeln(numbas); 456 assert(numbas == [1, 2]); 457 } 458 459 /** 460 * Removes the element at the 461 * provided position in the 462 * given array 463 * 464 * Params: 465 * array = the array 466 * position = position of 467 * element to remove 468 * Returns: the array 469 */ 470 public T[] removeResize(T)(T[] array, size_t position) 471 { 472 return array.shiftInto(position, false, true); 473 } 474 475 /** 476 * Tests removing an element from an array 477 */ 478 unittest 479 { 480 int[] numbas = [1, 5, 2]; 481 numbas = numbas.removeResize(1); 482 483 // should now be [1, 2] 484 writeln(numbas); 485 assert(numbas == [1, 2]); 486 } 487 488 /** 489 * Inserts the given value into 490 * the array at the provided index 491 * 492 * Params: 493 * array = the array to insert 494 * into 495 * position = the position to 496 * insert at 497 * value = the value to insert 498 * Returns: the updated array 499 */ 500 public T[] insertAt(T)(T[] array, size_t position, T value) 501 { 502 if(position > array.length) 503 { 504 return array; 505 } 506 507 // Case: Right at end 508 if(position == array.length) 509 { 510 array ~= value; 511 return array; 512 } 513 // Anywhere else 514 else 515 { 516 // Make space for a single new element 517 array.length++; 518 519 // Cha-cha to the right 520 for(size_t i = array.length-1; i > position; i--) 521 { 522 array[i] = array[i-1]; 523 } 524 525 // Overwrite 526 array[position] = value; 527 return array; 528 } 529 } 530 531 /** 532 * Tests inserting into an array 533 * at the given index 534 */ 535 unittest 536 { 537 int[] vals = []; 538 vals = vals.insertAt(0, 1); 539 assert(vals == [1]); 540 541 vals = vals.insertAt(0, 69); 542 assert(vals == [69, 1]); 543 544 vals = vals.insertAt(1, 68); 545 assert(vals == [69, 68, 1]); 546 547 vals = vals.insertAt(3, 420); 548 assert(vals == [69, 68, 1, 420]); 549 550 // Failure to insert (array stays the same) 551 vals = vals.insertAt(5, 421); 552 assert(vals == [69, 68, 1, 420]); 553 } 554 555 /** 556 * Returns a version of 557 * the input array with 558 * only unique elements. 559 * 560 * If the input array's 561 * length is 0 or 1 then 562 * it is immediately returned 563 * as an optimization. 564 * 565 * Params: 566 * array = the input array 567 * Returns: an array with 568 * only unique elements 569 */ 570 @safe 571 public T[] unique(T)(T[] array) 572 { 573 // optimize 574 if(array.length == 0 || array.length == 1) 575 { 576 return array; 577 } 578 579 T[] newArray; 580 foreach(T elem; array) 581 { 582 if(!isPresent(newArray, elem)) 583 { 584 newArray ~= elem; 585 } 586 } 587 588 return newArray; 589 } 590 591 /** 592 * Tests out using the uniqueness method 593 */ 594 unittest 595 { 596 // Empty or 1 elem should not re-allocate 597 int[] vals = []; 598 int[] newVals = unique(vals); 599 assert(vals.ptr == newVals.ptr); 600 601 vals = [1]; 602 newVals = unique(vals); 603 assert(vals.ptr == newVals.ptr); 604 605 // Copy triggering cases 606 vals = [1,1]; 607 newVals = unique(vals); 608 assert(newVals == [1]); 609 }