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