1 module anansi.container.array;
2 
3 import std.algorithm, std.conv, std.range, std.stdio, std.traits;
4 
5 version(unittest) {
6     import std.range;
7 }
8 
9 public struct Array(T) {
10 private:
11     this(size_t size, T[] payload) {
12         _size = size;
13         _payload = payload;
14     }
15 
16 public:
17     this(U)(U[] stuff) if (isImplicitlyConvertible!(U, T)) {
18         foreach(s; stuff)
19             insertBack(s);
20     }
21 
22     this(Stuff)(Stuff stuff) if (isInputRange!Stuff && 
23                                  isImplicitlyConvertible!(ElementType!Stuff, T) &&
24                                  !is(Stuff == T[])) {
25         foreach(s; stuff) 
26             insertBack(s);
27     }
28 
29     this(this) {
30         _payload = _payload[0 .. _size].dup;
31     }
32 
33     invariant() { 
34         assert(_size >= 0); 
35         assert(_payload.length >= _size);
36     }
37 
38 public:
39     size_t insertBack(T value) {
40         ensureSpace(_size+1);
41         _payload[_size] = move(value);
42         return _size++;
43     }
44 
45     size_t insert(T value, size_t index) 
46     in {
47         assert(index <= _size);
48     }
49     body {
50         ensureSpace(_size+1);
51         moveAll(_payload[index .. $-1], _payload[index + 1 .. $]);
52         _payload[index] = move(value);
53         _size += 1;
54         return index;
55     }
56 
57     void erase(size_t index) {
58         erase(index, 1);
59     }
60 
61     /**
62      * Erases a contiguous range of items from the array.
63      */
64     void erase(size_t index, size_t count) 
65     in {
66         assert(index < _size, "Index must be smaller than Array.length.");
67         assert((index + count) <= _size, "Range must not overflow array.");
68     }
69     body {
70         auto leftovers = moveAll(_payload[index + count .. $], _payload[index .. $]);
71         foreach(ref t; leftovers) { t = T.init; }
72 
73         _size -= count;
74         if (_size < (_payload.length / 2))
75             _payload.length = _size;
76     }
77 
78     void eraseFrontOfRange(RangeT)(RangeT r) 
79         if (is(RangeT == Range!T) || is(RangeT == Range!(const T)))
80     in {
81         assert (!r.empty, "Range must not be empty.");
82         assert (r._data is _payload);
83     }
84     body {
85         erase(r._index, 1);
86     }
87 
88     void reserve(size_t n) {     
89         if (_payload.length < n)
90             _payload.length = n;
91     }
92 
93     auto opSlice() {
94         return Range!(T)(_payload, _size);
95     }
96 
97     auto opSlice() const {
98         return Range!(const T)(_payload, _size);
99     }
100 
101 public:
102     static struct Range(ElementT) {
103         this(ElementT[] data, size_t size) {
104             _index = 0;
105             _data = data;
106             _size = size;
107         }
108 
109         @property bool empty() const {
110             return _index == _size;
111         }
112 
113         @property size_t length() const {
114             return _size;
115         } 
116 
117         @property ref ElementT front() 
118         in {
119             assert (_index < _size);
120         }
121         body {
122             return _data[_index];
123         }
124 
125         void popFront() 
126         in {
127             assert (_index < _size);
128         }
129         body {
130             ++_index;
131         }
132 
133     private:
134         size_t _index;
135         size_t _size;
136         ElementT[] _data;
137     }
138 
139     alias ConstRange = Range!(const T);
140 
141     /**
142      * Provides [] sytax for the array.
143      */
144     public ref inout(T) opIndex(size_t index) inout
145     in {
146         assert(index < _size, "Index must be less than Array.length.");
147     }
148     body {
149         return _payload[index];
150     }
151 
152     /**
153      * Returns the number of elements currently stored in the array.
154      */
155     public @property size_t length() const {
156         return _size;
157     }
158 
159     /**
160      * Returns the number of items the array could store, given its 
161      * current buffer allocation.
162      */
163     public @property size_t capacity() const {
164         return _payload.length;
165     }
166 
167     public @property Array dup() {
168         return Array(_size, _payload[0 .. _size].dup);
169     }
170 
171     /**
172      * Ensures that there is storage space a minimum number of elements in
173      * the array, possibly over-allocating for efficiency's sake.
174      */
175     private void ensureSpace(size_t n) {
176         if (_payload.length < n) {
177             size_t newSize = 1 + ((_payload.length * 3) / 2);
178             auto newPayload = new T[newSize];
179             moveAll(_payload, newPayload[0 .. _size]);            
180             _payload = newPayload;
181         }
182     }
183 
184     private size_t _size;
185     private T[] _payload;
186 }
187 
188 unittest {
189   writeln("Array: empty construction yields a valid Array.");
190   Array!string s;
191   assert(s.length == 0);
192   assert(s.capacity >= 0);
193 }
194 
195 unittest {
196   writeln("Array: construction from a Range yields a valid Array.");
197   auto data = ["alpha", "beta", "delta", "gamma", "epsilon"];
198   auto s = Array!string(data);
199 
200   assert(s.length == data.length);
201   for(auto i = 0; i < data.length; ++i)
202     assert(s[i] == data[i]);
203 }
204 
205 unittest {
206   writeln("Array: insertion into empty Array yields a valid 1-element array.");
207   Array!string a;
208   auto i = a.insert("hi", 0);
209   assert (a.length == 1, "Incorrect length after insertion.");
210   assert (a[0] == "hi", "Incorrect value at insertion point.");
211   assert (i == 0, "Incorrect insertion point index returned.");
212 }
213 
214 unittest {
215   writeln("Array: insertion at the end of an Array.");
216   auto data = ["alpha", "beta", "delta", "gamma", "epsilon"];
217   auto a = Array!string(data);
218   auto i = a.insert("o hai", a.length);
219   assert (i == data.length, "Incorrect insertion point index returned.");
220 
221   assert (a.length == (data.length+1), "Incorrect length after insertion.");  
222 
223   for(auto n = 0; n < data.length; ++n) 
224     assert (a[n] == data[n]);
225 
226   assert (a[data.length] == "o hai", 
227           "Incorrect value at insertion point.");
228 }
229 
230 unittest {
231   writeln("Array: erasing a single item in the middle of the list.");
232   auto a = Array!int(iota(0, 100, 1));
233   assert(a.length == 100);
234   assert(a[7] == 7);
235 
236   a.erase(7);    
237   assert(a.length == 99);
238   foreach(i; 0 .. 7)
239     assert(a[i] == i);
240 
241   foreach(i; 7 .. 99)
242     assert(a[i] == (i+1));
243 }
244 
245 version(unittest) {
246   /**
247    * A test struct that tracks the execution of its destructor, for use in
248    * testing.
249    */
250   struct DtorTestValue {
251     static int dtorCount = 0;
252     int n;
253     ~this() {
254       if (n != 0) {
255         ++dtorCount;                
256       }
257     }
258   }
259 }
260 
261 
262 unittest {
263   writeln("Array: erasing the only item in an Array.");
264   auto a = Array!DtorTestValue();    
265   a.insertBack(DtorTestValue(1));
266   DtorTestValue.dtorCount = 0;
267   a.erase(0);   
268   assert(a.length == 0);    
269   assert (DtorTestValue.dtorCount == 1, 
270           "bad dtor count: " ~ to!string(DtorTestValue.dtorCount));
271 }
272 
273 unittest {
274   writeln("Array: erasing the first item in an Array.");
275   Array!DtorTestValue a;
276   a.reserve(100);
277   foreach(n; 0 .. 100)
278     a.insertBack(DtorTestValue(n+1));
279 
280   DtorTestValue.dtorCount = 0;
281   a.erase(0);    
282   assert(a.length == 99);
283 
284   assert (DtorTestValue.dtorCount == 1, 
285           "bad dtor count: " ~ to!string(DtorTestValue.dtorCount));
286 
287   foreach(i; 0 .. 99) {
288     assert(a[i].n == (i+2), 
289           "Invalid post-erase value: expected " ~ to!string(i+2) ~ 
290           ", got: " ~ to!string(a[i].n));
291   }
292 }
293 
294 unittest {
295   writeln("Array: erasing the last item in an array.");
296   Array!DtorTestValue a;
297   a.reserve(100);
298   foreach(n; 0 .. 100)
299     a.insertBack(DtorTestValue(n+1));
300 
301   DtorTestValue.dtorCount = 0;
302   a.erase(99);    
303   assert(a.length == 99);
304 
305   assert (DtorTestValue.dtorCount == 1, 
306           "bad dtor count: " ~ to!string(DtorTestValue.dtorCount));
307 
308   foreach(i; 0 .. 99)
309     assert(a[i].n == i+1);
310 }
311 
312 unittest {
313   writeln("Array: erasing a single, essentially random, item.");
314   Array!DtorTestValue a;
315   a.reserve(100);
316   foreach(n; 0 .. 100)
317     a.insertBack(DtorTestValue(n+1));
318 
319   DtorTestValue.dtorCount = 0;
320   a.erase(7);    
321   assert(a.length == 99);
322 
323   assert (DtorTestValue.dtorCount == 1, 
324           "bad dtor count: " ~ to!string(DtorTestValue.dtorCount));
325 
326   foreach(i; 0 .. 7)
327     assert(a[i].n == (i+1));
328 
329   foreach(i; 7 .. 99)
330     assert(a[i].n == (i+2));
331 }
332 
333 unittest {
334   writeln("Array: erasing a range of items from the array.");
335   auto a = Array!int(iota(0, 100, 1));
336   a.erase(25, 50);
337   assert (a.length == 50, 
338     "Post-erasure length is wrong: " ~ to!string(a.length)); 
339 
340   foreach(i; 0 .. 25)
341     assert(a[i] == i, "Bad pre-erased-range value");
342 
343   foreach(i; 25 .. 50)
344     assert(a[i] == (i+50), "Bad post-erased-range value. " ~
345                            "Expected " ~ to!string(i+25) ~ 
346                            ", got " ~ to!string(a[i]));
347 }
348 
349 unittest {
350   writeln("Array: erasing the entire array");
351   auto a = Array!int(iota(0, 100, 1));
352   assert(a.length == 100);
353   a.erase(0, 100);
354 
355   assert(a.length == 0, 
356     "Array should be empty after erasing it all.");
357 
358   assert(a.capacity == 0, 
359     "Internal capacity should be 0 after erasing all items.");
360 }