1 /*
2 Copyright (c) 2015-2021 Timur Gafarov
3 
4 Boost Software License - Version 1.0 - August 17th, 2003
5 
6 Permission is hereby granted, free of charge, to any person or organization
7 obtaining a copy of the software and accompanying documentation covered by
8 this license (the "Software") to use, reproduce, display, distribute,
9 execute, and transmit the Software, and to prepare derivative works of the
10 Software, and to permit third-parties to whom the Software is furnished to
11 do so, all subject to the following:
12 
13 The copyright notices in the Software and this entire statement, including
14 the above license grant, this restriction and the following disclaimer,
15 must be included in all copies of the Software, in whole or in part, and
16 all derivative works of the Software, unless such copies or derivative
17 works are solely in the form of machine-executable object code generated by
18 a source language processor.
19 
20 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
23 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
24 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
25 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26 DEALINGS IN THE SOFTWARE.
27 */
28 
29 /**
30  * Dynamic (expandable) array with random access
31  *
32  * Copyright: Timur Gafarov 2015-2021.
33  * License: $(LINK2 boost.org/LICENSE_1_0.txt, Boost License 1.0).
34  * Authors: Timur Gafarov, Roman Vlasov, Andrey Penechko, Eugene Wissner, Roman Chistokhodov, aferust, ijet
35  */
36 module dlib.container.array;
37 
38 import std.traits;
39 import dlib.core.memory;
40 
41 /**
42  * GC-free dynamic array implementation.
43  * Very efficient for small-sized arrays.
44  */
45 struct Array(T, size_t chunkSize = 32)
46 {
47   private:
48     T[chunkSize] staticStorage;
49     T[] dynamicStorage;
50     uint numChunks = 0;
51     uint pos = 0;
52 
53     /**
54      * Get pointer to stored data
55      */
56     private T* storage() nothrow
57     {
58         if (numChunks == 0)
59             return staticStorage.ptr;
60         else
61             return dynamicStorage.ptr;
62     }
63 
64     private const(T)* readOnlyStorage() const nothrow
65     {
66         if (numChunks == 0)
67             return staticStorage.ptr;
68         else
69             return dynamicStorage.ptr;
70     }
71 
72     private void addChunk()
73     {
74         if (numChunks == 0)
75         {
76             dynamicStorage = New!(T[])(chunkSize);
77         }
78         else
79         {
80             reallocateArray(
81                 dynamicStorage,
82                 dynamicStorage.length + chunkSize);
83         }
84         numChunks++;
85     }
86 
87     ///
88     unittest
89     {
90         Array!int arr;
91         scope(exit) arr.free();
92 
93         assert(arr.length == 0);
94         arr.addChunk();
95         assert(arr.length == 0);
96     }
97 
98   public:
99     /**
100      * Returns true if the array uses dynamic memory.
101      */
102     @property bool isDynamic() const
103     {
104         return dynamicStorage.length > 0;
105     }
106 
107     /**
108      * Preallocate memory without resizing.
109      */
110     void reserve(const(size_t) amount)
111     {
112         if (amount > pos && amount > staticStorage.length)
113         {
114             if (numChunks == 0)
115             {
116                 dynamicStorage = New!(T[])(amount);
117                 foreach(i, v; staticStorage)
118                     dynamicStorage[i] = v;
119             }
120             else
121             {
122                 reallocateArray(dynamicStorage, amount);
123             }
124 
125             numChunks = cast(uint)(amount / 32 + amount % 32);
126         }
127     }
128 
129     ///
130     unittest
131     {
132         Array!int arr;
133         arr.reserve(100);
134         assert(arr.length == 0);
135         assert(arr.dynamicStorage.length == 100);
136     }
137 
138     /**
139      * Resize array and initialize newly added elements with initValue.
140      */
141     void resize(const(size_t) newLen, T initValue)
142     {
143         if (newLen > pos)
144         {
145             reserve(newLen);
146             for(size_t i = pos; i < newLen; i++)
147             {
148                 storage[i] = initValue;
149             }
150         }
151 
152         pos = cast(uint)newLen;
153     }
154 
155     ///
156     unittest
157     {
158         Array!int arr;
159         arr.resize(100, 1);
160         assert(arr.length == 100);
161         assert(arr[50] == 1);
162     }
163 
164     /**
165      * Shift contents of array to the right.
166      * It inreases the size of array by 1.
167      * The first element becomes default initialized.
168      */
169     void shiftRight()
170     {
171         insertBack(T.init);
172 
173         for(uint i = pos-1; i > 0; i--)
174         {
175             storage[i] = storage[i-1];
176         }
177     }
178 
179     ///
180     unittest
181     {
182         Array!int arr;
183         scope(exit) arr.free();
184 
185         arr.shiftRight();
186         assert(arr.length == 1);
187         assert(arr[0] == int.init);
188 
189         arr[0] = 1;
190         arr.insertBack([2,3]);
191 
192         arr.shiftRight();
193         assert(arr.length == 4);
194         assert(arr[0] == 1);
195         assert(arr[1] == 1);
196         assert(arr[2] == 2);
197         assert(arr[3] == 3);
198     }
199 
200     /**
201      * Shift contents of array to the left by n positions.
202      * Does not change the size of array.
203      * n of last elements becomes default initialized.
204      */
205     void shiftLeft(const(uint) n)
206     {
207         for(uint i = 0; i < pos; i++)
208         {
209             if (n + i < pos)
210                 storage[i] = storage[n + i];
211             else
212                 storage[i] = T.init;
213         }
214     }
215 
216     ///
217     unittest
218     {
219         Array!int arr;
220         scope(exit) arr.free();
221 
222         arr.shiftLeft(1);
223         assert(arr.length == 0);
224 
225         arr.insertBack([1,2,3,4,5]);
226 
227         arr.shiftLeft(2);
228         assert(arr.length == 5);
229         assert(arr[0] == 3);
230         assert(arr[1] == 4);
231         assert(arr[2] == 5);
232         assert(arr[3] == int.init);
233         assert(arr[4] == int.init);
234     }
235 
236     /**
237      * Append single element c to the end.
238      */
239     void insertBack(T c)
240     {
241         if (numChunks == 0)
242         {
243             staticStorage[pos] = c;
244             pos++;
245             if (pos == chunkSize)
246             {
247                 addChunk();
248                 foreach(i, ref v; dynamicStorage)
249                     v = staticStorage[i];
250             }
251         }
252         else
253         {
254             if (pos == dynamicStorage.length)
255                 addChunk();
256 
257             dynamicStorage[pos] = c;
258             pos++;
259         }
260     }
261 
262     ///
263     unittest
264     {
265         Array!int arr;
266         scope(exit) arr.free();
267 
268         foreach(i; 0..16) {
269             arr.insertBack(i);
270         }
271         assert(arr.length == 16);
272         arr.insertBack(16);
273         assert(arr.length == 17);
274         assert(arr[16] == 16);
275     }
276 
277     /**
278      * Append element to the start.
279      */
280     void insertFront(T c)
281     {
282         shiftRight();
283         storage[0] = c;
284     }
285 
286     ///
287     unittest
288     {
289         Array!int arr;
290         scope(exit) arr.free();
291 
292         arr.insertFront(1);
293         arr.insertBack(2);
294         arr.insertFront(0);
295         assert(arr.data == [0,1,2]);
296     }
297 
298     /**
299      * Append all elements of slice s to the end.
300      */
301     void insertBack(const(T)[] s)
302     {
303         foreach(c; s)
304             insertBack(cast(T)c);
305     }
306 
307     ///
308     unittest
309     {
310         Array!int arr;
311         scope(exit) arr.free();
312 
313         arr.insertBack([1,2,3,4]);
314         assert(arr.data == [1,2,3,4]);
315         arr.insertBack([5,6,7,8]);
316         assert(arr.data == [1,2,3,4,5,6,7,8]);
317     }
318 
319     /**
320      * Append all elements of slice s to the start.
321      */
322     void insertFront(const(T)[] s)
323     {
324         foreach_reverse(c; s)
325             insertFront(cast(T)c);
326     }
327 
328     ///
329     unittest
330     {
331         Array!int arr;
332         scope(exit) arr.free();
333 
334         arr.insertFront([5,6,7,8]);
335         assert(arr.data == [5,6,7,8]);
336         arr.insertFront([1,2,3,4]);
337         assert(arr.data == [1,2,3,4,5,6,7,8]);
338     }
339 
340     /**
341      * Same as insertBack, but in operator form.
342      */
343     auto opOpAssign(string op)(T c) if (op == "~")
344     {
345         insertBack(c);
346         return this;
347     }
348 
349     ///
350     unittest
351     {
352         Array!int arr;
353         scope(exit) arr.free();
354 
355         arr ~= 1;
356         arr ~= 2;
357         assert(arr.data == [1,2]);
358     }
359 
360     /**
361      * Same as insertBack, but in operator form.
362      */
363     auto opOpAssign(string op)(const(T)[] s) if (op == "~")
364     {
365         insertBack(s);
366         return this;
367     }
368 
369     ///
370     unittest
371     {
372         Array!int arr;
373         scope(exit) arr.free();
374 
375         arr ~= [1,2,3];
376         assert(arr.data == [1,2,3]);
377     }
378 
379     /**
380      * Remove n of elements from the end.
381      * Returns: number of removed elements.
382      */
383     uint removeBack(const(uint) n)
384     {
385         if (pos == n)
386         {
387             pos = 0;
388             return n;
389         }
390         else if (pos >= n)
391         {
392             pos -= n;
393             return n;
394         }
395         else
396         {
397             uint res = pos;
398             pos = 0;
399             return res;
400         }
401     }
402 
403     ///
404     unittest
405     {
406         Array!int arr;
407         scope(exit) arr.free();
408 
409         arr.insertBack([1,2,3]);
410         assert(arr.removeBack(3) == 3);
411         assert(arr.length == 0);
412 
413         arr.insertBack([1,2,3,4]);
414         assert(arr.removeBack(2) == 2);
415         assert(arr.data == [1,2]);
416 
417         assert(arr.removeBack(3) == 2);
418         assert(arr.length == 0);
419     }
420 
421     /**
422      * Remove n of elements from the start.
423      * Returns: number of removed elements.
424      */
425     uint removeFront(const(uint) n)
426     {
427         if (pos == n)
428         {
429             pos = 0;
430             return n;
431         }
432         else if (pos > n)
433         {
434             shiftLeft(n);
435             pos -= n;
436             return n;
437         }
438         else
439         {
440             uint res = pos;
441             pos = 0;
442             return res;
443         }
444     }
445 
446     ///
447     unittest
448     {
449         Array!int arr;
450         scope(exit) arr.free();
451 
452         arr.insertBack([1,2,3]);
453         assert(arr.removeFront(3) == 3);
454 
455         arr.insertBack([1,2,3,4]);
456         assert(arr.removeFront(2) == 2);
457         assert(arr.data == [3,4]);
458 
459         assert(arr.removeFront(3) == 2);
460         assert(arr.length == 0);
461     }
462 
463     /**
464      * Inserts an element by a given index
465      * (resizing an array and shifting elements).
466      */
467     void insertKey(const(size_t) i, T v)
468     {
469         if (i < pos)
470         {
471             T* s = storage();
472 
473             insertBack(T.init);
474 
475             for (size_t p = pos-1; p > i; p--)
476             {
477                 s[p] = s[p-1];
478             }
479 
480             s[i] = v;
481         }
482     }
483 
484     unittest
485     {
486         Array!int arr;
487         scope(exit) arr.free();
488 
489         arr.insertBack([1, 4, 5]);
490 
491         arr.insertKey(1, 7);
492         assert(arr.length == 4);
493         assert(arr.data == [1, 7, 4, 5]);
494     }
495 
496     /**
497      * Removes an element by a given index.
498      */
499     void removeKey(const(size_t) i)
500     {
501         if (i < pos)
502         {
503             T* s = storage();
504             for (size_t p = i+1; p <= pos; p++)
505             {
506                 s[p-1] = s[p];
507             }
508 
509             pos--;
510         }
511     }
512 
513     unittest
514     {
515         Array!int arr;
516         scope(exit) arr.free();
517 
518         arr.insertBack([1, 4, 5]);
519 
520         arr.removeKey(1);
521         assert(arr.length == 2);
522         assert(arr.data == [1, 5]);
523     }
524 
525     alias insertAt = insertKey;
526     alias removeAt = removeKey;
527 
528     /**
529      * If obj is in array, remove its first occurence and return true.
530      * Otherwise do nothing and return false.
531      */
532     bool removeFirst(T obj)
533     {
534         size_t index;
535         bool found = false;
536 
537         for (size_t i = 0; i < data.length; i++)
538         {
539             T o = data[i];
540 
541             static if (isArray!T)
542             {
543                 if (o[] == obj[])
544                 {
545                     index = i;
546                     found = true;
547                     break;
548                 }
549             }
550             else
551             {
552                 if (o is obj)
553                 {
554                     index = i;
555                     found = true;
556                     break;
557                 }
558             }
559         }
560 
561         if (found)
562         {
563             removeKey(index);
564             return true;
565         }
566 
567         return false;
568     }
569 
570     // For backward compatibility
571     alias append = insertBack;
572     alias appendLeft = insertFront;
573     alias remove = removeBack;
574     alias removeLeft = removeFront;
575 
576     /**
577      * Get number of elements in array.
578      */
579     size_t length() const nothrow
580     {
581         return pos;
582     }
583 
584     alias opDollar = length;
585 
586     ///
587     unittest
588     {
589         Array!int arr;
590         scope(exit) arr.free();
591 
592         arr.insertBack([1,2,3]);
593         assert(arr.length == 3);
594     }
595 
596     /**
597      * Get slice of data
598      */
599     T[] data() nothrow
600     {
601         return storage[0..pos];
602     }
603 
604     const(T)[] readOnlyData() const nothrow
605     {
606         return readOnlyStorage[0..pos];
607     }
608 
609     ///
610     unittest
611     {
612         Array!(int,4) arr;
613         scope(exit) arr.free();
614 
615         foreach(i; 0..6) {
616             arr.insertBack(i);
617         }
618 
619         assert(arr.data == [0,1,2,3,4,5]);
620     }
621 
622     /**
623      * Access element by index.
624      */
625     T opIndex(const(size_t) index)
626     {
627         return data[index];
628     }
629 
630     /**
631      * Access by slice.
632      */
633     T[] opSlice(const(size_t) start, const(size_t) end)
634     {
635         return data[start..end];
636     }
637 
638     /**
639      * Set element t for index.
640      */
641     T opIndexAssign(T t, const(size_t) index)
642     {
643         data[index] = t;
644         return t;
645     }
646 
647     /**
648      * Iterating over array via foreach.
649      */
650     int opApply(scope int delegate(size_t i, ref T) dg)
651     {
652         int result = 0;
653 
654         foreach(i, ref v; data)
655         {
656             result = dg(i, v);
657             if (result)
658                 break;
659         }
660 
661         return result;
662     }
663 
664     /**
665      * Iterating over array via foreach_reverse.
666      */
667     int opApplyReverse(scope int delegate(size_t i, ref T) dg)
668     {
669         int result = 0;
670 
671         for(size_t i =  length; i-- > 0; )
672         {
673             result = dg(i, data[i]);
674             if (result)
675                 break;
676         }
677 
678         return result;
679     }
680 
681     ///
682     unittest
683     {
684         Array!(int,4) arr;
685         scope(exit) arr.free();
686 
687         int[4] values;
688         arr.insertBack([1,2,3,4]);
689         foreach(i, ref val; arr) {
690             values[i] = val;
691             if(values[i] == 4) {
692                 break;
693             }
694         }
695         assert(values[] == arr.data);
696     }
697 
698     /**
699      * Iterating over array via foreach.
700      */
701     int opApply(scope int delegate(ref T) dg)
702     {
703         int result = 0;
704 
705         foreach(i, ref v; data)
706         {
707             result = dg(v);
708             if (result)
709                 break;
710         }
711 
712         return 0;
713     }
714 
715      /**
716      * Iterating over array via foreach_reverse.
717      */
718     int opApplyReverse(scope int delegate(ref T) dg)
719     {
720         int result = 0;
721 
722         for(size_t i = length; i-- > 0; )
723         {
724             result = dg(data[i]);
725             if (result)
726                 break;
727         }
728 
729         return result;
730     }
731 
732     ///
733     unittest
734     {
735         Array!(int,4) arr;
736         scope(exit) arr.free();
737 
738         int[] values;
739         arr.insertBack([1,2,3,4]);
740         foreach(ref val; arr) {
741             values ~= val;
742         }
743         assert(values[] == arr.data);
744     }
745 
746     /**
747      * Free dynamically allocated memory used by array.
748      */
749     void free()
750     {
751         if (dynamicStorage.length)
752             Delete(dynamicStorage);
753         numChunks = 0;
754         pos = 0;
755     }
756 }
757 
758 void reallocateArray(T)(ref T[] buffer, const(size_t) len)
759 {
760     T[] buffer2 = New!(T[])(len);
761     for(uint i = 0; i < buffer2.length; i++)
762         if (i < buffer.length)
763             buffer2[i] = buffer[i];
764     Delete(buffer);
765     buffer = buffer2;
766 }
767 
768 ///
769 unittest
770 {
771     auto arr = New!(int[])(3);
772     arr[0] = 1; arr[1] = 2; arr[2] = 3;
773 
774     reallocateArray(arr, 2);
775     assert(arr.length == 2);
776     assert(arr[0] == 1);
777     assert(arr[1] == 2);
778 
779     reallocateArray(arr, 4);
780     assert(arr.length == 4);
781     assert(arr[0] == 1);
782     assert(arr[1] == 2);
783 }