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 }