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 }