1 module magpie.index;
2 
3 import std.range: zip;
4 
5 /++
6 Structure for DataFrame Indexing
7 +/
8 struct Index
9 {
10 private:
11     struct Indexing
12     {
13         string[] titles;
14         string[][] index;
15         int[][] codes;
16     }
17 
18 public:
19     /// To know if data is multi-indexed
20     bool isMultiIndexed = false;
21 
22     /// Row and Column indexing
23     Indexing[2] indexing;
24 
25     /// Referring Index.indexing[0] as Index.row
26     ref row()
27     {
28         return indexing[0];
29     }
30 
31     /// Referring Index.indexing[1] as Index.column
32     ref column()
33     {
34         return indexing[1];
35     }
36 
37     /++
38     void optimize()
39     Description: Optimizes a DataFrame - If row.index can be expressed as integer, converts string row.index to int and stores in the codes
40     +/
41     void optimize()
42     {
43         foreach(k; 0 .. 2)
44         {
45             import std.range: lockstep;
46             foreach(ref a, ref b; lockstep(indexing[k].index, indexing[k].codes))
47             {
48                 import std.conv: to, ConvException;
49                 if(a.length > 0)
50                 {
51                     try
52                     {
53                         import std.array: appender;
54                         auto inx = to!(int[])(a);
55                         foreach(j; 0 .. b.length)
56                             b[j] = inx[b[j]];
57                         a = [];
58                     }
59                     catch(ConvException e)
60                     {
61                         import magpie.helper: sortIndex;
62                         sortIndex(a, b);
63                     }
64                 }
65             }
66         }
67     }
68 
69     /++
70     void generateCodes()
71     Description: Function to generate index codes for given index array
72     (Removes repetition)
73     +/
74     void generateCodes()
75     {
76         foreach(i; 0 .. 2)
77         {
78             if(indexing[i].index.length != indexing[i].codes.length)
79                 indexing[i].codes.length = indexing[i].index.length;
80 
81             import std.range: lockstep;
82             foreach(ref a, ref b; lockstep(indexing[i].index, indexing[i].codes))
83             {
84                 if(a.length > 0 && b.length == 0)
85                 {
86                     int[string] pos;
87                     string[] index;
88                     int[] codes;
89 
90                     int current = 0;
91                     foreach(k; 0 .. a.length)
92                     {
93                         codes ~= pos.require(a[k],
94                             { index ~= a[k]; return current++; }());
95                     }
96 
97                     a = index;
98                     b = codes;
99                 }
100             }
101         }
102 
103         optimize();
104     }
105 
106     /++
107     void setIndex(Args...)(Args args)
108     Description: Method for etting row.index
109     @params: rowindex - Can be a 1D or 2D array of int or string
110     @params: rowindexTitles - 1D array of string
111     @params?: columnindex - Can be a 1D or 2D array of int or string
112     @params?: columnIndexTitles - 1D array of string
113     +/
114     void setIndex(Args...)(Args args)
115         if(Args.length > 1 && Args.length < 5)
116     {
117         this = Index();
118 
119         static foreach(j; 0 .. 2)
120         {
121             static if(Args.length > 2 * j)
122             {
123                 static if(is(Args[j * 2] == string[]))
124                 {
125                     indexing[j].index = [args[j * 2]];
126                     indexing[j].codes = [[]];
127                     foreach(i; 0 .. args[j * 2].length)
128                         indexing[j].codes[0] ~= cast(int)i;
129                 }
130                 else static if(is(Args[j * 2] == int[]))
131                 {
132                     indexing[j].codes = [args[j * 2]];
133                     indexing[j].index = [[]];
134                 }
135                 else static if(is(Args[j* 2] == string[][]))
136                 {
137                     assert(args[j * 2].length > 0, "Can't construct index from empty array");
138                     assert(args[j * 2][0].length > 0, "Inner dimension cannot be 0");
139                     foreach(i; 0 .. args[j * 2].length)
140                         assert(args[j * 2][i].length == args[j * 2][0].length && args[j * 2][0].length > 0, "Inner dimension of indexes are unequal");
141 
142                     indexing[j].index = args[j * 2];
143                     foreach(i; 0 .. args[j * 2].length)
144                         indexing[j].codes ~= [[]];
145 
146                 }
147                 else static if(is(Args[j * 2] == int[][]))
148                 {
149                     assert(args[j * 2].length > 0, "Can't construct index from empty array");
150                     size_t len = args[j * 2][0].length;
151                     assert(len > 0, "Inner dimension cannot be 0");
152                     foreach(i; 0 .. args[j * 2].length)
153                         assert(args[j * 2][i].length == len, "Inner dimension of index not equal");
154                     indexing[j].codes = args[j * 2];
155                     foreach(i; 0 .. args[j * 2].length)
156                         indexing[j].index ~= [[]];
157                 }
158             }
159 
160             static if(Args.length > j * 2 + 1)
161                 indexing[j].titles = args[j*2 + 1];
162         }
163 
164         generateCodes();
165     }
166 
167     /++
168     void constructFromPairs(Args...)(Args args)
169     Description: Constructing index row wise
170     [["Hello", "Hi"], ["Hello", "Hi"], ["Hello", "Hi"]] will generate
171     Hello  Hi
172     Hello  Hi
173     Hello  Hi
174     @params: rowindex - Can be a 1D or 2D array of int or string
175     @params: rowindexTitles - 1D array of string
176     @params?: columnindex - Can be a 1D or 2D array of int or string
177     @params?: columnIndexTitles - 1D array of string
178     +/
179     void constructFromPairs(Args...)(Args args)
180         if(Args.length > 1 && Args.length < 5)
181     {
182         this = Index();
183 
184         static foreach(j; 0 .. 2)
185         {
186             static if(Args.length > 2 * j)
187             {
188                 static if(is(Args[j* 2] == string[][]))
189                 {
190                     assert(args[j * 2].length > 0, "Can't construct index from empty array");
191                     assert(args[j * 2][0].length > 0, "Inner dimension cannot be 0");
192                     foreach(i; 0 .. args[j * 2].length)
193                         assert(args[j * 2][i].length == args[j * 2][0].length, "Inner dimension of indexes are unequal");
194 
195                     // indexing[j].index = args[j * 2];
196                     foreach(i; 0 .. args[j * 2][0].length)
197                     {
198                         indexing[j].index ~= [[]];
199                         indexing[j].codes ~= [[]];
200                     }
201                     foreach(i; 0 .. args[j * 2].length)
202                         foreach(k; 0 .. args[j * 2][0].length)
203                             indexing[j].index[k] ~= args[j * 2][i][k];
204 
205                 }
206                 else static if(is(Args[j * 2] == int[][]))
207                 {
208                     assert(args[j * 2].length > 0, "Can't construct index from empty array");
209                     size_t len = args[j * 2][0].length;
210                     assert(len > 0, "Inner dimension cannot be 0");
211                     foreach(i; 0 .. args[j * 2].length)
212                         assert(args[j * 2][i].length == len, "Inner dimension of index not equal");
213 
214                     foreach(i; 0 .. args[j * 2][0].length)
215                     {
216                         indexing[j].index ~= [[]];
217                         indexing[j].codes ~= [[]];
218                     }
219                     foreach(i; 0 .. args[j * 2].length)
220                         foreach(k; 0 .. args[j * 2][0].length)
221                             indexing[j].codes[k] ~= args[j * 2][i][k];
222                 }
223             }
224 
225             static if(Args.length > j * 2 + 1)
226             {
227                 assert(args[j * 2 + 1].length == indexing[j].index.length);
228                 indexing[j].titles = args[j*2 + 1];
229             }
230         }
231 
232         generateCodes();
233     }
234 
235     /++
236     void constructFromZip(int axis, T...)(Zip!T index, string[] titles = [])
237     Description: Constructing Index from a Zip range
238     @params: axis - 0 for Row and 1 for Column
239     @params: index - Zip from which index will be constructed
240     @params?: titles - titles for index levels
241     +/
242     void constructFromZip(int axis, int levels, T)(T index, string[] titles = [])
243     {
244         assert(index.length > 0, "Cannot construct index out of empty zip");
245         if(titles.length > 0 || axis == 0)
246             assert(titles.length == index[0].length);
247 
248         indexing[axis] = Indexing();
249 
250         foreach(i; 0 .. levels)
251         {
252             indexing[axis].index ~= [[]];
253             indexing[axis].codes ~= [[]];
254         }
255 
256         foreach(i; 0 .. index.length)
257         {
258             import std.conv: to;
259             static foreach(j; 0 .. levels)
260                 indexing[axis].index[j] ~= to!string(index[i][j]);
261         }
262 
263         if(titles.length > 0)
264             indexing[axis].titles = titles;
265 
266         generateCodes();
267     }
268 
269     /++
270     void constructFromLevels(int axis)(string[][] index, string[] titles = [])
271     Description: Construct multi - index based on levels
272     [["Owl", "Kiwi"], ["Wild", "Domestic"]] will generate:
273     OWl   Wild
274     Owl   Domestic
275     Kiwi  Wild
276     Kiwi Domestic
277     @params: axis - 0 for rows, 1 for columns
278     @params: index - 2D array of string with each unique level index
279     @params?: titles - Titles for each index level
280     +/
281     void constructFromLevels(int axis)(string[][] index, string[] titles = [])
282     {
283         assert(index.length > 0, "Cannot construct indexes with empty levels");
284         import std.algorithm: map, reduce, min;
285         assert(index.map!(e => e.length).reduce!min > 0, "Index cannot have empty level");
286         assert(axis || titles.length == index.length, "Size of titles don't match level od indexing");
287 
288         indexing[axis] = Indexing();
289         indexing[axis].index = index;
290         foreach(i; 0 .. index.length)
291             indexing[axis].codes ~= [[]];
292 
293         // Generating codes and optimizing first because constructing levels is just repeating code [Index remains untouched]
294         generateCodes();
295 
296         foreach(i; 1 .. indexing[axis].index.length)
297         {
298             size_t r1 = indexing[axis].codes[i].length;
299             size_t r2 = indexing[axis].codes[i - 1].length;
300 
301             foreach(j; 0 .. i)
302             {
303                 int[] u = indexing[axis].codes[j];
304                 int[] newIndex = [];
305                 foreach(k; 0 .. indexing[axis].codes[j].length)
306                     foreach(l; 0 .. r1)
307                         newIndex ~= indexing[axis].codes[j][k];
308 
309                 indexing[axis].codes[j] = newIndex;
310             }
311 
312             int[] u = indexing[axis].codes[i];
313             foreach(j; 1 .. r2)
314                 indexing[axis].codes[i] ~= u;
315         }
316 
317         if(titles.length > 0)
318             indexing[axis].titles = titles;
319     }
320 
321     /++
322     void extend(int axis, T)(T next)
323     Description:Extends row.index
324     @params: axis - 0 for rows, 1 for column.index
325     @params: next - The element to extend element
326     +/
327     void extend(int axis, T)(T next, int insertPos = -1)
328     {
329         if(insertPos < 0)
330             insertPos = cast(int)indexing[axis].codes[0].length;
331 
332         static if(is(T == int[]))
333         {
334 
335             assert(next.length == indexing[axis].codes.length, "Index depth mismatch");
336             foreach(i, ref ele; indexing[axis].codes)
337             {
338                 if(indexing[axis].index[i].length == 0)
339                     ele = ele[0 .. insertPos] ~ next[i] ~ ele[insertPos .. $];
340                 else
341                 {
342                     import std.conv: to, ConvException;
343                     import std.algorithm: countUntil;
344                     string newele = to!string(next[i]);
345                     int pos = cast(int)countUntil(indexing[axis].index[i], newele);
346 
347                     if(pos > -1)
348                     {
349                         ele = ele[0 .. insertPos] ~ pos ~ ele[insertPos .. $];
350                     }
351                     else
352                     {
353                         ++indexing[axis].index[i].length;
354                         indexing[axis].index[i][$ - 1] = newele;
355                         ele = ele[0 .. insertPos] ~ (cast(int)indexing[axis].index[i].length - 1) ~ ele[insertPos .. $];
356                     }
357                 }
358             }
359         }
360         else static if(is(T == string[]))
361         {
362             assert(next.length == indexing[axis].codes.length, "Index depth mismatch");
363             foreach(i, ref ele; indexing[axis].codes)
364             {
365                 if(indexing[axis].index[i].length > 0)
366                 {
367                     import std.algorithm: countUntil;
368                     int pos = cast(int)countUntil(indexing[axis].index[i], next[i]);
369 
370                     if(pos > -1)
371                     {
372                         ele = ele[0 .. insertPos] ~ pos ~ ele[insertPos .. $];
373                     }
374                     else
375                     {
376                         ++indexing[axis].index[i].length;
377                         indexing[axis].index[i][$ -1] = next[i];
378                         ele = ele[0 .. insertPos] ~ (cast(int)indexing[axis].index[i].length - 1) ~ ele[insertPos .. $];
379                     }
380                 }
381                 else
382                 {
383                     import std.conv: to, ConvException;
384                     try
385                     {
386                         int newele = to!int(next[i]);
387                         ele = ele[0 .. insertPos] ~ newele ~ ele[insertPos .. $];
388                     }
389                     catch(ConvException e)
390                     {
391                         indexing[axis].index[i] = to!(string[])(ele);
392                         indexing[axis].index[i] = indexing[axis].index[i][0 .. insertPos] ~
393                             next[i] ~ indexing[axis].index[i][insertPos .. $];
394                         ele.length = indexing[axis].index[i].length;
395                         foreach(j; 0 .. indexing[axis].index[i].length)
396                             ele[j] = cast(int)j;
397                     }
398                 }
399             }
400 
401         }
402         else
403         {
404             if(insertPos == indexing[axis].codes[0].length)
405                 foreach(i; 0 .. next.length)
406                     extend!axis(next[i]);
407             else
408                 foreach_reverse(i; 0 .. next.length)
409                     extend!axis(next[i], insertPos);
410         }
411         optimize();
412     }
413 
414     /++
415     int getPosition(int axis)(string[] index)
416     Description: Get position of a particual index within the struct
417     @params: axis - 0 to search row Index, 1 to search column index
418     @params: index - index to search for
419     +/
420     ptrdiff_t getPosition(int axis)(string[] index)
421     {
422         import std.array: appender;
423         import std.algorithm: countUntil;
424         import std.conv: to;
425         auto codes = appender!(int[]);
426 
427         foreach(i; 0 .. indexing[axis].codes.length)
428         {
429             if(indexing[axis].index[i].length == 0)
430                 codes.put(to!int(index[i]));
431             else
432             {
433                 int indxpos = cast(int)countUntil(indexing[axis].index[i], index[i]);
434                 if(indxpos < 0)
435                     return -1;
436                 codes.put(indxpos);
437             }
438         }
439 
440         foreach(i; 0 .. indexing[axis].codes[0].length)
441         {
442             bool flag = true;
443             foreach(j; 0 .. indexing[axis].codes.length)
444             {
445                 if(indexing[axis].codes[j][i] != codes.data[j])
446                     flag = false;
447             }
448 
449             if(flag)
450                 return cast(int)i;
451         }
452 
453         return -1;
454     }
455 
456     void opIndexAssign(T)(T index, size_t axis)
457     {
458         indexing[axis] = Indexing();
459         static if(is(T == int[]))
460         {
461             indexing[axis].index.length = 1;
462             indexing[axis].codes = [index];
463         }
464         else static if(is(T == int[][]))
465         {
466             indexing[axis].index.length = index.length;
467             indexing[axis].codes = index;
468         }
469         else static if(is(T == string[]))
470         {
471             indexing[axis].index = [index];
472             generateCodes();
473         }
474         else static if(is(T == string[][]))
475         {
476             indexing[axis].index = index;
477             generateCodes();
478         }
479         else static assert(0, "Index assignment only takes string array or integer array as input");
480 
481         if(axis == 0)
482         {
483             indexing[axis].titles.length = indexing[axis].codes.length;
484         }
485     }
486 }
487 
488 // Optmization test
489 unittest
490 {
491     Index inx;
492     inx.row.index = [["B", "A"], ["1", "2"]];
493     inx.row.codes = [[0, 1], [0, 1]];
494     inx.column.index = [["B", "A"], ["1", "2"]];
495     inx.column.codes = [[0, 1], [0, 1]];
496 
497     inx.optimize();
498 
499     assert(inx.row.index == [["A", "B"], []]);
500     assert(inx.row.codes == [[1, 0], [1, 2]]);
501     assert(inx.column.index == [["A", "B"], []]);
502     assert(inx.column.codes == [[1, 0], [1, 2]]);
503 }
504 
505 // Setting integer row index
506 unittest
507 {
508     Index inx;
509     inx.setIndex([1,2,3,4,5,6], ["Index"]);
510     assert(inx.row.index == [[]]);
511     assert(inx.row.titles == ["Index"]);
512     assert(inx.row.codes == [[1,2,3,4,5,6]]);
513 }
514 
515 // Setting string row index
516 unittest
517 {
518     Index inx;
519     inx.setIndex(["Hello", "Hi"], ["Index"]);
520     assert(inx.row.index == [["Hello", "Hi"]]);
521     assert(inx.row.titles == ["Index"]);
522     assert(inx.row.codes == [[0, 1]]);
523 }
524 
525 // Setting 2D integer index for rows
526 unittest
527 {
528     Index inx;
529     inx.setIndex([[1,2], [3,4]], ["Index", "Index"]);
530     assert(inx.row.index == [[], []]);
531     assert(inx.row.titles == ["Index", "Index"]);
532     assert(inx.row.codes == [[1,2], [3,4]]);
533 }
534 
535 // Setting 2D string index for rows
536 unittest
537 {
538     Index inx;
539     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"]);
540     assert(inx.row.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
541     assert(inx.row.titles == ["Index", "Index"]);
542     assert(inx.row.codes == [[0,1], [1,0]]);
543 }
544 
545 // Setting integer column row.index
546 unittest
547 {
548     Index inx;
549     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"], [1,2,3,4,5]);
550     assert(inx.row.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
551     assert(inx.row.titles == ["Index", "Index"]);
552     assert(inx.row.codes == [[0,1], [1,0]]);
553     assert(inx.column.index == [[]]);
554     assert(inx.column.codes == [[1,2,3,4,5]]);
555     assert(inx.column.titles == []);
556 }
557 
558 // Setting string column index
559 unittest
560 {
561     Index inx;
562     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"], ["Hello", "Hi"]);
563     assert(inx.row.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
564     assert(inx.row.titles == ["Index", "Index"]);
565     assert(inx.row.codes == [[0,1], [1,0]]);
566     assert(inx.column.index == [["Hello", "Hi"]]);
567     assert(inx.column.codes == [[0, 1]]);
568     assert(inx.column.titles == []);
569 }
570 
571 // Setting 2D string index for column.index
572 unittest
573 {
574     Index inx;
575     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"], [["Hello", "Hi"], ["Hi", "Hello"]]);
576     assert(inx.row.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
577     assert(inx.row.titles == ["Index", "Index"]);
578     assert(inx.row.codes == [[0,1], [1,0]]);
579     assert(inx.column.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
580     assert(inx.column.codes == [[0,1], [1,0]]);
581     assert(inx.column.titles == []);
582 }
583 
584 // Setting 2D integer index for column.index
585 unittest
586 {
587     Index inx;
588     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"],[[1,2], [3,4]]);
589     assert(inx.row.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
590     assert(inx.row.titles == ["Index", "Index"]);
591     assert(inx.row.codes == [[0,1], [1,0]]);
592     assert(inx.column.index == [[], []]);
593     assert(inx.column.codes == [[1,2], [3,4]]);
594 }
595 
596 // Setting column titles
597 unittest
598 {
599     Index inx;
600     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"],
601         [["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"]);
602     assert(inx.row.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
603     assert(inx.row.titles == ["Index", "Index"]);
604     assert(inx.row.codes == [[0,1], [1,0]]);
605     assert(inx.column.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
606     assert(inx.column.codes == [[0,1], [1,0]]);
607     assert(inx.column.titles == ["Index", "Index"]);
608 }
609 
610 // Extending row.index
611 unittest
612 {
613     Index inx;
614     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"],
615         [["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"]);
616     inx.extend!0(["Hello", "Hi"]);
617     assert(inx.row.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
618     assert(inx.row.codes == [[0,1,0], [1,0,1]]);
619     inx.extend!1(["Hello", "Hi"]);
620     assert(inx.column.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
621     assert(inx.column.codes == [[0,1,0], [1,0,1]]);
622 }
623 
624 // Extending row.index that require int to be converted to string
625 unittest
626 {
627     Index inx;
628     inx.setIndex([1,2,3], ["Index"], [1,2,3]);
629     assert(inx.row.codes == [[1,2,3]]);
630     assert(inx.row.index == [[]]);
631     assert(inx.column.codes == [[1,2,3]]);
632     assert(inx.column.index == [[]]);
633 
634     // Appending string to integer row.index
635     inx.extend!0(["Hello"]);
636     assert(inx.row.index == [["1","2","3","Hello"]]);
637     assert(inx.row.codes == [[0,1,2,3]]);
638 
639     // Appending integer to integer row.index
640     inx.extend!1([4]);
641     assert(inx.column.index == [[]]);
642     assert(inx.column.codes == [[1,2,3,4]]);
643 
644     // Appending string to integer row.index
645     inx.extend!1(["Hello"]);
646     assert(inx.column.index == [["1","2","3","4","Hello"]]);
647     assert(inx.column.codes == [[0,1,2,3,4]]);
648 
649     // Checking if optimize() is working
650     inx.extend!1(["Arrow"]);
651     assert(inx.column.index == [["1","2","3","4","Arrow","Hello"]]);
652     assert(inx.column.codes == [[0,1,2,3,5,4]]);
653 }
654 
655 // Extending row.index with 2D array
656 unittest
657 {
658     Index inx;
659     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"],
660         [["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"]);
661     inx.extend!0([["Hello", "Hi"], ["Hello", "Hi"]]);
662     assert(inx.row.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
663     assert(inx.row.codes == [[0,1,0,0], [1,0,1,1]]);
664     inx.extend!1([["Hello", "Hi"], ["Hello", "Hi"]]);
665     assert(inx.column.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
666     assert(inx.column.codes == [[0,1,0,0], [1,0,1,1]]);
667 }
668 
669 // Extending index specifying position of insertion
670 unittest
671 {
672     Index inx;
673     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"],
674         [["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"]);
675     inx.extend!0(["Hello", "Hi"], 1);
676     assert(inx.row.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
677     assert(inx.row.codes == [[0, 0, 1], [1, 1, 0]]);
678     inx.extend!1(["Hello", "Hi"], 1);
679     assert(inx.column.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
680     assert(inx.column.codes == [[0, 0, 1], [1, 1, 0]]);
681 }
682 
683 // Extending specifying position - requires conversion of codes to index
684 unittest
685 {
686     Index inx;
687     inx.setIndex([1,2,3], ["Index"], [1,2,3]);
688     assert(inx.row.codes == [[1,2,3]]);
689     assert(inx.row.index == [[]]);
690     assert(inx.column.codes == [[1,2,3]]);
691     assert(inx.column.index == [[]]);
692 
693     // Appending string to integer row.index
694     inx.extend!0(["Hello"], 2);
695     assert(inx.row.index == [["1","2","3","Hello"]]);
696     assert(inx.row.codes == [[0,1,3,2]]);
697 
698     // Appending integer to integer row.index
699     inx.extend!1([4], 1);
700     assert(inx.column.index == [[]]);
701     assert(inx.column.codes == [[1,4,2,3]]);
702 
703     // Appending string to integer row.index
704     inx.extend!1(["Hello"], 3);
705     assert(inx.column.index == [["1","2","3","4","Hello"]]);
706     assert(inx.column.codes == [[0,3,1,4,2]]);
707 
708     // Checking if optimize() is working
709     inx.extend!1(["Arrow"], 0);
710     assert(inx.column.index == [["1","2","3","4","Arrow","Hello"]]);
711     assert(inx.column.codes == [[4,0,3,1,5,2]]);
712 }
713 
714 // Extending row.index with 2D array with position of insertion
715 unittest
716 {
717     Index inx;
718     inx.setIndex([["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"],
719         [["Hello", "Hi"], ["Hi", "Hello"]], ["Index", "Index"]);
720     inx.extend!0([["Hello", "Hi"], ["Hello", "Hi"]], 1);
721     assert(inx.row.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
722     assert(inx.row.codes == [[0,0,0,1], [1,1,1,0]]);
723     inx.extend!1([["Hello", "Hi"], ["Hello", "Hi"]], 1);
724     assert(inx.column.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
725     assert(inx.column.codes == [[0,0,0,1], [1,1,1,0]]);
726 }
727 
728 // Test for code generation
729 unittest
730 {
731     Index inx;
732     inx.indexing[0].index = [[]];
733     inx.indexing[0].codes = [[]];
734     foreach(i; 0 .. 100)
735         inx.indexing[0].index[0] ~= "Hello";
736 
737     inx.generateCodes();
738     assert(inx.indexing[0].index[0].length == 1);
739     assert(inx.indexing[0].codes[0].length == 100);
740     foreach(i; 0 .. 100)
741         assert(inx.indexing[0].codes[0][i] == 0);
742 }
743 
744 // Test for code generation
745 unittest
746 {
747     Index inx;
748     inx.indexing[0].index = [[]];
749     inx.indexing[0].codes = [[]];
750     foreach(i; 0 .. 100)
751         inx.indexing[0].index[0] ~= "Hello";
752     foreach(i; 0 .. 100)
753         inx.indexing[0].index[0] ~= "Allo";
754 
755     inx.generateCodes();
756     assert(inx.indexing[0].index[0].length == 2);
757     assert(inx.indexing[0].codes[0].length == 200);
758     foreach(i; 0 .. 100)
759         assert(inx.indexing[0].codes[0][i] == 1);
760     foreach(i; 100 .. 200)
761         assert(inx.indexing[0].codes[0][i] == 0);
762 }
763 
764 // Constructing from pairs
765 unittest
766 {
767     Index inx;
768     inx.constructFromPairs([["Hi", "Hello"], ["Yo", "Ahoy"], ["Yx", "Azoy"]], ["Index1", "Index2"]);
769     assert(inx.row.index == [["Hi", "Yo", "Yx"], ["Ahoy", "Azoy", "Hello"]]);
770     assert(inx.row.codes == [[0, 1, 2], [2, 0, 1]]);
771     assert(inx.row.titles == ["Index1", "Index2"]);
772 }
773 
774 // Constructing from pairs
775 unittest
776 {
777     Index inx;
778     inx.constructFromPairs([["Hi", "Hello"], ["Yo", "Ahoy"], ["Yx", "Azoy"]], ["Index1", "Index2"],
779         [["Hi", "Hello"], ["Yo", "Ahoy"], ["Yx", "Azoy"]]);
780     assert(inx.row.index == [["Hi", "Yo", "Yx"], ["Ahoy", "Azoy", "Hello"]]);
781     assert(inx.row.codes == [[0, 1, 2], [2, 0, 1]]);
782     assert(inx.row.titles == ["Index1", "Index2"]);
783 
784     assert(inx.column.index == [["Hi", "Yo", "Yx"], ["Ahoy", "Azoy", "Hello"]]);
785     assert(inx.column.codes == [[0, 1, 2], [2, 0, 1]]);
786 
787     inx.constructFromPairs([["Hi", "Hello"], ["Yo", "Ahoy"], ["Yx", "Azoy"]], ["Index1", "Index2"],
788         [["Hi", "Hello"], ["Yo", "Ahoy"], ["Yx", "Azoy"]],["Index1", "Index2"]);
789     assert(inx.column.index == [["Hi", "Yo", "Yx"], ["Ahoy", "Azoy", "Hello"]]);
790     assert(inx.column.codes == [[0, 1, 2], [2, 0, 1]]);
791     assert(inx.column.titles == ["Index1", "Index2"]);
792 }
793 
794 // Constructing from pairs
795 unittest
796 {
797     Index inx;
798     inx.constructFromPairs([[1, 2], [2, 3], [3, 4]], ["Index1", "Index2"],
799         [["Hi", "Hello"], ["Yo", "Ahoy"], ["Yx", "Azoy"]]);
800     assert(inx.row.index == [[], []]);
801     assert(inx.row.codes == [[1, 2, 3], [2, 3, 4]]);
802     assert(inx.row.titles == ["Index1", "Index2"]);
803 
804     assert(inx.column.index == [["Hi", "Yo", "Yx"], ["Ahoy", "Azoy", "Hello"]]);
805     assert(inx.column.codes == [[0, 1, 2], [2, 0, 1]]);
806 
807     inx.constructFromPairs([["Hi", "Hello"], ["Yo", "Ahoy"], ["Yx", "Azoy"]], ["Index1", "Index2"],
808         [[1, 2], [2, 3], [3, 4]], ["Index1", "Index2"]);
809     assert(inx.row.index == [["Hi", "Yo", "Yx"], ["Ahoy", "Azoy", "Hello"]]);
810     assert(inx.row.codes == [[0, 1, 2], [2, 0, 1]]);
811     assert(inx.row.titles == ["Index1", "Index2"]);
812 
813     assert(inx.column.index == [[], []]);
814     assert(inx.column.codes == [[1, 2, 3], [2, 3, 4]]);
815     assert(inx.column.titles == ["Index1", "Index2"]);
816 
817     // Checking if extend still works
818     inx.extend!0(["Zing", "Zang"]);
819     assert(inx.row.index == [["Hi", "Yo", "Yx", "Zing"], ["Ahoy", "Azoy", "Hello", "Zang"]]);
820     assert(inx.row.codes == [[0, 1, 2, 3], [2, 0, 1, 3]]);
821     assert(inx.row.titles == ["Index1", "Index2"]);
822 
823     inx.extend!1([4, 5]);
824     assert(inx.column.index == [[], []]);
825     assert(inx.column.codes == [[1, 2, 3, 4], [2, 3, 4, 5]]);
826     assert(inx.column.titles == ["Index1", "Index2"]);
827 
828     // Checking if extend still converts
829     inx.extend!1(["Zing", "Zang"]);
830     assert(inx.column.index == [["1", "2", "3", "4", "Zing"], ["2", "3", "4", "5", "Zang"]]);
831     assert(inx.column.codes == [[0, 1, 2, 3, 4], [0, 1, 2, 3, 4]]);
832     assert(inx.column.titles == ["Index1", "Index2"]);
833 }
834 
835 // Generate index from Zip
836 unittest
837 {
838     Index inx;
839     auto z = zip([1, 2, 3, 4], ["Hello", "Hi", "Hello", "Hi"]);
840 
841     inx.constructFromZip!(0, 2)(z, ["Index1", "Index2"]);
842     assert(inx.row.index == [[], ["Hello", "Hi"]]);
843     assert(inx.row.codes == [[1, 2, 3, 4], [0, 1, 0 ,1]]);
844     assert(inx.row.titles == ["Index1", "Index2"]);
845 
846     // Column without titles
847     auto zc = zip([1, 2, 3, 4], ["Hello", "Ho", "Hello", "Ho"]);
848     inx.constructFromZip!(1, 2)(zc);
849     assert(inx.column.index == [[], ["Hello", "Ho"]]);
850     assert(inx.column.codes == [[1, 2, 3, 4], [0, 1, 0 ,1]]);
851     assert(inx.column.titles == []);
852 
853     // Columns with titles
854     inx.constructFromZip!(1, 2)(zc, ["Index1", "Index2"]);
855     assert(inx.column.index == [[], ["Hello", "Ho"]]);
856     assert(inx.column.codes == [[1, 2, 3, 4], [0, 1, 0 ,1]]);
857     assert(inx.column.titles == ["Index1", "Index2"]);
858 
859     // Checking if row indexing remains un touched
860     assert(inx.row.index == [[], ["Hello", "Hi"]]);
861     assert(inx.row.codes == [[1, 2, 3, 4], [0, 1, 0 ,1]]);
862     assert(inx.row.titles == ["Index1", "Index2"]);
863 
864     // Checking if extend works
865     inx.extend!0(["Zing", "Zang"]);
866     assert(inx.row.index == [["1", "2", "3", "4", "Zing"], ["Hello", "Hi", "Zang"]]);
867     assert(inx.row.codes == [[0, 1, 2, 3, 4], [0, 1, 0, 1, 2]]);
868     assert(inx.row.titles == ["Index1", "Index2"]);
869 
870     // Checking if extend works
871     inx.extend!1(["Zing", "Zang"]);
872     assert(inx.column.index == [["1", "2", "3", "4", "Zing"], ["Hello", "Ho", "Zang"]]);
873     assert(inx.column.codes == [[0, 1, 2, 3, 4], [0, 1, 0, 1, 2]]);
874     assert(inx.column.titles == ["Index1", "Index2"]);
875 }
876 
877 // Generate index from levels
878 unittest
879 {
880     Index inx;
881     inx.constructFromLevels!0([["Air", "Water"], ["Transportation"], ["Net Income", "Gross Income"]], ["Index1", "Index2", "Index3"]);
882     assert(inx.row.index == [["Air", "Water"], ["Transportation"], ["Gross Income", "Net Income"]]);
883     assert(inx.row.codes == [[0, 0, 1, 1], [0, 0, 0, 0], [1, 0, 1, 0]]);
884     assert(inx.row.titles == ["Index1", "Index2", "Index3"]);
885 
886     inx.constructFromLevels!0([["Air", "Water"], ["Transportation", "Something"], ["Net Income", "Gross Income"]], ["Index1", "Index2", "Index3"]);
887     assert(inx.row.index == [["Air", "Water"], ["Something", "Transportation"], ["Gross Income", "Net Income"]]);
888     assert(inx.row.codes == [[0, 0, 0, 0, 1, 1, 1, 1], [1, 1, 0, 0, 1, 1, 0, 0], [1, 0, 1, 0, 1, 0, 1, 0]]);
889     assert(inx.row.titles == ["Index1", "Index2", "Index3"]);
890 
891     inx.constructFromLevels!1([["Air", "Water"], ["Transportation", "Something"], ["Net Income", "Gross Income"]]);
892     assert(inx.column.index == [["Air", "Water"], ["Something", "Transportation"], ["Gross Income", "Net Income"]]);
893     assert(inx.column.codes == [[0, 0, 0, 0, 1, 1, 1, 1], [1, 1, 0, 0, 1, 1, 0, 0], [1, 0, 1, 0, 1, 0, 1, 0]]);
894 
895     assert(inx.row.index == [["Air", "Water"], ["Something", "Transportation"], ["Gross Income", "Net Income"]]);
896     assert(inx.row.codes == [[0, 0, 0, 0, 1, 1, 1, 1], [1, 1, 0, 0, 1, 1, 0, 0], [1, 0, 1, 0, 1, 0, 1, 0]]);
897     assert(inx.row.titles == ["Index1", "Index2", "Index3"]);
898 
899     inx.constructFromLevels!1([["Air", "Water"], ["Transportation", "Something"], ["Net Income", "Gross Income"]], ["Index1", "Index2", "Index3"]);
900     assert(inx.column.index == [["Air", "Water"], ["Something", "Transportation"], ["Gross Income", "Net Income"]]);
901     assert(inx.column.codes == [[0, 0, 0, 0, 1, 1, 1, 1], [1, 1, 0, 0, 1, 1, 0, 0], [1, 0, 1, 0, 1, 0, 1, 0]]);
902     assert(inx.column.titles == ["Index1", "Index2", "Index3"]);
903 
904     inx.extend!0(["Zing", "Zang", "Zong"]);
905     assert(inx.row.index == [["Air", "Water", "Zing"], ["Something", "Transportation", "Zang"], ["Gross Income", "Net Income", "Zong"]]);
906     assert(inx.row.codes == [[0, 0, 0, 0, 1, 1, 1, 1, 2], [1, 1, 0, 0, 1, 1, 0, 0, 2], [1, 0, 1, 0, 1, 0, 1, 0, 2]]);
907     assert(inx.row.titles == ["Index1", "Index2", "Index3"]);
908 
909     inx.extend!1(["Zing", "Zang", "Zong"]);
910     assert(inx.column.index == [["Air", "Water", "Zing"], ["Something", "Transportation", "Zang"], ["Gross Income", "Net Income", "Zong"]]);
911     assert(inx.column.codes == [[0, 0, 0, 0, 1, 1, 1, 1, 2], [1, 1, 0, 0, 1, 1, 0, 0, 2], [1, 0, 1, 0, 1, 0, 1, 0, 2]]);
912     assert(inx.column.titles == ["Index1", "Index2", "Index3"]);
913 }
914 
915 // README examples
916 unittest
917 {
918     Index inx;
919     inx.constructFromPairs([["Hello", "Hi"], ["Hi", "Hello"], ["Hey", "Hey"]],
920                             ["RL1", "RL2"],
921                             [["Hello", "Hi"], ["Hi", "Hello"], ["Hey", "Hey"]],
922                             ["CL1", "CL2"]);
923 
924     assert(inx.row.index == [["Hello", "Hey", "Hi"], ["Hello", "Hey", "Hi"]]);
925     assert(inx.column.index == [["Hello", "Hey", "Hi"], ["Hello", "Hey", "Hi"]]);
926     assert(inx.row.codes == [[0, 2, 1], [2, 0, 1]]);
927     assert(inx.column.codes == [[0, 2, 1], [2, 0, 1]]);
928     assert(inx.row.titles == ["RL1", "RL2"]);
929     assert(inx.column.titles == ["CL1", "CL2"]);
930 }
931 
932 // OpIndexAssign to set indexes - 1D integer array
933 unittest
934 {
935     Index inx;
936     inx[0] = [1, 2, 3, 4];
937     assert(inx.row.index == [[]]);
938     assert(inx.row.codes == [[1, 2, 3, 4]]);
939 
940     inx[1] = [1, 2, 3, 4];
941     assert(inx.column.index == [[]]);
942     assert(inx.column.codes == [[1, 2, 3, 4]]);
943 
944     // Checking previously set values remains unchanged
945     assert(inx.row.index == [[]]);
946     assert(inx.row.codes == [[1, 2, 3, 4]]);    
947 }
948 
949 // OpIndexAssign to set indexes - 1D string array
950 unittest
951 {
952     Index inx;
953     inx[0] = ["Hello", "Hi"];
954     assert(inx.row.index == [["Hello", "Hi"]]);
955     assert(inx.row.codes == [[0, 1]]);
956 
957     inx[1] = ["Hello", "Hi"];
958     assert(inx.column.index == [["Hello", "Hi"]]);
959     assert(inx.column.codes == [[0, 1]]);
960     // Checking previous values remain unchanged
961     assert(inx.row.index == [["Hello", "Hi"]]);
962     assert(inx.row.codes == [[0, 1]]);
963 }
964 
965 // OpIndexAssign to set indexes - 1D integer array
966 unittest
967 {
968     Index inx;
969     inx[0] = [[1, 2, 3, 4], [1, 2, 3, 4]];
970     assert(inx.row.index == [[], []]);
971     assert(inx.row.codes == [[1, 2, 3, 4], [1, 2, 3, 4]]);
972 
973     inx[1] = [[1, 2, 3, 4], [1, 2, 3, 4]];
974     assert(inx.column.index == [[], []]);
975     assert(inx.column.codes == [[1, 2, 3, 4], [1, 2, 3, 4]]);
976 
977     // Checking previously set values remains unchanged
978     assert(inx.row.index == [[], []]);
979     assert(inx.row.codes == [[1, 2, 3, 4], [1, 2, 3, 4]]);    
980 }
981 
982 // OpIndexAssign to set indexes - 1D string array
983 unittest
984 {
985     Index inx;
986     inx[0] = [["Hello", "Hi"], ["Hello", "Hi"]];
987     assert(inx.row.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
988     assert(inx.row.codes == [[0, 1], [0, 1]]);
989 
990     inx[1] = [["Hello", "Hi"], ["Hello", "Hi"]];
991     assert(inx.column.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
992     assert(inx.column.codes == [[0, 1], [0, 1]]);
993     // Checking previous values remain unchanged
994     assert(inx.row.index == [["Hello", "Hi"], ["Hello", "Hi"]]);
995     assert(inx.row.codes == [[0, 1], [0, 1]]);
996 }
997 
998 // Checking optimization because of trust issues
999 unittest
1000 {
1001     Index inx;
1002     inx[0] = [["Hello", "Hi"], ["0", "1"]];
1003     assert(inx.row.index == [["Hello", "Hi"], []]);
1004     assert(inx.row.codes == [[0, 1], [0, 1]]);
1005 }