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 }