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