/** * Arrays tooling * * Authors: Tristan Brice Velloza Kildaire (deavmi) */ module niknaks.arrays; import niknaks.functional : Predicate; /** * Checks if the given value is present in * the given array * * Params: * array = the array to check against * value = the value to check prescence * for * Returns: `true` if present, `false` * otherwise */ public bool isPresent(T)(T[] array, T value) { if(array.length == 0) { return false; } else { foreach(T cur; array) { if(cur == value) { return true; } } return false; } } /** * Tests the `isPresent!(T)(T[], T)` function * * Case: Non-empty array */ unittest { ubyte[] values = [1,2,3]; foreach(ubyte value; values) { assert(isPresent(values, value)); } assert(isPresent(values, 0) == false); assert(isPresent(values, 5) == false); } /** * Tests the `isPresent!(T)(T[], T)` function * * Case: Empty array */ unittest { assert(isPresent([], 1) == false); } /** * Given an array of values this tries to find * the next free value of which is NOT present * within the given array. * * If the array provided is emptied then a value * will always be found and will be that of `T.init`. * * Params: * used = the array of values * found = the found free value * Returns: `true` if a free value was found * otherwise `false` */ @nogc public bool findNextFree(T)(T[] used, ref T found) if(__traits(isIntegral, T)) { // Temporary value used for searching T tmp = T.init; // If the array is empty then the first // ... found value may as well be T.init if(used.length == 0) { found = tmp; return true; } else { // Is the starting value in use? // If it is increment it such that // ... looping infinite rule in the // ... upcoming while loop works // ... as expected if(isPresent(used, tmp)) { tmp++; } // If not, then we found it else { found = tmp; return true; } // Loop till we hit starting value while(tmp != T.init) { if(isPresent(used, tmp)) { tmp++; } else { found = tmp; return true; } } // We exited loop because we exhausted all possible values return false; } } /** * Tests the `findNextFree!(T)(T[], ref T)` function * * Case: First value is free + non-empty array */ unittest { ubyte[] values = [1,2,3]; ubyte free; bool status = findNextFree(values, free); assert(status == true); assert(isPresent(values, free) == false); } /** * Tests the `findNextFree!(T)(T[], ref T)` function * * Case: First value is unfree + non-empty array */ unittest { ubyte[] values = [0,2,3]; ubyte free; bool status = findNextFree(values, free); assert(status == true); assert(isPresent(values, free) == false); } /** * Tests the `findNextFree!(T)(T[], ref T)` function * * Case: Array is empty, first value should be T.init */ unittest { ubyte[] values = []; ubyte free; bool status = findNextFree(values, free); assert(status == true); assert(free == ubyte.init); assert(isPresent(values, free) == false); } version(unittest) { import std.stdio : writeln; } /** * Tests the `findNextFree!(T)(T[], ref T)` function * * Case: All values are unfree */ unittest { // Populate entire array with 0 through 255 ubyte[] values; static foreach(ushort val; 0..256) { values~=val; } writeln(values); ubyte free; bool status = findNextFree(values, free); assert(status == false); // Ensure none of the values are present foreach(ubyte i; values) { assert(isPresent(values, i) == true); } } /** * Filters items by the given predicate * * Params: * filterIn = the array to filer * predicate = the predicate to use * filterOut = output array */ public void filter(T)(T[] filterIn, Predicate!(T) predicate, ref T[] filterOut) { foreach(T t; filterIn) { if(predicate(t)) { filterOut ~= t; } } } version(unittest) { import niknaks.functional : predicateOf; } /** * Tests the array filtering method */ unittest { bool onlyEven(int i) { return i % 2 == 0; } int[] vals = [0, 1, 2, 3]; int[] vals_expected = [0, 2]; int[] vals_got; // TODO: See why not auto detecting the array type filter!(int)(vals, predicateOf!(onlyEven), vals_got); assert(vals_got == vals_expected); } /** * Shifts a subset of the elements of * the given array to a given position * either from the left or right. * * Optionally allowing the shrinking * of the array after the process, * otherwise the last element shifted's * previous value will be set to the * value specified. * * Params: * array = the input array * position = the position to shift * onto * rightwards = if `true` then shift * elements into the position rightwards, * else leftwards (which is also the default) * shrink = if set to `true` then * the array will be resized to exclude * the now "empty" element * filler = the value to place in * the space where the last element * shifted no longer occupies, by default * this is `T.init` * Returns: the shifted array */ public T[] shiftInto(T)(T[] array, size_t position, bool rightwards = false, bool shrink = false, T filler = T.init) { // Out of range if(position >= array.length) { return array; } // if rightwards if(rightwards) { // nothing further left than index 0 if(!position) { return array; } for(size_t i = position; i > 0; i--) { array[i] = array[i-1]; } // no shrink, then fill with filler if(!shrink) { array[0] = filler; } // chomp left-hand side else { array = array[1..$]; } } // if leftwards else { // nothing furtherright if(position == array.length-1) { return array; } for(size_t i = position; i < array.length-1; i++) { array[i] = array[i+1]; } // no shrink, then fill with filler if(!shrink) { array[$-1] = filler; } // chomp right-hand side else { array = array[0..$-1]; } } return array; } /** * Rightwards shifting into * * See_Also: `shiftInto` */ public T[] shiftIntoRightwards(T)(T[] array, size_t position, bool shrink = false) { return shiftInto(array, position, true, shrink); } /** * Tests the rightwards shifting */ unittest { int[] numbas = [1, 5, 2]; numbas = numbas.shiftIntoRightwards(1); // should now be [0, 1, 2] writeln(numbas); assert(numbas == [0, 1, 2]); numbas = [1, 5, 2]; numbas = numbas.shiftIntoRightwards(0); // should now be [1, 5, 2] writeln(numbas); assert(numbas == [1, 5, 2]); numbas = [1, 5, 2]; numbas = numbas.shiftIntoRightwards(2); // should now be [0, 1, 5] writeln(numbas); assert(numbas == [0, 1, 5]); numbas = [1, 2]; numbas = numbas.shiftIntoRightwards(1); // should now be [0, 1] writeln(numbas); assert(numbas == [0, 1]); numbas = [1, 2]; numbas = numbas.shiftIntoRightwards(0); // should now be [1, 2] writeln(numbas); assert(numbas == [1, 2]); numbas = []; numbas = numbas.shiftIntoRightwards(0); // should now be [] writeln(numbas); assert(numbas == []); numbas = [1, 5, 2]; numbas = numbas.shiftIntoRightwards(1, true); // should now be [1, 2] writeln(numbas); assert(numbas == [1, 2]); } /** * Leftwards shifting into * * See_Also: `shiftInto` */ public T[] shiftIntoLeftwards(T)(T[] array, size_t position, bool shrink = false) { return shiftInto(array, position, false, shrink); } /** * Tests the leftwards shifting */ unittest { int[] numbas = [1, 5, 2]; numbas = numbas.shiftIntoLeftwards(1); // should now be [1, 2, 0] writeln(numbas); assert(numbas == [1, 2, 0]); numbas = [1, 5, 2]; numbas = numbas.shiftIntoLeftwards(0); // should now be [5, 2, 0] writeln(numbas); assert(numbas == [5, 2, 0]); numbas = [1, 5, 2]; numbas = numbas.shiftIntoLeftwards(2); // should now be [1, 5, 2] writeln(numbas); assert(numbas == [1, 5, 2]); numbas = []; numbas = numbas.shiftIntoLeftwards(0); // should now be [] writeln(numbas); assert(numbas == []); numbas = [1, 5, 2]; numbas = numbas.shiftIntoLeftwards(1, true); // should now be [1, 2] writeln(numbas); assert(numbas == [1, 2]); } /** * Removes the element at the * provided position in the * given array * * Params: * array = the array * position = position of * element to remove * Returns: the array */ public T[] removeResize(T)(T[] array, size_t position) { return array.shiftInto(position, false, true); } /** * Tests removing an element from an array */ unittest { int[] numbas = [1, 5, 2]; numbas = numbas.removeResize(1); // should now be [1, 2] writeln(numbas); assert(numbas == [1, 2]); }