1 /** 2 Functions and types that manipulate multidimensional rectangular arrays. 3 4 Copyright: Denis Shelomovskij 2011-2014 5 6 License: $(HTTP boost.org/LICENSE_1_0.txt, Boost License 1.0). 7 8 Authors: Denis Shelomovskij 9 10 Macros: 11 TR = <tr>$0</tr> 12 TH = <th>$0</th> 13 TD = <td>$0</td> 14 TABLE = <table border=1 cellpadding=4 cellspacing=0>$0</table> 15 */ 16 module unstd.multidimarray; 17 18 import core.exception; 19 import std.exception; 20 import std.array; 21 import unstd.traits; 22 import unstd.generictuple; 23 import std.typecons; 24 import std.conv; 25 import std..string; 26 import std.range; 27 import std.algorithm; 28 29 import unstd.lifetime; 30 31 32 private struct R { size_t from, to; } 33 34 private template RCount(T...) 35 { 36 static if(T.length) 37 enum RCount = is(T[0] == R) + RCount!(T[1 .. $]); 38 else 39 enum RCount = 0u; 40 } 41 42 private enum isROrSize(T) = RCount!T || isImplicitlyConvertible!(T, const(size_t)); 43 44 // Note: unittest can't be used as an example for `MultidimArray` member functions 45 // as it will lead to "forward reference to inferred return type of function call multidimArray". 46 47 /** 48 Implements multidimensional rectangular arrays. 49 50 Something like FORTRAN's one. 51 */ 52 struct MultidimArray(T, size_t n) if(n >= 1) 53 { 54 /// Dimensions of this array. 55 alias dimensions = n; 56 57 private 58 { 59 size_t[n] _lengths = void; 60 size_t[n] _strides = void; 61 T[] _data; // _strides[0] * _lengths[0] <= _data.length 62 } 63 64 private this(in size_t[n] lengths, in size_t[n] strides, inout T[] data) inout @safe pure nothrow @nogc 65 { 66 _lengths = lengths; 67 _strides = strides; 68 _data = data; 69 } 70 71 private this(in size_t[n] lengths, inout T[] data) inout @safe pure nothrow 72 in 73 { 74 if(data) 75 { 76 const count = lengths[].reduce!`a * b`(); // Ignore integral overflow here. 77 assert(count == data.length, 78 format("Data length for lengths %s must be %s, not %s.", lengths, count, data.length)); 79 } 80 } 81 body 82 { 83 if(n > 1 && lengths[].all!`a`()) // At least two dimensions and any elements. 84 { 85 size_t count = lengths[0]; 86 foreach(i; iotaTuple!(1, n)) count *= lengths[i]; 87 foreach(i; iotaTuple!(1, n)) count /= lengths[i]; 88 if(count != lengths[0]) 89 onOutOfMemoryError(); // Not enough address space to index all elements. 90 } 91 92 size_t[n] strides = void; 93 strides[$ - 1] = 1; 94 static if(n > 1) 95 { 96 strides[$ - 2] = lengths[$ - 1]; 97 foreach_reverse(i; iotaTuple!(n - 2)) 98 strides[i] = lengths[i + 1] * strides[i + 1]; 99 } 100 this(lengths, strides, data ? data : new inout T[strides[0] * lengths[0]]); 101 } 102 103 this(in size_t[n] lengths...) @safe pure nothrow 104 { 105 this(lengths, null); 106 } 107 108 this(inout T[] data, in size_t[n] lengths...) inout @safe pure nothrow 109 in { assert(data, "data must be non-null."); } 110 body 111 { 112 this(lengths, data); 113 } 114 115 /// Returns the read only view at its lengths array. 116 @property ref const(size_t)[n] lengths() const @safe pure nothrow @nogc 117 { 118 return _lengths; 119 } 120 121 /// Returns the elements count of the array. 122 @property size_t elements() const @safe pure nothrow @nogc 123 { 124 size_t res = _lengths[0]; 125 foreach(i; iotaTuple!(1, n)) 126 res *= _lengths[i]; 127 return res; 128 } 129 130 /** 131 Returns the maximum number of tail dimensions without pading. Note, that there can be no 132 such dimensions. 133 */ 134 @property size_t packedDimensions() const @safe pure nothrow @nogc 135 { 136 static if(n > 1) 137 { 138 size_t packedStride = 1; 139 foreach_reverse(i; iotaTuple!n) 140 { 141 static if(i < n - 1) 142 packedStride *= _lengths[i + 1]; 143 assert(_strides[i] >= packedStride); 144 if(_strides[i] > packedStride) 145 return n - i - 1; 146 } 147 } 148 return n; 149 } 150 151 /// Returns a forward range which has mutable elements and a length for iteration by an element. 152 @property byElementForward() @safe pure nothrow @nogc 153 { 154 static struct Result 155 { 156 @safe pure nothrow @nogc: 157 158 private 159 { 160 MultidimArray e; // entity 161 size_t rest, shift; 162 size_t[n] indices; 163 } 164 165 @property 166 { 167 auto save() inout 168 { return this; } 169 170 size_t length() const 171 { return rest; } 172 173 bool empty() const 174 { return !rest; } 175 176 ref front() inout 177 in { assert(!empty, "Trying to call front() on an empty MultidimArray.byElementForward"); } 178 body 179 { 180 return e._data[shift]; 181 } 182 } 183 184 void popFront() 185 in { assert(!empty, "Trying to call popFront() on an empty MultidimArray.byElementForward"); } 186 body 187 { 188 --rest; 189 foreach_reverse(i; iotaTuple!n) 190 { 191 shift += e._strides[i]; 192 if(++indices[i] < e._lengths[i]) 193 { 194 break; 195 } 196 else 197 { 198 assert(i || !rest); 199 shift -= indices[i] * e._strides[i]; 200 indices[i] = 0; 201 } 202 } 203 } 204 } 205 206 return Result(this, elements, 0); 207 } 208 209 /// Returns a finite random-access range which has mutable elements and a length for iteration by an element. 210 @property byElementRandomAccess() @safe pure nothrow @nogc 211 { 212 static struct Result 213 { 214 @safe pure nothrow /* dmd Issue 13118 @nogc*/: 215 216 private 217 { 218 MultidimArray e; // entity 219 size_t frontIndex, rest, frontShift, backShift; 220 size_t[n] frontIndices, afterBackIndices; 221 } 222 223 @property 224 { 225 auto save() inout 226 { return this; } 227 228 size_t length() const 229 { return rest; } 230 231 bool empty() const 232 { return !rest; } 233 234 ref front() inout 235 in { assert(!empty, "Trying to call front() on an empty MultidimArray.byElementRandomAccess"); } 236 body 237 { 238 return e._data[frontShift]; 239 } 240 241 ref back() inout 242 in { assert(!empty, "Trying to call back() on an empty MultidimArray.byElementRandomAccess"); } 243 body 244 { 245 return e._data[backShift]; 246 } 247 } 248 249 ref opIndex(in size_t i) inout 250 in 251 { 252 assert(!empty, format("Trying to call opIndex(%s) on an empty MultidimArray.byElementRandomAccess", i)); 253 assert(i < length, format("Index is out of bounds: trying to call opIndex(%s) on an MultidimArray.byElementRandomAccess with a length = %s", i, length)); 254 } 255 body 256 { 257 size_t k = i + frontIndex; 258 size_t shift = 0; 259 foreach_reverse(j; iotaTuple!n) 260 { 261 // TODO: do % and / in one operation 262 const currIndex = k % e._lengths[j]; 263 k /= e._lengths[j]; 264 shift += currIndex * e._strides[j]; 265 } 266 267 return e._data[shift]; 268 } 269 270 void popFront() 271 in { assert(!empty, "Trying to call popFront() on an empty MultidimArray.byElementRandomAccess"); } 272 body 273 { 274 275 ++frontIndex; 276 --rest; 277 foreach_reverse(i; iotaTuple!n) 278 { 279 frontShift += e._strides[i]; 280 if(++frontIndices[i] < e._lengths[i]) 281 break; 282 assert(i || !rest); 283 frontShift -= frontIndices[i] * e._strides[i]; 284 frontIndices[i] = 0; 285 } 286 } 287 288 void popBack() 289 in { assert(!empty, "Trying to call popBack() on an empty MultidimArray.byElementRandomAccess"); } 290 body 291 { 292 --rest; 293 foreach_reverse(i; iotaTuple!n) 294 { 295 backShift -= e._strides[i]; 296 if(--afterBackIndices[i] > 0) 297 break; 298 assert(i || !rest); 299 backShift += e._lengths[i] * e._strides[i]; 300 afterBackIndices[i] = e._lengths[i]; 301 } 302 } 303 } 304 305 const els = elements; 306 return Result(this, 0, els, 0, els - 1, 0, _lengths); 307 } 308 309 /// Returns a finite random-access range for iteration over the top dimension. 310 /// It has mutable elements iff $(D dimensions) is 1. 311 @property byTopDimension() @safe pure nothrow @nogc 312 { 313 static struct Result 314 { 315 @safe pure nothrow /* dmd Issue 13118 @nogc*/: 316 317 private MultidimArray e; // entity 318 319 @property 320 { 321 auto save() inout 322 { return this; } 323 324 size_t length() const 325 { return e._lengths[0]; } 326 327 bool empty() const 328 { return !e._lengths[0]; } 329 330 auto ref front() inout 331 in { assert(!empty, "Trying to call front() on an empty MultidimArray.byTopDimension"); } 332 body 333 { 334 return opIndex(0); 335 } 336 337 auto ref back() inout 338 in { assert(!empty, "Trying to call back() on an empty MultidimArray.byTopDimension"); } 339 body 340 { 341 return opIndex(e._lengths[0] - 1); 342 } 343 } 344 345 static if(n == 1) 346 { 347 ref opIndex(in size_t i) inout 348 in 349 { 350 assert(!empty, format("Trying to call opIndex(%s) on an empty MultidimArray.byTopDimension", i)); 351 assert(i < length, format("Index is out of bounds: trying to call opIndex(%s) on an MultidimArray.byTopDimension with a length = %s", i, length)); 352 } 353 body 354 { 355 return e._data[i]; 356 } 357 } 358 else 359 { 360 auto opIndex(in size_t i) inout @trusted 361 in 362 { 363 assert(!empty, format("Trying to call opIndex(%s) on an empty MultidimArray.byTopDimension", i)); 364 assert(i < length, format("Index is out of bounds: trying to call opIndex(%s) on an MultidimArray.byTopDimension with a length = %s", i, length)); 365 } 366 body 367 { 368 const shift = i * e._strides[0]; 369 return inout MultidimArray!(T, n - 1)( 370 e._lengths[1 .. $], 371 e._strides[1 .. $], 372 e._data[shift .. shift + e._strides[0]] 373 ); 374 } 375 } 376 377 void popFront() 378 in { assert(!empty, "Trying to call popFront() on an empty MultidimArray.byTopDimension"); } 379 body 380 { 381 e._data = e._data[e._strides[0] .. $]; 382 --e._lengths[0]; 383 } 384 385 void popBack() 386 in { assert(!empty, "Trying to call popBack() on an empty MultidimArray.byTopDimension"); } 387 body 388 { 389 e._data = e._data[0 .. $ - e._strides[0]]; 390 --e._lengths[0]; 391 } 392 } 393 394 return Result(this); 395 } 396 397 /** 398 Returns a forward range which has mutable elements for iteration 399 using indices defined by $(D pred) starting from $(D a = 0) and 400 incrementing it while indices are in valid range. 401 402 Example: 403 ---- 404 auto matrix = multidimArray!char(30, 20); 405 matrix[] = ' '; 406 407 foreach(ref el; matrix.byFunction!`a, a`) // fills a diagonal 408 el = 'X'; 409 410 foreach(ref el; matrix.byFunction!`a^^2 / 5, a`()) // fills a parabola points 411 el = 'Y'; 412 413 import std.stdio; 414 writeln(matrix); 415 --- 416 */ 417 @property auto byFunction(string pred)() 418 { 419 static struct Result 420 { 421 private 422 { 423 MultidimArray marr; 424 size_t a; 425 size_t[n] indices; 426 } 427 428 @property @safe pure 429 { 430 auto save() inout nothrow 431 { return this; } 432 433 bool empty() const nothrow 434 { return !marr.elementExists(indices); } 435 436 ref front() // FIXME `opIndex` isn't `inout nothrow` 437 { return marr[indices]; } 438 } 439 440 void popFront() 441 { 442 ++a; 443 indices = mixin('[' ~ pred ~ ']'); 444 } 445 } 446 447 auto res = Result(this, -1); 448 res.popFront(); 449 return res; 450 } 451 452 /** 453 Implements by-element iteration with inidces starting from the top dimension. 454 455 Example: 456 ---- 457 auto matrix = multidimArray!int(2, 3, 4); 458 foreach(z, y, x, ref el; matrices) 459 el = z * 100 + y * 10 + x; 460 ---- 461 */ 462 int opApply(int delegate(RepeatTuple!(n, size_t), ref T) dg) 463 { 464 if(!elements) 465 return 0; 466 467 RepeatTuple!(n, size_t) indices = 0; 468 indices[$ - 1] = -1; 469 470 for(;;) 471 { 472 foreach_reverse(const plane, ref index; indices) 473 { 474 if(++index < _lengths[plane]) 475 break; 476 else if(plane) 477 index = 0; 478 else 479 return 0; 480 } 481 482 if(const res = dg(indices, _data[getOffset(indices)])) 483 return res; 484 } 485 } 486 487 static if(isAssignable!T) 488 { 489 /** 490 Implements elements initialisation with a $(D value), where $(D value) can be 491 of type $(D T) or an input range which $(D front) can be assigned to an element. 492 The range should contain exectly $(D elements) elements, otherwise an $(D Exception) 493 will be thrown. 494 495 Returns: 496 If $(D value) is of type $(D T) or a forward range, returns $(D value). 497 Otherwise ($(D value) is an input range but not a forward range) returns $(D void). 498 499 Example: 500 ---- 501 auto a23 = multidimArray!int(2, 3); 502 auto a46 = multidimArray!int(4, 6); 503 auto a234 = multidimArray!int(2, 3, 4); 504 505 a23[] = a234[] = 7; 506 a23[] = take(a46[] = a234[] = iota(24), 6); 507 ---- 508 */ 509 MultidimArray opSliceAssign(T value) 510 { 511 fill(byElementForward, value); 512 return this; 513 } 514 515 /// ditto 516 MultidimArray opSliceAssign(Range)(Range value) 517 if(isInputRange!Range && isAssignable!(T, ElementType!Range)) 518 in 519 { 520 static if(hasLength!Range) 521 assert(value.length == elements, format( 522 "MultidimArray.opSliceAssign(Range): value length (%s) doesn't match array elements count (%s)", 523 value.length, elements)); 524 } 525 body 526 { 527 foreach(ref el; byElementForward) 528 { 529 assert(!value.empty, format( 530 "MultidimArray.opSliceAssign(Range): value doesn't contain enough elements (< %s)", 531 elements).assumeWontThrow()); 532 el = value.front; 533 value.popFront(); 534 } 535 536 assert(value.empty, format( 537 "MultidimArray.opSliceAssign(Range): value contains too many elements (> %s)", 538 elements).assumeWontThrow()); 539 540 return this; 541 } 542 543 /// ditto 544 MultidimArray opSliceAssign(U)(MultidimArray!(U, n) value) 545 if(isAssignable!(T, U)) 546 in 547 { 548 assert(value._lengths == _lengths, format( 549 "MultidimArray.opSliceAssign(MultidimArray): value lengths %s aren't equal to this lengths %s", 550 value._lengths, _lengths)); 551 } 552 body 553 { 554 return opSliceAssign(value.byElementForward); 555 } 556 } 557 558 private auto makeCopy(U)() 559 { 560 auto res = MultidimArray!(U, n)(lengths); 561 auto r = byElementForward; 562 foreach(ref el; res._data) 563 { 564 _assumeSafeCopyConstructFrom(el, r.front); 565 r.popFront(); 566 } 567 assert(r.empty); 568 return res; 569 } 570 571 /** 572 Support for $(D dup) and $(D idup) properties for MultidimArray. 573 */ 574 static if(__traits(compiles, { Unqual!T t = rvalueOf!T; })) 575 @property dup() 576 { 577 return makeCopy!(Unqual!T)(); 578 } 579 580 /// ditto 581 static if(is(T == immutable) || __traits(compiles, { immutable t = rvalueOf!T; })) 582 @property idup() 583 { 584 static if(is(T == immutable)) 585 return this; 586 else 587 return makeCopy!(immutable T)(); 588 } 589 590 591 size_t opDollar(size_t dim)() const @safe pure nothrow @nogc 592 { return lengths[dim]; } 593 594 R opSlice(size_t dim)(in size_t from, in size_t to) const @safe pure nothrow @nogc 595 { return R(from, to); } 596 597 598 /** 599 Inexing/slicing. 600 601 A parameter can be: 602 603 $(TABLE 604 $(TR $(TH type) $(TH meaning) $(TH effect on a resulting dimensions)) 605 $(TR $(TD $(D n)) $(TD a position) $(TD -1)) 606 $(TR $(TD $(D m .. n)) $(TD a range) $(TD 0)) 607 ) 608 Examples: 609 See $(D MultidimArray) examples. 610 611 Bugs: 612 A bit ugly syntax is used because dmd hasn't support for a better one yet (see $(DBUGZILLA 6798)). 613 */ 614 ref opIndex(in size_t[n] indices...) inout @safe pure nothrow /* dmd Issue 13118 @nogc*/ 615 in { assert(elementExists(indices), format("MultidimArray.opIndex(size_t[n]): indices %s are out of bounds (lengths are %s)", indices, _lengths)); } 616 body 617 { 618 return _data[getOffset(indices)]; 619 } 620 621 /// ditto 622 auto opIndex(A...)(A args) inout @trusted pure nothrow /* dmd Issue 13118 @nogc*/ 623 if(args.length == n && allTuple!(isROrSize, A) && RCount!A) 624 in 625 { 626 string formatOutOfBounds(int i, string idx, string reason) @safe pure nothrow 627 { 628 return format("MultidimArray.opIndex: Index #%s = %s is out of bounds 0 .. %s (%s)", 629 i + 1, idx, _lengths[i], reason).assumeWontThrow(); 630 } 631 632 foreach(const i, const a; args) 633 { 634 alias U = Unqual!(A[i]); 635 636 static if(is(U == R)) 637 { 638 assert(a.from <= a.to, format("MultidimArray.opIndex: Index #%s = %s..%s is a range with from > to", i + 1, a.from, a.to)); 639 assert(a.from >= 0, formatOutOfBounds(i, format("%s..%s", a.from, a.to), "from < 0")); 640 assert(a.to <= _lengths[i], formatOutOfBounds(i, format("%s..%s", a.from, a.to), "to > lengths[i]")); 641 } 642 } 643 } 644 body 645 { 646 size_t[n] firstIndices; 647 648 MultidimArray!(T, RCount!A) res; 649 static if(RCount!A == n) 650 res._strides = _strides; 651 652 foreach(const i, const a; args) 653 { 654 alias U = Unqual!(A[i]); 655 656 static if(RCount!U) 657 { 658 enum j = RCount!(A[0 .. i]); 659 static if(RCount!A != n) 660 res._strides[j] = _strides[i]; 661 } 662 663 static if(is(U == R)) 664 { 665 firstIndices[i] = a.from; 666 res._lengths[j] = a.to - a.from; 667 } 668 else 669 { 670 firstIndices[i] = a; 671 } 672 } 673 674 res._data = cast(T[]) _data[getOffset(firstIndices) .. $]; // TODO $ -> actual bound 675 return cast(inout) res; 676 } 677 678 /// ditto 679 static if(isAssignable!T) 680 auto opIndexAssign(U, A...)(U value, A args) 681 if(args.length == n && allTuple!(isROrSize, A)) 682 in 683 { 684 static if(!RCount!A) 685 assert(elementExists(args), format("MultidimArray.opIndexAssign: index out of bounds (lengths: %s, indices: %s)", 686 _lengths, [args])); 687 } 688 body 689 { 690 static if(RCount!A) 691 return this[args][] = value; 692 else 693 return _data[getOffset(args)] = value; 694 } 695 696 /** 697 Creates a slice of this entire array with reordered indices. $(D newOrder[i] = n) means that 698 $(D i)-th index of a resulting array will behave like $(D n)-th index of the original array. 699 Every index sould be used once, otherwise an $(D Exception) will be thrown. 700 701 Example: 702 ---- 703 auto matrix3x4 = multidimArray!int(3, 4); 704 auto transposed = matrix3x4.reorderIndices(1, 0); 705 assert(transposed.lengths == [4, 3]); 706 assert(&matrix3x4[2, 3] == &transposed[3, 2]); 707 ---- 708 709 Example: 710 ---- 711 auto a = multidimArray!int(2, 3, 4); 712 auto b = a.reorderIndices(2, 0, 1); 713 assert(b.lengths == [4, 2, 3]); 714 assert(&a[1, 2, 3] == &b[3, 1, 2]); 715 ---- 716 */ 717 auto reorderIndices(in size_t[n] newOrder...) @safe pure nothrow /* dmd Issue 13118 @nogc*/ 718 in 719 { 720 bool[n] used = false; 721 foreach(const newPlane, const oldPlane; newOrder) 722 { 723 assert(oldPlane >= 0 || oldPlane < n, format( 724 "MultidimArray.reorderIndices: %s isn't a valid index number for a %s-dimnsional array", 725 oldPlane, n)); 726 727 assert(!used[oldPlane], format( 728 "MultidimArray.reorderIndices: Index number %s is used more than once in %s", 729 oldPlane, newOrder)); 730 731 used[oldPlane] = true; 732 } 733 } 734 body 735 { 736 MultidimArray res; 737 foreach(const newPlane, const oldPlane; newOrder) 738 { 739 res._strides[newPlane] = _strides[oldPlane]; 740 res._lengths[newPlane] = _lengths[oldPlane]; 741 } 742 res._data = _data; 743 return res; 744 } 745 746 static if(n <= 3) 747 { 748 /** 749 Conversion to string function for debugging purposes. 750 751 Implemented for $(D dimensions <= 3). 752 */ 753 string toString() 754 { return toStringImpl(0); } 755 756 private string toStringImpl(in size_t minElementLength) 757 { 758 size_t elementLength = minElementLength; 759 760 static if(n == 1) 761 { 762 string res = "["; 763 foreach(i, el; this) 764 res ~= to!string(el).rightJustify(elementLength) ~ (i == _lengths[0] - 1 ? "" : ", "); 765 res ~= ']'; 766 } 767 else static if(n == 2) 768 { 769 foreach(el; byElementForward) 770 elementLength = max(elementLength, to!string(el).length); 771 772 string res; 773 foreach(i; 0 .. _lengths[0]) 774 res ~= this[i, 0..$].toStringImpl(elementLength) ~ '\n'; 775 } 776 else static if(n == 3) 777 { 778 foreach(el; byElementForward) 779 elementLength = max(elementLength, to!string(el).length); 780 781 string res = "["; 782 foreach(i; 0 .. _lengths[0]) 783 res ~= '\n' ~ this[i, 0..$, 0..$].toStringImpl(elementLength); 784 res ~= "]\n"; 785 } 786 else 787 static assert(0); 788 789 return res; 790 } 791 } 792 793 private: 794 795 size_t getOffset(in size_t[n] indices...) const @safe pure nothrow @nogc 796 in 797 { 798 foreach(const plane, const index; indices) 799 assert(index >= 0 && index <= _lengths[plane]); 800 } 801 body 802 { 803 size_t res = 0; 804 foreach(const plane; iotaTuple!n) 805 res += indices[plane] * _strides[plane]; 806 return res; 807 } 808 809 bool elementExists(in size_t[n] indices...) const @safe pure nothrow @nogc 810 { 811 foreach(const plane; iotaTuple!n) 812 if(indices[plane] < 0 || indices[plane] >= _lengths[plane]) 813 return false; 814 return true; 815 } 816 } 817 818 /// 819 unittest 820 { 821 // Let's creates an GC allocated three-dimensional rectangular array from 2 matrices 3x4 822 auto matrices = multidimArray!int(2, 3, 4); // matrices has a type MultidimArray!(int, 3) 823 824 // Setting an element at intersection of the first column and 825 // the third row of the secon matrix to seven: 826 matrices[1, 0, 2] = 7; 827 828 // Filling the whole first column of the secon matrix with sixes: 829 matrices[1, 0, 0..$][] = 6; 830 831 // Filling the whole array with fives: 832 matrices[] = 5; 833 834 // Iterating the array 835 foreach(z, y, x, ref el; matrices) // using opApply 836 el = cast(int) (z * 100 + y * 10 + x); 837 838 int c = 0; 839 foreach(ref el; matrices.byElementForward) 840 el = c++; 841 842 c = 0; 843 foreach(i; 0 .. matrices.elements) 844 matrices.byElementRandomAccess[i] = c++; 845 846 c = 0; 847 foreach(matrix; matrices.byTopDimension) // for each of two matrices 848 foreach(row; matrix.byTopDimension) // for each row 849 foreach(ref el; row.byTopDimension) // for each element 850 el = c++; 851 852 c = 0; 853 foreach_reverse(ref el; matrices.byElementRandomAccess) 854 el = c++; 855 856 c = 0; 857 foreach_reverse(i; 0 .. matrices.elements) 858 matrices.byElementRandomAccess[i] = c++; 859 860 // Inexing/slicing 861 // * use <integer> to select a position 862 // * use <integer> .. <integer> to select a range 863 // E.g. use 0..$ to select the whole range 864 matrices = matrices[0..$, 0..$, 0..$]; // the entire array, same as [0..2, 0..3, 0..4] 865 auto array2d = matrices[0, 0..$, 0..$]; // the first matrix 866 auto array1d = matrices[0, 1, 0..$]; // the second row of the first matrix 867 array1d = matrices[0, 0..$, 1]; // the second column of the first matrix 868 matrices[0, 1, 1] = 9; // setting an element at a crossing of the row an the column 869 870 // first two rows and three columns of the secon matrix 871 array2d = matrices[1, 0 .. 2, 0 .. 3]; 872 } 873 874 /** 875 Convenience function that returns an $(D MultidimArray!(T, n)) object. 876 877 Returns: 878 The first overload returns a $(D MultidimArray) with a newly allocated data 879 Others use an existing storage. 880 881 Params: 882 data = A memory storage for a resulting array of type $(D T[]). 883 array = An array to wrap. It can be a multidimensional static array or a 884 slice of it (has a dynamic top dimension). 885 lengths = Lengths of a resulting array. 886 887 Template_parameters: 888 $(D T) Element type of a resulting array. Should be explicitly defined only 889 for the first overload which has no memory storage. 890 891 $(D n) Dimensions of a resulting array. Can be explicitly defined to use only 892 first $(D n) of $(D array) dimensions. 893 894 $(D A) Type of a wrapping $(D array). It is inferred from the $(D array) argument 895 and should not be explicitly defined. 896 897 See_Also: MultidimArray 898 899 Throws: The first overload throws an $(D RangeError) in $(D debug) build if $(D data) length isn't equal to $(D lengths) prouct. 900 */ 901 // #1: allocate new 902 auto multidimArray(T, size_t n)(size_t[n] lengths...) @safe pure nothrow 903 if(n > 0) 904 { 905 return MultidimArray!(T, n)(lengths); 906 } 907 908 /// ditto 909 // #2: use existing storage 910 auto multidimArray(size_t n, T)(T[] data, size_t[n] lengths...) @safe pure nothrow 911 if(n > 0) 912 { 913 return MultidimArray!(T, n)(data, lengths); 914 } 915 916 /// ditto 917 // #3: use some dimensions of an existing static array 918 auto multidimArray(size_t n, A)(ref A array) pure nothrow 919 if(n > 0 && n <= staticArrayDims!A) 920 { 921 alias ElementType = MultidimStaticArrayElementType!(A, n); 922 return multidimArray!(n, ElementType)(cast(ElementType[]) array, multidimStaticArrayLengths!(A, n)); 923 } 924 925 /// ditto 926 // #4: use all dimensions of an existing static array 927 auto multidimArray(A)(ref A array) pure nothrow 928 if(isStaticArray!A) 929 { 930 return multidimArray!(staticArrayDims!A)(array); 931 } 932 933 /// ditto 934 // #5: use some dimensions of an existing dynamic array of static arrays 935 auto multidimArray(size_t n, A)(A array) @safe pure nothrow 936 if(isDynamicArray!A && n > 0 && n - 1 <= staticArrayDims!(ElementType!A)) 937 { 938 alias U = MultidimStaticArrayElementType!(ElementType!A, n - 1); 939 return multidimArray!(n, U)(cast(U[]) array, array.length, multidimStaticArrayLengths!(ElementType!A, n - 1)); 940 } 941 942 /// ditto 943 // #6: use all dimensions of an existing dynamic array of static arrays 944 auto multidimArray(A)(A array) @safe pure nothrow 945 if(isDynamicArray!A) 946 { 947 return multidimArray!(1 + staticArrayDims!(ElementType!A))(array); 948 } 949 950 /// 951 pure nothrow unittest 952 { 953 // Let's create an GC allocated three-dimensional rectangular array from 2 matrices 3x4 954 auto matrix1 = multidimArray!int(2, 3, 4); 955 956 // Let's create the same array using an existing storage 957 auto darr2 = new int[24]; // At least 24 elements are needed 958 auto matrix2 = multidimArray(darr2, 2, 3, 4); // No need for explicit element type declaration 959 960 // Let's create the same array using an existing static array as data storage 961 int[4][3][2] sarr3; // or in a postfix form: int sarr[2][3][4]; 962 auto matrix3 = multidimArray(sarr3); // No need for any explicit template declarations 963 964 // The head array can be dynamic 965 int[4][3][] darr3 = sarr3[]; 966 auto matrix31 = multidimArray(darr3); // Works like previous one 967 968 // Let's create an array of static arrays 969 ubyte[4][4][3][2] sarr4; // a postfix form: ubyte[4] sarr[2][3][4]; 970 auto matrix4 = multidimArray!3(sarr4); // Only 3 major of 4 dimensions are indeces 971 972 // The head array can also be dynamic 973 auto matrix41 = multidimArray!3(sarr4[]); // Works like previous one 974 } 975 976 pure nothrow unittest // multidimArray 977 { 978 void test234matrix(T)(ref T matrix) 979 { 980 static assert(isForwardRange!(typeof(matrix.byElementForward))); 981 static assert(hasAssignableElements!(typeof(matrix.byElementForward))); 982 static assert(hasLength!(typeof(matrix.byElementForward))); 983 984 static assert(isBidirectionalRange!(typeof(matrix.byElementRandomAccess))); 985 static assert(isRandomAccessRange!(typeof(matrix.byElementRandomAccess))); 986 static assert(hasAssignableElements!(typeof(matrix.byElementRandomAccess))); 987 static assert(hasLength!(typeof(matrix.byElementRandomAccess))); 988 989 static assert(isBidirectionalRange!(typeof(matrix.byTopDimension))); 990 static assert(isRandomAccessRange!(typeof(matrix.byTopDimension))); 991 static assert(hasAssignableElements!(typeof(matrix.byTopDimension)) == (T.dimensions == 1)); 992 static assert(hasLength!(typeof(matrix.byTopDimension))); 993 with(matrix) 994 { 995 assert(_data.length == 24); 996 static assert(lengths.length == 3); 997 assert(lengths == [2, 3, 4]); 998 } 999 } 1000 1001 auto matrix1 = multidimArray!int(2, 3, 4); // #1 1002 test234matrix(matrix1); 1003 1004 auto darr2 = new int[24]; 1005 auto matrix2 = multidimArray(darr2, 2, 3, 4); // #2 1006 test234matrix(matrix2); 1007 1008 int[4][3][2] sarr3; 1009 auto matrix3 = multidimArray(sarr3); // #4 1010 test234matrix(matrix3); 1011 1012 int[4][3][] darr3 = sarr3[]; 1013 auto matrix31 = multidimArray(darr3); // #6 1014 test234matrix(matrix31); 1015 1016 ubyte[4][4][3][2] sarr4; 1017 auto matrix4 = multidimArray!3(sarr4); // #3 1018 test234matrix(matrix4); 1019 1020 auto matrix41 = multidimArray!3(sarr4[]); // #5 1021 test234matrix(matrix41); 1022 } 1023 1024 @safe pure nothrow unittest // MultidimArray properties: dimensions, lengths, elements, packedDimensions 1025 { 1026 auto marr3 = multidimArray!int(3, 4, 5); 1027 1028 with(marr3) 1029 { 1030 static assert(dimensions == 3); 1031 assert(lengths == [3, 4, 5]); 1032 assert(elements == 60); 1033 assert(packedDimensions == 3); 1034 } 1035 1036 auto marr3s = marr3[0..$, 0..$, 0..$]; 1037 with(marr3) 1038 { 1039 static assert(dimensions == 3); 1040 assert(lengths == [3, 4, 5]); 1041 assert(elements == 60); 1042 assert(packedDimensions == 3); 1043 } 1044 1045 marr3s = marr3[0..2, 0..$, 0..$]; 1046 with(marr3s) 1047 { 1048 static assert(dimensions == 3); 1049 assert(lengths == [2, 4, 5]); 1050 assert(elements == 40); 1051 assert(packedDimensions == 3); 1052 } 1053 1054 marr3s = marr3[0..1, 0..$, 0..$]; 1055 with(marr3s) 1056 { 1057 static assert(dimensions == 3); 1058 assert(lengths == [1, 4, 5]); 1059 assert(elements == 20); 1060 assert(packedDimensions == 3); 1061 } 1062 1063 foreach(i; 0 .. 4) 1064 { 1065 marr3s = marr3[0..$, 0..i, 0..$]; 1066 with(marr3s) 1067 { 1068 static assert(dimensions == 3); 1069 assert(lengths == [3, i, 5]); 1070 assert(elements == 15 * i); 1071 assert(packedDimensions == 2); 1072 } 1073 } 1074 1075 foreach(i; 0 .. 5) 1076 { 1077 marr3s = marr3[0..$, 0..$, 0..i]; 1078 with(marr3s) 1079 { 1080 static assert(dimensions == 3); 1081 assert(lengths == [3, 4, i]); 1082 assert(elements == 12 * i); 1083 assert(packedDimensions == 1); 1084 } 1085 } 1086 1087 auto marr2 = marr3[1, 0..$, 0..$]; 1088 with(marr2) 1089 { 1090 static assert(dimensions == 2); 1091 assert(lengths == [4, 5]); 1092 assert(elements == 20); 1093 assert(packedDimensions == 2); 1094 } 1095 1096 marr2 = marr3[0..$, 1, 0..$]; 1097 with(marr2) 1098 { 1099 static assert(dimensions == 2); 1100 assert(lengths == [3, 5]); 1101 assert(elements == 15); 1102 assert(packedDimensions == 1); 1103 } 1104 1105 marr2 = marr3[0..$, 0..$, 1]; 1106 with(marr2) 1107 { 1108 static assert(dimensions == 2); 1109 assert(lengths == [3, 4]); 1110 assert(elements == 12); 1111 assert(packedDimensions == 0); 1112 } 1113 } 1114 1115 unittest // MultidimArray iterations: byElementForward, byElementRandomAccess, byTopDimension, opApply 1116 { 1117 void test(T)() 1118 { 1119 T sarr; 1120 auto darr = cast(int[])sarr; 1121 auto matrix = multidimArray(sarr); 1122 1123 darr[] = -1; 1124 int c; 1125 foreach(ref el; matrix.byElementForward) 1126 { 1127 el = c; 1128 assert(darr[c] == c && el == c && &el == &darr[c]); 1129 ++c; 1130 } 1131 1132 darr[] = -1; 1133 c = 0; 1134 foreach(ref el; matrix.byElementRandomAccess) 1135 { 1136 el = c; 1137 assert(darr[c] == c && el == c && &el == &darr[c]); 1138 ++c; 1139 } 1140 1141 darr[] = -1; 1142 c = cast(int) matrix.elements; 1143 foreach_reverse(ref el; matrix.byElementRandomAccess) 1144 { 1145 --c; 1146 el = c; 1147 assert(darr[c] == c && el == c && &el == &darr[c]); 1148 } 1149 1150 darr[] = -1; 1151 foreach(i; 0 .. matrix.elements) 1152 { 1153 auto ptr = &matrix.byElementRandomAccess[i]; 1154 *ptr = cast(int) i; 1155 assert(darr[i] == i && ptr == &darr[i]); 1156 } 1157 1158 darr[] = -1; 1159 { 1160 auto r = matrix.byElementRandomAccess; 1161 1162 foreach(i; 0 .. matrix.elements) 1163 { 1164 assert(&r[matrix.elements - i - 1] == &darr[$ - 1]); 1165 auto ptr = &r[0]; 1166 // &r.front() instead of &r.front because of bad property syntax 1167 assert(ptr == &darr[i] && ptr == &r.front()); 1168 r.popFront(); 1169 } 1170 } 1171 1172 darr[] = -1; 1173 c = 0; 1174 static if(matrix.dimensions == 3) 1175 foreach(x, y, z, ref el; matrix) 1176 { 1177 el = c; 1178 assert(c == x * matrix._strides[0] + y * matrix._strides[1] + z * matrix._strides[2]); 1179 assert(darr[c] == c && el == c && &el == &darr[c]); 1180 ++c; 1181 } 1182 else static if(matrix.dimensions == 2) 1183 foreach(x, y, ref el; matrix) 1184 { 1185 el = c; 1186 assert(c == x * matrix._strides[0] + y * matrix._strides[1]); 1187 assert(darr[c] == c && el == c && &el == &darr[c]); 1188 ++c; 1189 } 1190 1191 darr[] = -1; 1192 c = 0; 1193 static if(matrix.dimensions == 3) 1194 foreach(subMatrix; matrix.byTopDimension) 1195 foreach(row; subMatrix.byTopDimension) 1196 foreach(ref el; row.byTopDimension) 1197 { 1198 el = c; 1199 assert(darr[c] == c && el == c && &el == &darr[c]); 1200 ++c; 1201 } 1202 else static if(matrix.dimensions == 2) 1203 foreach(row; matrix.byTopDimension) 1204 foreach(ref el; row.byTopDimension) 1205 { 1206 el = c; 1207 assert(darr[c] == c && el == c && &el == &darr[c]); 1208 ++c; 1209 } 1210 else static if(matrix.dimensions == 1) 1211 foreach(ref el; matrix.byTopDimension) 1212 { 1213 el = c; 1214 assert(darr[c] == c && el == c && &el == &darr[c]); 1215 ++c; 1216 } 1217 1218 /*darr[] = -1; 1219 c = 0; 1220 foreach(size_t[matrix.dimensions] indices, ref el; matrix) 1221 { 1222 el = c; 1223 assert(darr[c] == c && el == c && &el == &darr[c]); 1224 ++c; 1225 }*/ 1226 } 1227 test!(int[0])(); 1228 test!(int[2])(); 1229 test!(int[2][0])(); 1230 test!(int[0][2])(); 1231 test!(int[1][1])(); 1232 test!(int[7][3])(); 1233 test!(int[4][3][2])(); 1234 test!(int[1][2][3][4])(); 1235 test!(int[7][3][7][3])(); 1236 } 1237 1238 version(unittest) // MultidimArray unittest helper functions 1239 { 1240 bool isSame(MArr1, MArr2)(MArr1 marr1, MArr2 marr2) pure nothrow 1241 in { assert(marr1.lengths == marr2.lengths); } 1242 body 1243 { 1244 return &marr1[0, 0, 0] == &marr2[0, 0, 0]; 1245 } 1246 1247 bool isCopy(MArr1, MArr2)(MArr1 marr1, MArr2 marr2) 1248 { 1249 return !isSame(marr1, marr2) && equal(marr1.byElementForward, marr2.byElementForward); 1250 } 1251 1252 bool equalRange(MArr, Range)(MArr marr, Range r) 1253 { 1254 return equal(marr.byElementForward, r); 1255 } 1256 } 1257 1258 pure nothrow unittest // MultidimArray copying: opSliceAssign, dup, idup 1259 { 1260 alias repeat = std.range.repeat; // std.string.repeat will be removed in February 2012 1261 1262 auto a23 = multidimArray!int(2, 3); 1263 auto a46 = multidimArray!int(4, 6); 1264 auto a234 = multidimArray!int(2, 3, 4); 1265 assert(equalRange(a23, repeat(0, 6))); 1266 assert(equalRange(a46, repeat(0, 24))); 1267 assert(equalRange(a234, repeat(0, 24))); 1268 1269 a23[] = 7; 1270 assert(equalRange(a23, repeat(7, 6))); 1271 assert(equalRange(a23[] = iota(6), iota(6))); 1272 1273 a234[] = iota(24); 1274 assert(equalRange(a234, iota(24))); 1275 assert((a23[] = a234[0..$, 0..$, 1]) is a23); 1276 assert(equalRange(a23, [1, 5, 9, 13, 17, 21])); 1277 1278 auto b234 = a234.dup; 1279 assert(isCopy(a234, b234)); 1280 1281 b234[] = -1; 1282 assert(equalRange(a234, iota(24))); 1283 1284 b234 = a234; 1285 assert(isSame(a234, b234)); 1286 1287 b234 = multidimArray!int(2, 3, 4); 1288 b234[] = a234; 1289 assert(isCopy(a234, b234)); 1290 1291 auto ia234 = a234.idup; 1292 static assert(is(typeof(ia234) == MultidimArray!(immutable int, 3u))); 1293 static assert(!__traits(compiles, (ia234[] = 7))); 1294 static assert(!__traits(compiles, (ia234[] = new int[24]))); 1295 assert(isCopy(a234, ia234)); 1296 assert(isSame(ia234, ia234.idup)); 1297 assert(isCopy(ia234, ia234.dup)); 1298 1299 const(int)[4][3][2] carr; 1300 auto ca234 = multidimArray(carr); 1301 static assert(is(typeof(ca234) == MultidimArray!(const int, 3u))); 1302 static assert(!__traits(compiles, (ca234[] = 7))); 1303 static assert(!__traits(compiles, (ca234[] = new int[24]))); 1304 assert(isCopy(ca234, ca234.idup)); 1305 assert(isCopy(ca234, ca234.dup)); 1306 1307 1308 b234[] = ia234; 1309 assert(isCopy(a234, b234)); 1310 1311 1312 auto a123 = multidimArray!int(1, 2, 3); 1313 assert(equalRange(a123, repeat(0, 6))); 1314 1315 a123[] = 7; 1316 assert(equalRange(a123, repeat(7, 6))); 1317 1318 a123[] = [8, 8, 8, 8, 8, 8]; 1319 assert(equalRange(a123, repeat(8, 6))); 1320 1321 a123[] = repeat(9, 6); 1322 assert(equalRange(a123, repeat(9, 6))); 1323 1324 auto matrix2 = multidimArray!(int[])(1, 2, 3); 1325 matrix2[] = [9]; 1326 } 1327 1328 @safe pure nothrow unittest // MultidimArray duplication: dup, idup 1329 { 1330 static struct S { int i; } 1331 auto s1 = multidimArray!S(1); 1332 ++s1[0].i; 1333 assert(s1.dup[0].i == 1); 1334 assert(s1.idup[0].i == 1); 1335 } 1336 1337 @safe pure nothrow unittest // MultidimArray reordering: reorderIndices 1338 { 1339 auto matrix3x4 = multidimArray!int(3, 4); 1340 auto transposed = matrix3x4.reorderIndices(1, 0); 1341 assert(transposed.lengths == [4, 3]); 1342 assert(&matrix3x4[2, 3] == &transposed[3, 2]); 1343 1344 auto a = multidimArray!int(2, 3, 4); 1345 auto b = a.reorderIndices(2, 0, 1); 1346 assert(b.lengths == [4, 2, 3]); 1347 assert(&a[1, 2, 3] == &b[3, 1, 2]); 1348 } 1349 1350 @safe pure nothrow unittest 1351 { 1352 auto matrix = multidimArray!int(3, 4); 1353 1354 foreach(ref el; matrix.byFunction!`a + 1, a + 1`) 1355 el = 1; 1356 foreach(ref el; matrix.byFunction!`a, (a + 1)^^2 - 1`()) 1357 el = 2; 1358 1359 assert(equalRange(matrix, [ 1360 2, 0, 0, 0, 1361 0, 1, 0, 2, 1362 0, 0, 1, 0])); 1363 } 1364 1365 // TODO unittests for: opIndexAssign, opIndex