1 /++
2 This is a submodule of $(MREF mir,ndslice).
3 
4 Selectors create new views and iteration patterns over the same data, without copying.
5 
6 $(BOOKTABLE $(H2 Sequence Selectors),
7 $(TR $(TH Function Name) $(TH Description))
8 
9 $(T2 cycle, Cycle repeates 1-dimensional field/range/array/slice in a fixed length 1-dimensional slice)
10 $(T2 iota, Contiguous Slice with initial flattened (contiguous) index.)
11 $(T2 linspace, Evenly spaced numbers over a specified interval.)
12 $(T2 magic, Magic square.)
13 $(T2 ndiota, Contiguous Slice with initial multidimensional index.)
14 $(T2 repeat, Slice with identical values)
15 )
16 
17 $(BOOKTABLE $(H2 Shape Selectors),
18 $(TR $(TH Function Name) $(TH Description))
19 
20 $(T2 blocks, n-dimensional slice composed of n-dimensional non-overlapping blocks. If the slice has two dimensions, it is a block matrix.)
21 $(T2 diagonal, 1-dimensional slice composed of diagonal elements)
22 $(T2 dropBorders, Drops borders for all dimensions.)
23 $(T2 reshape, New slice view with changed dimensions)
24 $(T2 squeeze, New slice view of an n-dimensional slice with dimension removed)
25 $(T2 unsqueeze, New slice view of an n-dimensional slice with a dimension added)
26 $(T2 windows, n-dimensional slice of n-dimensional overlapping windows. If the slice has two dimensions, it is a sliding window.)
27 
28 )
29 
30 
31 $(BOOKTABLE $(H2 Subspace Selectors),
32 $(TR $(TH Function Name) $(TH Description))
33 
34 $(T2 alongDim , Returns a slice that can be iterated along dimension.)
35 $(T2 byDim    , Returns a slice that can be iterated by dimension.)
36 $(T2 pack     , Returns slice of slices.)
37 $(T2 ipack    , Returns slice of slices.)
38 $(T2 unpack   , Merges two hight dimension packs. See also $(SUBREF fuse, fuse).)
39 $(T2 evertPack, Reverses dimension packs.)
40 
41 )
42 
43 $(BOOKTABLE $(H2 SliceKind Selectors),
44 $(TR $(TH Function Name) $(TH Description))
45 
46 $(T2 asKindOf, Converts a slice to a user provied kind $(SUBREF slice, SliceKind).)
47 $(T2 universal, Converts a slice to universal $(SUBREF slice, SliceKind).)
48 $(T2 canonical, Converts a slice to canonical $(SUBREF slice, SliceKind).)
49 $(T2 assumeCanonical, Converts a slice to canonical $(SUBREF slice, SliceKind). Does only `assert` checks.)
50 $(T2 assumeContiguous, Converts a slice to contiguous $(SUBREF slice, SliceKind). Does only `assert` checks.)
51 $(T2 assumeHypercube, Helps the compiler to use optimisations related to the shape form. Does only `assert` checks.)
52 $(T2 assumeSameShape, Helps the compiler to use optimisations related to the shape form. Does only `assert` checks.)
53 
54 )
55 
56 $(BOOKTABLE $(H2 Products),
57 $(TR $(TH Function Name) $(TH Description))
58 
59 $(T2 cartesian, Cartesian product.)
60 $(T2 kronecker, Kronecker product.)
61 
62 )
63 
64 $(BOOKTABLE $(H2 Representation Selectors),
65 $(TR $(TH Function Name) $(TH Description))
66 
67 $(T2 as, Convenience function that creates a lazy view,
68 where each element of the original slice is converted to a type `T`.)
69 $(T2 bitpack, Bitpack slice over an unsigned integral slice.)
70 $(T2 bitwise, Bitwise slice over an unsigned integral slice.)
71 $(T2 bytegroup, Groups existing slice into fixed length chunks and uses them as data store for destination type.)
72 $(T2 cached, Random access cache. It is usefull in combiation with $(LREF map) and $(LREF vmap).)
73 $(T2 cachedGC, Random access cache auto-allocated in GC heap. It is usefull in combiation with $(LREF map) and $(LREF vmap).)
74 $(T2 diff, Differences between vector elements.)
75 $(T2 flattened, Contiguous 1-dimensional slice of all elements of a slice.)
76 $(T2 map, Multidimensional functional map.)
77 $(T2 member, Field (element's member) projection.)
78 $(T2 orthogonalReduceField, Functional deep-element wise reduce of a slice composed of fields or iterators.)
79 $(T2 pairwise, Pairwise map for vectors.)
80 $(T2 pairwiseMapSubSlices, Maps pairwise index pairs to subslices.)
81 $(T2 retro, Reverses order of iteration for all dimensions.)
82 $(T2 slide, Lazy convolution for tensors.)
83 $(T2 slideAlong, Lazy convolution for tensors.)
84 $(T2 stairs, Two functions to pack, unpack, and iterate triangular and symmetric matrix storage.)
85 $(T2 stride, Strides 1-dimensional slice.)
86 $(T2 subSlices, Maps index pairs to subslices.)
87 $(T2 triplets, Constructs a lazy view of triplets with `left`, `center`, and `right` members. The topology is usefull for Math and Physics.)
88 $(T2 unzip, Selects a slice from a zipped slice.)
89 $(T2 withNeighboursSum, Zip view of elements packed with sum of their neighbours.)
90 $(T2 zip, Zips slices into a slice of refTuples.)
91 )
92 
93 Subspace selectors serve to generalize and combine other selectors easily.
94 For a slice of `Slice!(Iterator, N, kind)` type `slice.pack!K` creates a slice of
95 slices of `Slice!(kind, [N - K, K], Iterator)` type by packing
96 the last `K` dimensions of the top dimension pack,
97 and the type of element of $(LREF flattened) is `Slice!(Iterator, K)`.
98 Another way to use $(LREF pack) is transposition of dimension packs using
99 $(LREF evertPack).
100 Examples of use of subspace selectors are available for selectors,
101 $(SUBREF slice, Slice.shape), and $(SUBREF slice, Slice.elementCount).
102 
103 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0)
104 Copyright: 2020 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments
105 Authors: Ilya Yaroshenko, John Michael Hall, Shigeki Karita (original numir code)
106 
107 Sponsors: Part of this work has been sponsored by $(LINK2 http://symmetryinvestments.com, Symmetry Investments) and Kaleidic Associates.
108 
109 Macros:
110 SUBREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP)
111 T2=$(TR $(TDNW $(LREF $1)) $(TD $+))
112 T4=$(TR $(TDNW $(LREF $1)) $(TD $2) $(TD $3) $(TD $4))
113 +/
114 module mir.ndslice.topology;
115 
116 import mir.internal.utility;
117 import mir.math.common: optmath;
118 import mir.ndslice.field;
119 import mir.ndslice.internal;
120 import mir.ndslice.iterator;
121 import mir.ndslice.ndfield;
122 import mir.ndslice.slice;
123 import mir.primitives;
124 import mir.qualifier;
125 import mir.utility: min;
126 import std.meta: AliasSeq, allSatisfy, staticMap, templateOr, Repeat;
127 
128 private immutable choppedExceptionMsg = "bounds passed to chopped are out of sliceable bounds.";
129 version (D_Exceptions) private immutable choppedException = new Exception(choppedExceptionMsg);
130 
131 @optmath:
132 
133 /++
134 Converts a slice to user provided kind.
135 
136 Contiguous slices can be converted to any kind.
137 Canonical slices can't be converted to contiguous slices.
138 Universal slices can be converted only to the same kind.
139 
140 See_also:
141     $(LREF canonical),
142     $(LREF universal),
143     $(LREF assumeCanonical),
144     $(LREF assumeContiguous).
145 +/
146 template asKindOf(SliceKind kind)
147 {
148     static if (kind == Contiguous)
149     {
150         auto asKindOf(Iterator, size_t N, Labels...)(Slice!(Iterator, N, Contiguous, Labels) slice)
151         {
152             return slice;
153         }
154     }
155     else
156     static if (kind == Canonical)
157     {
158         alias asKindOf = canonical;
159     }
160     else
161     {
162         alias asKindOf = universal;
163     }
164 }
165 
166 /// Universal
167 @safe pure nothrow
168 version(mir_test) unittest
169 {
170     import mir.ndslice.slice: Universal;
171     auto slice = iota(2, 3).asKindOf!Universal;
172     assert(slice == [[0, 1, 2], [3, 4, 5]]);
173     assert(slice._lengths == [2, 3]);
174     assert(slice._strides == [3, 1]);
175 }
176 
177 /// Canonical
178 @safe pure nothrow
179 version(mir_test) unittest
180 {
181     import mir.ndslice.slice: Canonical;
182     auto slice = iota(2, 3).asKindOf!Canonical;
183     assert(slice == [[0, 1, 2], [3, 4, 5]]);
184     assert(slice._lengths == [2, 3]);
185     assert(slice._strides == [3]);
186 }
187 
188 /// Contiguous
189 @safe pure nothrow
190 version(mir_test) unittest
191 {
192     import mir.ndslice.slice: Contiguous;
193     auto slice = iota(2, 3).asKindOf!Contiguous;
194     assert(slice == [[0, 1, 2], [3, 4, 5]]);
195     assert(slice._lengths == [2, 3]);
196     assert(slice._strides == []);
197 }
198 
199 /++
200 Converts a slice to universal kind.
201 
202 Params:
203     slice = a slice
204 Returns:
205     universal slice
206 See_also:
207     $(LREF canonical),
208     $(LREF assumeCanonical),
209     $(LREF assumeContiguous).
210 +/
211 auto universal(Iterator, size_t N, SliceKind kind, Labels...)(Slice!(Iterator, N, kind, Labels) slice)
212 {
213     import core.lifetime: move;
214 
215     static if (kind == Universal)
216     {
217         return slice;
218     }
219     else
220     static if (is(Iterator : RetroIterator!It, It))
221     {
222         return slice.move.retro.universal.retro;
223     }
224     else
225     {
226         alias Ret = Slice!(Iterator, N, Universal, Labels);
227         size_t[Ret.N] lengths;
228         auto strides = sizediff_t[Ret.S].init;
229         foreach (i; Iota!(slice.N))
230             lengths[i] = slice._lengths[i];
231         static if (kind == Canonical)
232         {
233             foreach (i; Iota!(slice.S))
234                 strides[i] = slice._strides[i];
235             strides[$-1] = 1;
236         }
237         else
238         {
239             ptrdiff_t ball = 1;
240             foreach_reverse (i; Iota!(Ret.S))
241             {
242                 strides[i] = ball;
243                 static if (i)
244                     ball *= slice._lengths[i];
245             }
246         }
247         return Ret(lengths, strides, slice._iterator.move, slice._labels);
248     }
249 }
250 
251 ///
252 @safe pure nothrow
253 version(mir_test) unittest
254 {
255     auto slice = iota(2, 3).universal;
256     assert(slice == [[0, 1, 2], [3, 4, 5]]);
257     assert(slice._lengths == [2, 3]);
258     assert(slice._strides == [3, 1]);
259 }
260 
261 @safe pure nothrow
262 version(mir_test) unittest
263 {
264     auto slice = iota(2, 3).canonical.universal;
265     assert(slice == [[0, 1, 2], [3, 4, 5]]);
266     assert(slice._lengths == [2, 3]);
267     assert(slice._strides == [3, 1]);
268 }
269 
270 ///
271 @safe pure nothrow
272 version(mir_test) unittest
273 {
274     import mir.ndslice.slice;
275     import mir.ndslice.allocation: slice;
276 
277     auto dataframe = slice!(double, int, string)(2, 3);
278     dataframe.label[] = [1, 2];
279     dataframe.label!1[] = ["Label1", "Label2", "Label3"];
280 
281     auto universaldf = dataframe.universal;
282     assert(universaldf._lengths == [2, 3]);
283     assert(universaldf._strides == [3, 1]);
284 
285     assert(is(typeof(universaldf) ==
286         Slice!(double*, 2, Universal, int*, string*)));
287     assert(universaldf.label!0[0] == 1);
288     assert(universaldf.label!1[1] == "Label2");
289 }
290 
291 /++
292 Converts a slice to canonical kind.
293 
294 Params:
295     slice = contiguous or canonical slice
296 Returns:
297     canonical slice
298 See_also:
299     $(LREF universal),
300     $(LREF assumeCanonical),
301     $(LREF assumeContiguous).
302 +/
303 Slice!(Iterator, N, N == 1 ? Contiguous : Canonical, Labels)
304     canonical
305     (Iterator, size_t N, SliceKind kind, Labels...)
306     (Slice!(Iterator, N, kind, Labels) slice)
307     if (kind == Contiguous || kind == Canonical)
308 {
309     import core.lifetime: move;
310 
311     static if (kind == Canonical || N == 1)
312         return slice;
313     else
314     {
315         alias Ret = typeof(return);
316         size_t[Ret.N] lengths;
317         auto strides = sizediff_t[Ret.S].init;
318         foreach (i; Iota!(slice.N))
319             lengths[i] = slice._lengths[i];
320         ptrdiff_t ball = 1;
321         foreach_reverse (i; Iota!(Ret.S))
322         {
323             ball *= slice._lengths[i + 1];
324             strides[i] = ball;
325         }
326         return Ret(lengths, strides, slice._iterator.move, slice._labels);
327     }
328 }
329 
330 ///
331 @safe pure nothrow
332 version(mir_test) unittest
333 {
334     auto slice = iota(2, 3).canonical;
335     assert(slice == [[0, 1, 2], [3, 4, 5]]);
336     assert(slice._lengths == [2, 3]);
337     assert(slice._strides == [3]);
338 }
339 
340 ///
341 @safe pure nothrow
342 version(mir_test) unittest
343 {
344     import mir.ndslice.slice;
345     import mir.ndslice.allocation: slice;
346 
347     auto dataframe = slice!(double, int, string)(2, 3);
348     dataframe.label[] = [1, 2];
349     dataframe.label!1[] = ["Label1", "Label2", "Label3"];
350 
351     auto canonicaldf = dataframe.canonical;
352     assert(canonicaldf._lengths == [2, 3]);
353     assert(canonicaldf._strides == [3]);
354 
355     assert(is(typeof(canonicaldf) ==
356         Slice!(double*, 2, Canonical, int*, string*)));
357     assert(canonicaldf.label!0[0] == 1);
358     assert(canonicaldf.label!1[1] == "Label2");
359 }
360 
361 /++
362 Converts a slice to canonical kind (unsafe).
363 
364 Params:
365     slice = a slice
366 Returns:
367     canonical slice
368 See_also:
369     $(LREF universal),
370     $(LREF canonical),
371     $(LREF assumeContiguous).
372 +/
373 Slice!(Iterator, N, Canonical, Labels)
374     assumeCanonical
375     (Iterator, size_t N, SliceKind kind, Labels...)
376     (Slice!(Iterator, N, kind, Labels) slice)
377 {
378     static if (kind == Contiguous)
379         return slice.canonical;
380     else
381     static if (kind == Canonical)
382         return slice;
383     else
384     {
385         import mir.utility: swap;
386         assert(slice._lengths[N - 1] <= 1 || slice._strides[N - 1] == 1);
387         typeof(return) ret;
388         ret._lengths = slice._lengths;
389         ret._strides = slice._strides[0 .. $ - 1];
390         swap(ret._iterator, slice._iterator);
391         foreach(i, _; Labels)
392             swap(ret._labels[i], slice._labels[i]);
393         return ret;
394     }
395 }
396 
397 ///
398 @safe pure nothrow
399 version(mir_test) unittest
400 {
401     auto slice = iota(2, 3).universal.assumeCanonical;
402     assert(slice == [[0, 1, 2], [3, 4, 5]]);
403     assert(slice._lengths == [2, 3]);
404     assert(slice._strides == [3]);
405 }
406 
407 ///
408 @safe pure nothrow
409 version(mir_test) unittest
410 {
411     import mir.ndslice.slice;
412     import mir.ndslice.allocation: slice;
413 
414     auto dataframe = slice!(double, int, string)(2, 3);
415     dataframe.label[] = [1, 2];
416     dataframe.label!1[] = ["Label1", "Label2", "Label3"];
417 
418     auto assmcanonicaldf = dataframe.assumeCanonical;
419     assert(assmcanonicaldf._lengths == [2, 3]);
420     assert(assmcanonicaldf._strides == [3]);
421 
422     assert(is(typeof(assmcanonicaldf) ==
423         Slice!(double*, 2, Canonical, int*, string*)));
424     assert(assmcanonicaldf.label!0[0] == 1);
425     assert(assmcanonicaldf.label!1[1] == "Label2");
426 }
427 
428 /++
429 Converts a slice to contiguous kind (unsafe).
430 
431 Params:
432     slice = a slice
433 Returns:
434     canonical slice
435 See_also:
436     $(LREF universal),
437     $(LREF canonical),
438     $(LREF assumeCanonical).
439 +/
440 Slice!(Iterator, N, Contiguous, Labels)
441     assumeContiguous
442     (Iterator, size_t N, SliceKind kind, Labels...)
443     (Slice!(Iterator, N, kind, Labels) slice)
444 {
445     static if (kind == Contiguous)
446         return slice;
447     else
448     {
449         import mir.utility: swap;
450         typeof(return) ret;
451         ret._lengths = slice._lengths;
452         swap(ret._iterator, slice._iterator);
453         foreach(i, _; Labels)
454             swap(ret._labels[i], slice._labels[i]);
455         return ret;
456     }
457 }
458 
459 ///
460 @safe pure nothrow
461 version(mir_test) unittest
462 {
463     auto slice = iota(2, 3).universal.assumeContiguous;
464     assert(slice == [[0, 1, 2], [3, 4, 5]]);
465     assert(slice._lengths == [2, 3]);
466     static assert(slice._strides.length == 0);
467 }
468 
469 ///
470 @safe pure nothrow
471 version(mir_test) unittest
472 {
473     import mir.ndslice.slice;
474     import mir.ndslice.allocation: slice;
475 
476     auto dataframe = slice!(double, int, string)(2, 3);
477     dataframe.label[] = [1, 2];
478     dataframe.label!1[] = ["Label1", "Label2", "Label3"];
479 
480     auto assmcontdf = dataframe.canonical.assumeContiguous;
481     assert(assmcontdf._lengths == [2, 3]);
482     static assert(assmcontdf._strides.length == 0);
483 
484     assert(is(typeof(assmcontdf) ==
485         Slice!(double*, 2, Contiguous, int*, string*)));
486     assert(assmcontdf.label!0[0] == 1);
487     assert(assmcontdf.label!1[1] == "Label2");
488 }
489 
490 /++
491 Helps the compiler to use optimisations related to the shape form
492 +/
493 void assumeHypercube
494     (Iterator, size_t N, SliceKind kind, Labels...)
495     (ref scope Slice!(Iterator, N, kind, Labels) slice)
496 {
497     foreach (i; Iota!(1, N))
498     {
499         assert(slice._lengths[i] == slice._lengths[0]);
500         slice._lengths[i] = slice._lengths[0];
501     }
502 }
503 
504 ///
505 @safe @nogc pure nothrow version(mir_test) unittest
506 {
507     auto b = iota(5, 5);
508     
509     assumeHypercube(b);
510 
511     assert(b == iota(5, 5));
512 }
513 
514 /++
515 Helps the compiler to use optimisations related to the shape form
516 +/
517 void assumeSameShape(T...)
518     (ref scope T slices)
519     if (allSatisfy!(isSlice, T))
520 {
521     foreach (i; Iota!(1, T.length))
522     {
523         assert(slices[i]._lengths == slices[0]._lengths);
524         slices[i]._lengths = slices[0]._lengths;
525     }
526 }
527 
528 ///
529 @safe @nogc pure nothrow version(mir_test) unittest
530 {
531     auto a = iota(5, 5);
532     auto b = iota(5, 5);
533     
534     assumeHypercube(a); // first use this one, if applicable
535     assumeSameShape(a, b); //
536 
537     assert(a == iota(5, 5));
538     assert(b == iota(5, 5));
539 }
540 
541 /++
542 +/
543 auto assumeFieldsHaveZeroShift(Iterator, size_t N, SliceKind kind)
544     (Slice!(Iterator, N, kind) slice)
545     if (__traits(hasMember, Iterator, "assumeFieldsHaveZeroShift"))
546 {
547     return slice._iterator.assumeFieldsHaveZeroShift.slicedField(slice._lengths);
548 }
549 
550 /++
551 Creates a packed slice, i.e. slice of slices.
552 Packs the last `P` dimensions.
553 The function does not allocate any data.
554 
555 Params:
556     P = size of dimension pack
557     slice = a slice to pack
558 Returns:
559     `slice.pack!p` returns `Slice!(kind, [N - p, p], Iterator)`
560 See_also: $(LREF ipack)
561 +/
562 Slice!(SliceIterator!(Iterator, P, P == 1 && kind == Canonical ? Contiguous : kind), N - P, Universal)
563 pack(size_t P, Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice)
564     if (P && P < N)
565 {
566     import core.lifetime: move;
567     return slice.move.ipack!(N - P);
568 }
569 
570 ///
571 @safe @nogc pure nothrow version(mir_test) unittest
572 {
573     import mir.ndslice.slice: sliced, Slice;
574 
575     auto a = iota(3, 4, 5, 6);
576     auto b = a.pack!2;
577 
578     static immutable res1 = [3, 4];
579     static immutable res2 = [5, 6];
580     assert(b.shape == res1);
581     assert(b[0, 0].shape == res2);
582     assert(a == b.unpack);
583     assert(a.pack!2 == b);
584     static assert(is(typeof(b) == typeof(a.pack!2)));
585 }
586 
587 /++
588 Creates a packed slice, i.e. slice of slices.
589 Packs the last `N - P` dimensions.
590 The function does not allocate any data.
591 
592 Params:
593     + = size of dimension pack
594     slice = a slice to pack
595 See_also: $(LREF pack)
596 +/
597 Slice!(SliceIterator!(Iterator, N - P, N - P == 1 && kind == Canonical ? Contiguous : kind), P, Universal)
598 ipack(size_t P, Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice)
599     if (P && P < N)
600 {
601     import core.lifetime: move;
602     alias Ret = typeof(return);
603     alias It = Ret.Iterator;
604     alias EN = It.Element.N;
605     alias ES = It.Element.S;
606     auto sl = slice.move.universal;
607     static if (It.Element.kind == Contiguous)
608         return Ret(
609             cast(   size_t[P]) sl._lengths[0 .. P],
610             cast(ptrdiff_t[P]) sl._strides[0 .. P],
611             It(
612                 cast(size_t[EN]) sl._lengths[P .. $],
613                 sl._iterator.move));
614     else
615         return Ret(
616             cast(   size_t[P]) sl._lengths[0 .. P],
617             cast(ptrdiff_t[P]) sl._strides[0 .. P],
618             It(
619                 cast(   size_t[EN]) sl._lengths[P .. $],
620                 cast(ptrdiff_t[ES]) sl._strides[P .. $ - (It.Element.kind == Canonical)],
621                 sl._iterator.move));
622 }
623 
624 ///
625 @safe @nogc pure nothrow version(mir_test) unittest
626 {
627     import mir.ndslice.slice: sliced, Slice;
628 
629     auto a = iota(3, 4, 5, 6);
630     auto b = a.ipack!2;
631 
632     static immutable res1 = [3, 4];
633     static immutable res2 = [5, 6];
634     assert(b.shape == res1);
635     assert(b[0, 0].shape == res2);
636     assert(a.ipack!2 == b);
637     static assert(is(typeof(b) == typeof(a.ipack!2)));
638 }
639 
640 /++
641 Unpacks a packed slice.
642 
643 The functions does not allocate any data.
644 
645 Params:
646     slice = packed slice
647 Returns:
648     unpacked slice, that is a view on the same data.
649 
650 See_also: $(LREF pack), $(LREF evertPack)
651 +/
652 Slice!(Iterator, N + M, min(innerKind, Canonical))
653     unpack(Iterator, size_t M, SliceKind innerKind, size_t N, SliceKind outerKind)
654     (Slice!(SliceIterator!(Iterator, M, innerKind), N, outerKind) slice)
655 {
656     alias Ret = typeof(return);
657     size_t[N + M] lengths;
658     auto strides = sizediff_t[Ret.S].init;
659     auto outerStrides = slice.strides;
660     auto innerStrides = Slice!(Iterator, M, innerKind)(
661         slice._iterator._structure,
662         slice._iterator._iterator,
663         ).strides;
664     foreach(i; Iota!N)
665         lengths[i] = slice._lengths[i];
666     foreach(i; Iota!N)
667         strides[i] = outerStrides[i];
668     foreach(i; Iota!M)
669         lengths[N + i] = slice._iterator._structure[0][i];
670     foreach(i; Iota!(Ret.S - N))
671         strides[N + i] = innerStrides[i];
672     return Ret(lengths, strides, slice._iterator._iterator);
673 }
674 
675 /++
676 Reverses the order of dimension packs.
677 This function is used in a functional pipeline with other selectors.
678 
679 Params:
680     slice = packed slice
681 Returns:
682     packed slice
683 
684 See_also: $(LREF pack), $(LREF unpack)
685 +/
686 Slice!(SliceIterator!(Iterator, N, outerKind), M, innerKind)
687 evertPack(Iterator, size_t M, SliceKind innerKind, size_t N, SliceKind outerKind)
688     (Slice!(SliceIterator!(Iterator, M, innerKind), N, outerKind) slice)
689 {
690     import core.lifetime: move;
691     return typeof(return)(
692         slice._iterator._structure,
693         typeof(return).Iterator(
694             slice._structure,
695             slice._iterator._iterator.move));
696 }
697 
698 ///
699 @safe @nogc pure nothrow version(mir_test) unittest
700 {
701     import mir.ndslice.dynamic : transposed;
702     auto slice = iota(3, 4, 5, 6, 7, 8, 9, 10, 11).universal;
703     assert(slice
704         .pack!2
705         .evertPack
706         .unpack
707              == slice.transposed!(
708                 slice.shape.length-2,
709                 slice.shape.length-1));
710 }
711 
712 ///
713 @safe pure nothrow version(mir_test) unittest
714 {
715     import mir.ndslice.iterator: SliceIterator;
716     import mir.ndslice.slice: sliced, Slice, Universal;
717     import mir.ndslice.allocation: slice;
718     static assert(is(typeof(
719         slice!int(6)
720             .sliced(1,2,3)
721             .pack!1
722             .evertPack
723         )
724          == Slice!(SliceIterator!(int*, 2, Universal), 1)));
725 }
726 
727 ///
728 @safe pure nothrow @nogc
729 version(mir_test) unittest
730 {
731     auto a = iota(3, 4, 5, 6, 7, 8, 9, 10, 11);
732     auto b = a.pack!2.unpack;
733     static assert(is(typeof(a.canonical) == typeof(b)));
734     assert(a == b);
735 }
736 
737 /++
738 Returns a slice, the elements of which are equal to the initial flattened index value.
739 
740 Params:
741     N = dimension count
742     lengths = list of dimension lengths
743     start = value of the first element in a slice (optional for integer `I`)
744     stride = value of the stride between elements (optional)
745 Returns:
746     n-dimensional slice composed of indices
747 See_also: $(LREF ndiota)
748 +/
749 Slice!(IotaIterator!I, N)
750 iota
751     (I = sizediff_t, size_t N)(size_t[N] lengths...)
752     if (__traits(isIntegral, I))
753 {
754     import mir.ndslice.slice: sliced;
755     return IotaIterator!I(I.init).sliced(lengths);
756 }
757 
758 ///ditto
759 Slice!(IotaIterator!sizediff_t, N)
760 iota
761     (size_t N)(size_t[N] lengths, sizediff_t start)
762 {
763     import mir.ndslice.slice: sliced;
764     return IotaIterator!sizediff_t(start).sliced(lengths);
765 }
766 
767 ///ditto
768 Slice!(StrideIterator!(IotaIterator!sizediff_t), N)
769 iota
770     (size_t N)(size_t[N] lengths, sizediff_t start, size_t stride)
771 {
772     import mir.ndslice.slice: sliced;
773     return StrideIterator!(IotaIterator!sizediff_t)(stride, IotaIterator!sizediff_t(start)).sliced(lengths);
774 }
775 
776 ///ditto
777 template iota(I)
778     if (__traits(isIntegral, I))
779 {
780     ///
781     Slice!(IotaIterator!I, N)
782     iota
783         (size_t N)(size_t[N] lengths, I start)
784         if (__traits(isIntegral, I))
785     {
786         import mir.ndslice.slice: sliced;
787         return IotaIterator!I(start).sliced(lengths);
788     }
789 
790     ///ditto
791     Slice!(StrideIterator!(IotaIterator!I), N)
792     iota
793         (size_t N)(size_t[N] lengths, I start, size_t stride)
794         if (__traits(isIntegral, I))
795     {
796         import mir.ndslice.slice: sliced;
797         return StrideIterator!(IotaIterator!I)(stride, IotaIterator!I(start)).sliced(lengths);
798     }
799 }
800 
801 ///ditto
802 Slice!(IotaIterator!I, N)
803 iota
804     (I, size_t N)(size_t[N] lengths, I start)
805     if (is(I P : P*))
806 {
807     import mir.ndslice.slice: sliced;
808     return IotaIterator!I(start).sliced(lengths);
809 }
810 
811 ///ditto
812 Slice!(StrideIterator!(IotaIterator!I), N)
813 iota
814     (I, size_t N)(size_t[N] lengths, I start, size_t stride)
815     if (is(I P : P*))
816 {
817     import mir.ndslice.slice: sliced;
818     return StrideIterator!(IotaIterator!I)(stride, IotaIterator!I(start)).sliced(lengths);
819 }
820 
821 ///
822 @safe pure nothrow @nogc version(mir_test) unittest
823 {
824     import mir.primitives: DeepElementType;
825     auto slice = iota(2, 3);
826     static immutable array =
827         [[0, 1, 2],
828          [3, 4, 5]];
829 
830     assert(slice == array);
831 
832     static assert(is(DeepElementType!(typeof(slice)) == sizediff_t));
833 }
834 
835 ///
836 pure nothrow @nogc
837 version(mir_test) unittest
838 {
839     int[6] data;
840     auto slice = iota([2, 3], data.ptr);
841     assert(slice[0, 0] == data.ptr);
842     assert(slice[0, 1] == data.ptr + 1);
843     assert(slice[1, 0] == data.ptr + 3);
844 }
845 
846 ///
847 @safe pure nothrow @nogc
848 version(mir_test) unittest
849 {
850     auto im = iota([10, 5], 100);
851     assert(im[2, 1] == 111); // 100 + 2 * 5 + 1
852 
853     //slicing works correctly
854     auto cm = im[1 .. $, 3 .. $];
855     assert(cm[2, 1] == 119); // 119 = 100 + (1 + 2) * 5 + (3 + 1)
856 }
857 
858 /// `iota` with step
859 @safe pure nothrow version(mir_test) unittest
860 {
861     auto sl = iota([2, 3], 10, 10);
862 
863     assert(sl == [[10, 20, 30],
864                   [40, 50, 60]]);
865 }
866 
867 /++
868 Returns a 1-dimensional slice over the main diagonal of an n-dimensional slice.
869 `diagonal` can be generalized with other selectors such as
870 $(LREF blocks) (diagonal blocks) and $(LREF windows) (multi-diagonal slice).
871 
872 Params:
873     slice = input slice
874 Returns:
875     1-dimensional slice composed of diagonal elements
876 See_also: $(LREF antidiagonal)
877 +/
878 Slice!(Iterator, 1, N == 1 ? kind : Universal)
879     diagonal
880     (Iterator, size_t N, SliceKind kind)
881     (Slice!(Iterator, N, kind) slice)
882 {
883     static if (N == 1)
884     {
885         return slice;
886     }
887     else
888     {
889         alias Ret = typeof(return);
890         size_t[Ret.N] lengths;
891         auto strides = sizediff_t[Ret.S].init;
892         lengths[0] = slice._lengths[0];
893         foreach (i; Iota!(1, N))
894             if (lengths[0] > slice._lengths[i])
895                 lengths[0] = slice._lengths[i];
896         foreach (i; Iota!(1, Ret.N))
897             lengths[i] = slice._lengths[i + N - 1];
898         auto rstrides = slice.strides;
899         strides[0] = rstrides[0];
900         foreach (i; Iota!(1, N))
901             strides[0] += rstrides[i];
902         foreach (i; Iota!(1, Ret.S))
903             strides[i] = rstrides[i + N - 1];
904         return Ret(lengths, strides, slice._iterator);
905     }
906 }
907 
908 /// Matrix, main diagonal
909 @safe @nogc pure nothrow version(mir_test) unittest
910 {
911     //  -------
912     // | 0 1 2 |
913     // | 3 4 5 |
914     //  -------
915     //->
916     // | 0 4 |
917     static immutable d = [0, 4];
918     assert(iota(2, 3).diagonal == d);
919 }
920 
921 /// Non-square matrix
922 @safe pure nothrow version(mir_test) unittest
923 {
924     //  -------
925     // | 0 1 |
926     // | 2 3 |
927     // | 4 5 |
928     //  -------
929     //->
930     // | 0 3 |
931 
932     assert(iota(3, 2).diagonal == iota([2], 0, 3));
933 }
934 
935 /// Loop through diagonal
936 @safe pure nothrow version(mir_test) unittest
937 {
938     import mir.ndslice.slice;
939     import mir.ndslice.allocation;
940 
941     auto slice = slice!int(3, 3);
942     int i;
943     foreach (ref e; slice.diagonal)
944         e = ++i;
945     assert(slice == [
946         [1, 0, 0],
947         [0, 2, 0],
948         [0, 0, 3]]);
949 }
950 
951 /// Matrix, subdiagonal
952 @safe @nogc pure nothrow
953 version(mir_test) unittest
954 {
955     //  -------
956     // | 0 1 2 |
957     // | 3 4 5 |
958     //  -------
959     //->
960     // | 1 5 |
961     static immutable d = [1, 5];
962     auto a = iota(2, 3).canonical;
963     a.popFront!1;
964     assert(a.diagonal == d);
965 }
966 
967 /// 3D, main diagonal
968 @safe @nogc pure nothrow version(mir_test) unittest
969 {
970     //  -----------
971     // |  0   1  2 |
972     // |  3   4  5 |
973     //  - - - - - -
974     // |  6   7  8 |
975     // |  9  10 11 |
976     //  -----------
977     //->
978     // | 0 10 |
979     static immutable d = [0, 10];
980     assert(iota(2, 2, 3).diagonal == d);
981 }
982 
983 /// 3D, subdiagonal
984 @safe @nogc pure nothrow version(mir_test) unittest
985 {
986     //  -----------
987     // |  0   1  2 |
988     // |  3   4  5 |
989     //  - - - - - -
990     // |  6   7  8 |
991     // |  9  10 11 |
992     //  -----------
993     //->
994     // | 1 11 |
995     static immutable d = [1, 11];
996     auto a = iota(2, 2, 3).canonical;
997     a.popFront!2;
998     assert(a.diagonal == d);
999 }
1000 
1001 /// 3D, diagonal plain
1002 @nogc @safe pure nothrow
1003 version(mir_test) unittest
1004 {
1005     //  -----------
1006     // |  0   1  2 |
1007     // |  3   4  5 |
1008     // |  6   7  8 |
1009     //  - - - - - -
1010     // |  9  10 11 |
1011     // | 12  13 14 |
1012     // | 15  16 17 |
1013     //  - - - - - -
1014     // | 18  20 21 |
1015     // | 22  23 24 |
1016     // | 24  25 26 |
1017     //  -----------
1018     //->
1019     //  -----------
1020     // |  0   4  8 |
1021     // |  9  13 17 |
1022     // | 18  23 26 |
1023     //  -----------
1024 
1025     static immutable d =
1026         [[ 0,  4,  8],
1027          [ 9, 13, 17],
1028          [18, 22, 26]];
1029 
1030     auto slice = iota(3, 3, 3)
1031         .pack!2
1032         .evertPack
1033         .diagonal
1034         .evertPack;
1035 
1036     assert(slice == d);
1037 }
1038 
1039 /++
1040 Returns a 1-dimensional slice over the main antidiagonal of an 2D-dimensional slice.
1041 `antidiagonal` can be generalized with other selectors such as
1042 $(LREF blocks) (diagonal blocks) and $(LREF windows) (multi-diagonal slice).
1043 
1044 It runs from the top right corner to the bottom left corner.
1045 
1046 Pseudo_code:
1047 ------
1048 auto antidiagonal = slice.dropToHypercube.reversed!1.diagonal;
1049 ------
1050 
1051 Params:
1052     slice = input slice
1053 Returns:
1054     1-dimensional slice composed of antidiagonal elements.
1055 See_also: $(LREF diagonal)
1056 +/
1057 Slice!(Iterator, 1, Universal)
1058     antidiagonal
1059     (Iterator, size_t N, SliceKind kind)
1060     (Slice!(Iterator, N, kind) slice)
1061     if (N == 2)
1062 {
1063     import mir.ndslice.dynamic : dropToHypercube, reversed;
1064     return slice.dropToHypercube.reversed!1.diagonal;
1065 }
1066 
1067 ///
1068 @safe @nogc pure nothrow version(mir_test) unittest
1069 {
1070     //  -----
1071     // | 0 1 |
1072     // | 2 3 |
1073     //  -----
1074     //->
1075     // | 1 2 |
1076     static immutable c = [1, 2];
1077     import std.stdio;
1078     assert(iota(2, 2).antidiagonal == c);
1079 }
1080 
1081 ///
1082 @safe @nogc pure nothrow version(mir_test) unittest
1083 {
1084     //  -------
1085     // | 0 1 2 |
1086     // | 3 4 5 |
1087     //  -------
1088     //->
1089     // | 1 3 |
1090     static immutable d = [1, 3];
1091     assert(iota(2, 3).antidiagonal == d);
1092 }
1093 
1094 /++
1095 Returns an n-dimensional slice of n-dimensional non-overlapping blocks.
1096 `blocks` can be generalized with other selectors.
1097 For example, `blocks` in combination with $(LREF diagonal) can be used to get a slice of diagonal blocks.
1098 For overlapped blocks, combine $(LREF windows) with $(SUBREF dynamic, strided).
1099 
1100 Params:
1101     N = dimension count
1102     slice = slice to be split into blocks
1103     rlengths_ = dimensions of block, residual blocks are ignored
1104 Returns:
1105     packed `N`-dimensional slice composed of `N`-dimensional slices
1106 
1107 See_also: $(SUBREF chunks, ._chunks)
1108 +/
1109 Slice!(SliceIterator!(Iterator, N, N == 1 ? Universal : min(kind, Canonical)), N, Universal)
1110     blocks
1111     (Iterator, size_t N, SliceKind kind)
1112     (Slice!(Iterator, N, kind) slice, size_t[N] rlengths_...)
1113 in
1114 {
1115     foreach (i, length; rlengths_)
1116         assert(length > 0, "length of dimension = " ~ i.stringof ~ " must be positive"
1117             ~ tailErrorMessage!());
1118 }
1119 do
1120 {
1121     size_t[N] lengths;
1122     size_t[N] rlengths = rlengths_;
1123     sizediff_t[N] strides;
1124     foreach (dimension; Iota!N)
1125         lengths[dimension] = slice._lengths[dimension] / rlengths[dimension];
1126     auto rstrides = slice.strides;
1127     foreach (i; Iota!N)
1128     {
1129         strides[i] = rstrides[i];
1130         if (lengths[i]) //do not remove `if (...)`
1131             strides[i] *= rlengths[i];
1132     }
1133     return typeof(return)(
1134         lengths,
1135         strides,
1136         typeof(return).Iterator(
1137             rlengths,
1138             rstrides[0 .. typeof(return).DeepElement.S],
1139             slice._iterator));
1140 }
1141 
1142 ///
1143 pure nothrow version(mir_test) unittest
1144 {
1145     import mir.ndslice.slice;
1146     import mir.ndslice.allocation;
1147     auto slice = slice!int(5, 8);
1148     auto blocks = slice.blocks(2, 3);
1149     int i;
1150     foreach (blocksRaw; blocks)
1151         foreach (block; blocksRaw)
1152             block[] = ++i;
1153 
1154     assert(blocks ==
1155         [[[[1, 1, 1], [1, 1, 1]],
1156           [[2, 2, 2], [2, 2, 2]]],
1157          [[[3, 3, 3], [3, 3, 3]],
1158           [[4, 4, 4], [4, 4, 4]]]]);
1159 
1160     assert(    slice ==
1161         [[1, 1, 1,  2, 2, 2,  0, 0],
1162          [1, 1, 1,  2, 2, 2,  0, 0],
1163 
1164          [3, 3, 3,  4, 4, 4,  0, 0],
1165          [3, 3, 3,  4, 4, 4,  0, 0],
1166 
1167          [0, 0, 0,  0, 0, 0,  0, 0]]);
1168 }
1169 
1170 /// Diagonal blocks
1171 @safe pure nothrow version(mir_test) unittest
1172 {
1173     import mir.ndslice.slice;
1174     import mir.ndslice.allocation;
1175     auto slice = slice!int(5, 8);
1176     auto blocks = slice.blocks(2, 3);
1177     auto diagonalBlocks = blocks.diagonal.unpack;
1178 
1179     diagonalBlocks[0][] = 1;
1180     diagonalBlocks[1][] = 2;
1181 
1182     assert(diagonalBlocks ==
1183         [[[1, 1, 1], [1, 1, 1]],
1184          [[2, 2, 2], [2, 2, 2]]]);
1185 
1186     assert(blocks ==
1187         [[[[1, 1, 1], [1, 1, 1]],
1188           [[0, 0, 0], [0, 0, 0]]],
1189          [[[0, 0, 0], [0, 0, 0]],
1190           [[2, 2, 2], [2, 2, 2]]]]);
1191 
1192     assert(slice ==
1193         [[1, 1, 1,  0, 0, 0,  0, 0],
1194          [1, 1, 1,  0, 0, 0,  0, 0],
1195 
1196          [0, 0, 0,  2, 2, 2,  0, 0],
1197          [0, 0, 0,  2, 2, 2,  0, 0],
1198 
1199          [0, 0, 0, 0, 0, 0, 0, 0]]);
1200 }
1201 
1202 /// Matrix divided into vertical blocks
1203 @safe pure version(mir_test) unittest
1204 {
1205     import mir.ndslice.allocation;
1206     import mir.ndslice.slice;
1207     auto slice = slice!int(5, 13);
1208     auto blocks = slice
1209         .pack!1
1210         .evertPack
1211         .blocks(3)
1212         .unpack;
1213 
1214     int i;
1215     foreach (block; blocks)
1216         block[] = ++i;
1217 
1218     assert(slice ==
1219         [[1, 1, 1,  2, 2, 2,  3, 3, 3,  4, 4, 4,  0],
1220          [1, 1, 1,  2, 2, 2,  3, 3, 3,  4, 4, 4,  0],
1221          [1, 1, 1,  2, 2, 2,  3, 3, 3,  4, 4, 4,  0],
1222          [1, 1, 1,  2, 2, 2,  3, 3, 3,  4, 4, 4,  0],
1223          [1, 1, 1,  2, 2, 2,  3, 3, 3,  4, 4, 4,  0]]);
1224 }
1225 
1226 /++
1227 Returns an n-dimensional slice of n-dimensional overlapping windows.
1228 `windows` can be generalized with other selectors.
1229 For example, `windows` in combination with $(LREF diagonal) can be used to get a multi-diagonal slice.
1230 
1231 Params:
1232     N = dimension count
1233     slice = slice to be iterated
1234     rlengths = dimensions of windows
1235 Returns:
1236     packed `N`-dimensional slice composed of `N`-dimensional slices
1237 +/
1238 Slice!(SliceIterator!(Iterator, N, N == 1 ? kind : min(kind, Canonical)), N, Universal)
1239     windows
1240     (Iterator, size_t N, SliceKind kind)
1241     (Slice!(Iterator, N, kind) slice, size_t[N] rlengths...)
1242 in
1243 {
1244     foreach (i, length; rlengths)
1245         assert(length > 0, "length of dimension = " ~ i.stringof ~ " must be positive"
1246             ~ tailErrorMessage!());
1247 }
1248 do
1249 {
1250     size_t[N] rls = rlengths;
1251     size_t[N] lengths;
1252     foreach (dimension; Iota!N)
1253         lengths[dimension] = slice._lengths[dimension] >= rls[dimension] ?
1254             slice._lengths[dimension] - rls[dimension] + 1 : 0;
1255     auto rstrides = slice.strides;
1256     static if (typeof(return).DeepElement.S)
1257         return typeof(return)(
1258             lengths,
1259             rstrides,
1260             typeof(return).Iterator(
1261                 rls,
1262                 rstrides[0 .. typeof(return).DeepElement.S],
1263                 slice._iterator));
1264     else
1265         return typeof(return)(
1266             lengths,
1267             rstrides,
1268             typeof(return).Iterator(
1269                 rls,
1270                 slice._iterator));
1271 }
1272 
1273 ///
1274 @safe pure nothrow
1275 version(mir_test) unittest
1276 {
1277     import mir.ndslice.allocation;
1278     import mir.ndslice.slice;
1279     auto slice = slice!int(5, 8);
1280     auto windows = slice.windows(2, 3);
1281 
1282     int i;
1283     foreach (windowsRaw; windows)
1284         foreach (window; windowsRaw)
1285             ++window[];
1286 
1287     assert(slice ==
1288         [[1,  2,  3, 3, 3, 3,  2,  1],
1289 
1290          [2,  4,  6, 6, 6, 6,  4,  2],
1291          [2,  4,  6, 6, 6, 6,  4,  2],
1292          [2,  4,  6, 6, 6, 6,  4,  2],
1293 
1294          [1,  2,  3, 3, 3, 3,  2,  1]]);
1295 }
1296 
1297 ///
1298 @safe pure nothrow version(mir_test) unittest
1299 {
1300     import mir.ndslice.allocation;
1301     import mir.ndslice.slice;
1302     auto slice = slice!int(5, 8);
1303     auto windows = slice.windows(2, 3);
1304     windows[1, 2][] = 1;
1305     windows[1, 2][0, 1] += 1;
1306     windows.unpack[1, 2, 0, 1] += 1;
1307 
1308     assert(slice ==
1309         [[0, 0,  0, 0, 0,  0, 0, 0],
1310 
1311          [0, 0,  1, 3, 1,  0, 0, 0],
1312          [0, 0,  1, 1, 1,  0, 0, 0],
1313 
1314          [0, 0,  0, 0, 0,  0, 0, 0],
1315          [0, 0,  0, 0, 0,  0, 0, 0]]);
1316 }
1317 
1318 /// Multi-diagonal matrix
1319 @safe pure nothrow version(mir_test) unittest
1320 {
1321     import mir.ndslice.allocation;
1322     import mir.ndslice.slice;
1323     auto slice = slice!int(8, 8);
1324     auto windows = slice.windows(3, 3);
1325 
1326     auto multidiagonal = windows
1327         .diagonal
1328         .unpack;
1329     foreach (window; multidiagonal)
1330         window[] += 1;
1331 
1332     assert(slice ==
1333         [[ 1, 1, 1,  0, 0, 0, 0, 0],
1334          [ 1, 2, 2, 1,  0, 0, 0, 0],
1335          [ 1, 2, 3, 2, 1,  0, 0, 0],
1336          [0,  1, 2, 3, 2, 1,  0, 0],
1337          [0, 0,  1, 2, 3, 2, 1,  0],
1338          [0, 0, 0,  1, 2, 3, 2, 1],
1339          [0, 0, 0, 0,  1, 2, 2, 1],
1340          [0, 0, 0, 0, 0,  1, 1, 1]]);
1341 }
1342 
1343 /// Sliding window over matrix columns
1344 @safe pure nothrow version(mir_test) unittest
1345 {
1346     import mir.ndslice.allocation;
1347     import mir.ndslice.slice;
1348     auto slice = slice!int(5, 8);
1349     auto windows = slice
1350         .pack!1
1351         .evertPack
1352         .windows(3)
1353         .unpack;
1354 
1355     foreach (window; windows)
1356         window[] += 1;
1357 
1358     assert(slice ==
1359         [[1,  2,  3, 3, 3, 3,  2,  1],
1360          [1,  2,  3, 3, 3, 3,  2,  1],
1361          [1,  2,  3, 3, 3, 3,  2,  1],
1362          [1,  2,  3, 3, 3, 3,  2,  1],
1363          [1,  2,  3, 3, 3, 3,  2,  1]]);
1364 }
1365 
1366 /// Overlapping blocks using windows
1367 @safe pure nothrow version(mir_test) unittest
1368 {
1369     //  ----------------
1370     // |  0  1  2  3  4 |
1371     // |  5  6  7  8  9 |
1372     // | 10 11 12 13 14 |
1373     // | 15 16 17 18 19 |
1374     // | 20 21 22 23 24 |
1375     //  ----------------
1376     //->
1377     //  ---------------------
1378     // |  0  1  2 |  2  3  4 |
1379     // |  5  6  7 |  7  8  9 |
1380     // | 10 11 12 | 12 13 14 |
1381     // | - - - - - - - - - - |
1382     // | 10 11 13 | 12 13 14 |
1383     // | 15 16 17 | 17 18 19 |
1384     // | 20 21 22 | 22 23 24 |
1385     //  ---------------------
1386 
1387     import mir.ndslice.slice;
1388     import mir.ndslice.dynamic : strided;
1389 
1390     auto overlappingBlocks = iota(5, 5)
1391         .windows(3, 3)
1392         .universal
1393         .strided!(0, 1)(2, 2);
1394 
1395     assert(overlappingBlocks ==
1396             [[[[ 0,  1,  2], [ 5,  6,  7], [10, 11, 12]],
1397               [[ 2,  3,  4], [ 7,  8,  9], [12, 13, 14]]],
1398              [[[10, 11, 12], [15, 16, 17], [20, 21, 22]],
1399               [[12, 13, 14], [17, 18, 19], [22, 23, 24]]]]);
1400 }
1401 
1402 version(mir_test) unittest
1403 {
1404     auto w = iota(9, 9).windows(3, 3);
1405     assert(w.front == w[0]);
1406 }
1407 
1408 /++
1409 Error codes for $(LREF reshape).
1410 +/
1411 enum ReshapeError
1412 {
1413     /// No error
1414     none,
1415     /// Slice should be not empty
1416     empty,
1417     /// Total element count should be the same
1418     total,
1419     /// Structure is incompatible with new shape
1420     incompatible,
1421 }
1422 
1423 /++
1424 Returns a new slice for the same data with different dimensions.
1425 
1426 Params:
1427     slice = slice to be reshaped
1428     rlengths = list of new dimensions. One of the lengths can be set to `-1`.
1429         In this case, the corresponding dimension is inferable.
1430     err = $(LREF ReshapeError) code
1431 Returns:
1432     reshaped slice
1433 +/
1434 Slice!(Iterator, M, kind) reshape
1435         (Iterator, size_t N, SliceKind kind, size_t M)
1436         (Slice!(Iterator, N, kind) slice, ptrdiff_t[M] rlengths, ref int err)
1437 {
1438     static if (kind == Canonical)
1439     {
1440         auto r = slice.universal.reshape(rlengths, err);
1441         assert(err || r._strides[$-1] == 1);
1442         r._strides[$-1] = 1;
1443         return r.assumeCanonical;
1444     }
1445     else
1446     {
1447         alias Ret = typeof(return);
1448         auto structure = Ret._Structure.init;
1449         alias lengths = structure[0];
1450         foreach (i; Iota!M)
1451             lengths[i] = rlengths[i];
1452 
1453         /// Code size optimization
1454         immutable size_t eco = slice.elementCount;
1455         size_t ecn = lengths[0 .. rlengths.length].iota.elementCount;
1456         if (eco == 0)
1457         {
1458             err = ReshapeError.empty;
1459             goto R;
1460         }
1461         foreach (i; Iota!M)
1462             if (lengths[i] == -1)
1463             {
1464                 ecn = -ecn;
1465                 lengths[i] = eco / ecn;
1466                 ecn *= lengths[i];
1467                 break;
1468             }
1469         if (eco != ecn)
1470         {
1471             err = ReshapeError.total;
1472             goto R;
1473         }
1474         static if (kind == Universal)
1475         {
1476             for (size_t oi, ni, oj, nj; oi < N && ni < M; oi = oj, ni = nj)
1477             {
1478                 size_t op = slice._lengths[oj++];
1479                 size_t np =        lengths[nj++];
1480 
1481                 for (;;)
1482                 {
1483                     if (op < np)
1484                         op *= slice._lengths[oj++];
1485                     if (op > np)
1486                         np *=        lengths[nj++];
1487                     if (op == np)
1488                         break;
1489                 }
1490                 while (oj < N && slice._lengths[oj] == 1) oj++;
1491                 while (nj < M        &&        lengths[nj] == 1) nj++;
1492 
1493                 for (size_t l = oi, r = oi + 1; r < oj; r++)
1494                     if (slice._lengths[r] != 1)
1495                     {
1496                         if (slice._strides[l] != slice._lengths[r] * slice._strides[r])
1497                         {
1498                             err = ReshapeError.incompatible;
1499                             goto R;
1500                         }
1501                         l = r;
1502                     }
1503                 assert((oi == N) == (ni == M));
1504 
1505                 structure[1][nj - 1] = slice._strides[oj - 1];
1506                 foreach_reverse (i; ni .. nj - 1)
1507                     structure[1][i] = lengths[i + 1] * structure[1][i + 1];
1508             }
1509         }
1510         foreach (i; Iota!(M, Ret.N))
1511             lengths[i] = slice._lengths[i + N - M];
1512         static if (M < Ret.S)
1513         foreach (i; Iota!(M, Ret.S))
1514             structure[1][i] = slice._strides[i + N - M];
1515         err = 0;
1516         return Ret(structure, slice._iterator);
1517     R:
1518         return Ret(structure, slice._iterator.init);
1519     }
1520 }
1521 
1522 ///
1523 @safe nothrow pure
1524 version(mir_test) unittest
1525 {
1526     import mir.ndslice.dynamic : allReversed;
1527     int err;
1528     auto slice = iota(3, 4)
1529         .universal
1530         .allReversed
1531         .reshape([-1, 3], err);
1532     assert(err == 0);
1533     assert(slice ==
1534         [[11, 10, 9],
1535          [ 8,  7, 6],
1536          [ 5,  4, 3],
1537          [ 2,  1, 0]]);
1538 }
1539 
1540 /// Reshaping with memory allocation
1541 @safe pure version(mir_test) unittest
1542 {
1543     import mir.ndslice.slice: sliced;
1544     import mir.ndslice.allocation: slice;
1545     import mir.ndslice.dynamic : reversed;
1546 
1547     auto reshape2(S, size_t M)(S sl, ptrdiff_t[M] lengths)
1548     {
1549         int err;
1550         // Tries to reshape without allocation
1551         auto ret = sl.reshape(lengths, err);
1552         if (!err)
1553             return ret;
1554         if (err == ReshapeError.incompatible)
1555             // allocates, flattens, reshapes with `sliced`, converts to universal kind
1556             return sl.slice.flattened.sliced(cast(size_t[M])lengths).universal;
1557         throw new Exception("total elements count is different or equals to zero");
1558     }
1559 
1560     auto sl = iota!int(3, 4)
1561         .slice
1562         .universal
1563         .reversed!0;
1564 
1565     assert(reshape2(sl, [4, 3]) ==
1566         [[ 8, 9, 10],
1567          [11, 4,  5],
1568          [ 6, 7,  0],
1569          [ 1, 2,  3]]);
1570 }
1571 
1572 nothrow @safe pure version(mir_test) unittest
1573 {
1574     import mir.ndslice.dynamic : allReversed;
1575     auto slice = iota(1, 1, 3, 2, 1, 2, 1).universal.allReversed;
1576     int err;
1577     assert(slice.reshape([1, -1, 1, 1, 3, 1], err) ==
1578         [[[[[[11], [10], [9]]]],
1579           [[[[ 8], [ 7], [6]]]],
1580           [[[[ 5], [ 4], [3]]]],
1581           [[[[ 2], [ 1], [0]]]]]]);
1582     assert(err == 0);
1583 }
1584 
1585 // Issue 15919
1586 nothrow @nogc @safe pure
1587 version(mir_test) unittest
1588 {
1589     int err;
1590     assert(iota(3, 4, 5, 6, 7).pack!2.reshape([4, 3, 5], err)[0, 0, 0].shape == cast(size_t[2])[6, 7]);
1591     assert(err == 0);
1592 }
1593 
1594 nothrow @nogc @safe pure version(mir_test) unittest
1595 {
1596     import mir.ndslice.slice;
1597 
1598     int err;
1599     auto e = iota(1);
1600     // resize to the wrong dimension
1601     auto s = e.reshape([2], err);
1602     assert(err == ReshapeError.total);
1603     e.popFront;
1604     // test with an empty slice
1605     e.reshape([1], err);
1606     assert(err == ReshapeError.empty);
1607 }
1608 
1609 nothrow @nogc @safe pure
1610 version(mir_test) unittest
1611 {
1612     auto pElements = iota(3, 4, 5, 6, 7)
1613         .pack!2
1614         .flattened;
1615     assert(pElements[0][0] == iota(7));
1616     assert(pElements[$-1][$-1] == iota([7], 2513));
1617 }
1618 
1619 /++
1620 A contiguous 1-dimensional slice of all elements of a slice.
1621 `flattened` iterates existing data.
1622 The order of elements is preserved.
1623 
1624 `flattened` can be generalized with other selectors.
1625 
1626 Params:
1627     slice = slice to be iterated
1628 Returns:
1629     contiguous 1-dimensional slice of elements of the `slice`
1630 +/
1631 Slice!(FlattenedIterator!(Iterator, N, kind))
1632     flattened
1633     (Iterator, size_t N, SliceKind kind)
1634     (Slice!(Iterator, N, kind) slice)
1635     if (N != 1 && kind != Contiguous)
1636 {
1637     import core.lifetime: move;
1638     size_t[typeof(return).N] lengths;
1639     sizediff_t[typeof(return)._iterator._indices.length] indices;
1640     lengths[0] = slice.elementCount;
1641     return typeof(return)(lengths, FlattenedIterator!(Iterator, N, kind)(indices, slice.move));
1642 }
1643 
1644 /// ditto
1645 Slice!Iterator
1646     flattened
1647     (Iterator, size_t N)
1648     (Slice!(Iterator, N) slice)
1649 {
1650     static if (N == 1)
1651     {
1652         return slice;
1653     }
1654     else
1655     {
1656         import core.lifetime: move;
1657         size_t[typeof(return).N] lengths;
1658         lengths[0] = slice.elementCount;
1659         return typeof(return)(lengths, slice._iterator.move);
1660     }
1661 }
1662 
1663 /// ditto
1664 Slice!(StrideIterator!Iterator)
1665     flattened
1666     (Iterator)
1667     (Slice!(Iterator,  1, Universal) slice)
1668 {
1669     import core.lifetime: move;
1670     return slice.move.hideStride;
1671 }
1672 
1673 version(mir_test) unittest
1674 {
1675     import mir.ndslice.allocation: slice;
1676     auto sl1 = iota(2, 3).slice.universal.pack!1.flattened;
1677     auto sl2 = iota(2, 3).slice.canonical.pack!1.flattened;
1678     auto sl3 = iota(2, 3).slice.pack!1.flattened;
1679 }
1680 
1681 /// Regular slice
1682 @safe @nogc pure nothrow version(mir_test) unittest
1683 {
1684     assert(iota(4, 5).flattened == iota(20));
1685     assert(iota(4, 5).canonical.flattened == iota(20));
1686     assert(iota(4, 5).universal.flattened == iota(20));
1687 }
1688 
1689 @safe @nogc pure nothrow version(mir_test) unittest
1690 {
1691     assert(iota(4).flattened == iota(4));
1692     assert(iota(4).canonical.flattened == iota(4));
1693     assert(iota(4).universal.flattened == iota(4));
1694 }
1695 
1696 /// Packed slice
1697 @safe @nogc pure nothrow version(mir_test) unittest
1698 {
1699     import mir.ndslice.slice;
1700     import mir.ndslice.dynamic;
1701     assert(iota(3, 4, 5, 6, 7).pack!2.flattened[1] == iota([6, 7], 6 * 7));
1702 }
1703 
1704 /// Properties
1705 @safe pure nothrow version(mir_test) unittest
1706 {
1707     auto elems = iota(3, 4).universal.flattened;
1708 
1709     elems.popFrontExactly(2);
1710     assert(elems.front == 2);
1711     /// `_index` is available only for canonical and universal ndslices.
1712     assert(elems._iterator._indices == [0, 2]);
1713 
1714     elems.popBackExactly(2);
1715     assert(elems.back == 9);
1716     assert(elems.length == 8);
1717 }
1718 
1719 /// Index property
1720 @safe pure nothrow version(mir_test) unittest
1721 {
1722     import mir.ndslice.slice;
1723     auto slice = new long[20].sliced(5, 4);
1724 
1725     for (auto elems = slice.universal.flattened; !elems.empty; elems.popFront)
1726     {
1727         ptrdiff_t[2] index = elems._iterator._indices;
1728         elems.front = index[0] * 10 + index[1] * 3;
1729     }
1730     assert(slice ==
1731         [[ 0,  3,  6,  9],
1732          [10, 13, 16, 19],
1733          [20, 23, 26, 29],
1734          [30, 33, 36, 39],
1735          [40, 43, 46, 49]]);
1736 }
1737 
1738 @safe pure nothrow version(mir_test) unittest
1739 {
1740     auto elems = iota(3, 4).universal.flattened;
1741     assert(elems.front == 0);
1742     assert(elems.save[1] == 1);
1743 }
1744 
1745 /++
1746 Random access and slicing
1747 +/
1748 nothrow version(mir_test) unittest
1749 {
1750     import mir.ndslice.allocation: slice;
1751     import mir.ndslice.slice: sliced;
1752 
1753     auto elems = iota(4, 5).slice.flattened;
1754 
1755     elems = elems[11 .. $ - 2];
1756 
1757     assert(elems.length == 7);
1758     assert(elems.front == 11);
1759     assert(elems.back == 17);
1760 
1761     foreach (i; 0 .. 7)
1762         assert(elems[i] == i + 11);
1763 
1764     // assign an element
1765     elems[2 .. 6] = -1;
1766     assert(elems[2 .. 6] == repeat(-1, 4));
1767 
1768     // assign an array
1769     static ar = [-1, -2, -3, -4];
1770     elems[2 .. 6] = ar;
1771     assert(elems[2 .. 6] == ar);
1772 
1773     // assign a slice
1774     ar[] *= 2;
1775     auto sl = ar.sliced(ar.length);
1776     elems[2 .. 6] = sl;
1777     assert(elems[2 .. 6] == sl);
1778 }
1779 
1780 @safe @nogc pure nothrow version(mir_test) unittest
1781 {
1782     import mir.ndslice.dynamic : allReversed;
1783 
1784     auto slice = iota(3, 4, 5);
1785 
1786     foreach (ref e; slice.universal.flattened.retro)
1787     {
1788         //...
1789     }
1790 
1791     foreach_reverse (ref e; slice.universal.flattened)
1792     {
1793         //...
1794     }
1795 
1796     foreach (ref e; slice.universal.allReversed.flattened)
1797     {
1798         //...
1799     }
1800 }
1801 
1802 @safe @nogc pure nothrow version(mir_test) unittest
1803 {
1804     import std.range.primitives : isRandomAccessRange, hasSlicing;
1805     auto elems = iota(4, 5).flattened;
1806     static assert(isRandomAccessRange!(typeof(elems)));
1807     static assert(hasSlicing!(typeof(elems)));
1808 }
1809 
1810 // Checks strides
1811 @safe @nogc pure nothrow version(mir_test) unittest
1812 {
1813     import mir.ndslice.dynamic;
1814     import std.range.primitives : isRandomAccessRange;
1815     auto elems = iota(4, 5).universal.everted.flattened;
1816     static assert(isRandomAccessRange!(typeof(elems)));
1817 
1818     elems = elems[11 .. $ - 2];
1819     auto elems2 = elems;
1820     foreach (i; 0 .. 7)
1821     {
1822         assert(elems[i] == elems2.front);
1823         elems2.popFront;
1824     }
1825 }
1826 
1827 @safe @nogc pure nothrow version(mir_test) unittest
1828 {
1829     import mir.ndslice.slice;
1830     import mir.ndslice.dynamic;
1831     import std.range.primitives : isRandomAccessRange, hasLength;
1832 
1833     auto range = (3 * 4 * 5 * 6 * 7).iota;
1834     auto slice0 = range.sliced(3, 4, 5, 6, 7).universal;
1835     auto slice1 = slice0.transposed!(2, 1).pack!2;
1836     auto elems0 = slice0.flattened;
1837     auto elems1 = slice1.flattened;
1838 
1839     foreach (S; AliasSeq!(typeof(elems0), typeof(elems1)))
1840     {
1841         static assert(isRandomAccessRange!S);
1842         static assert(hasLength!S);
1843     }
1844 
1845     assert(elems0.length == slice0.elementCount);
1846     assert(elems1.length == 5 * 4 * 3);
1847 
1848     auto elems2 = elems1;
1849     foreach (q; slice1)
1850         foreach (w; q)
1851             foreach (e; w)
1852             {
1853                 assert(!elems2.empty);
1854                 assert(e == elems2.front);
1855                 elems2.popFront;
1856             }
1857     assert(elems2.empty);
1858 
1859     elems0.popFront();
1860     elems0.popFrontExactly(slice0.elementCount - 14);
1861     assert(elems0.length == 13);
1862     assert(elems0 == range[slice0.elementCount - 13 .. slice0.elementCount]);
1863 
1864     foreach (elem; elems0) {}
1865 }
1866 
1867 // Issue 15549
1868 version(mir_test) unittest
1869 {
1870     import std.range.primitives;
1871     import mir.ndslice.allocation;
1872     alias A = typeof(iota(1, 2, 3, 4).pack!1);
1873     static assert(isRandomAccessRange!A);
1874     static assert(hasLength!A);
1875     static assert(hasSlicing!A);
1876     alias B = typeof(slice!int(1, 2, 3, 4).pack!3);
1877     static assert(isRandomAccessRange!B);
1878     static assert(hasLength!B);
1879     static assert(hasSlicing!B);
1880 }
1881 
1882 // Issue 16010
1883 version(mir_test) unittest
1884 {
1885     auto s = iota(3, 4).flattened;
1886     foreach (_; 0 .. s.length)
1887         s = s[1 .. $];
1888 }
1889 
1890 /++
1891 Returns a slice, the elements of which are equal to the initial multidimensional index value.
1892 For a flattened (contiguous) index, see $(LREF iota).
1893 
1894 Params:
1895     N = dimension count
1896     lengths = list of dimension lengths
1897 Returns:
1898     `N`-dimensional slice composed of indices
1899 See_also: $(LREF iota)
1900 +/
1901 Slice!(FieldIterator!(ndIotaField!N), N)
1902     ndiota
1903     (size_t N)
1904     (size_t[N] lengths...)
1905     if (N)
1906 {
1907     return FieldIterator!(ndIotaField!N)(0, ndIotaField!N(lengths[1 .. $])).sliced(lengths);
1908 }
1909 
1910 ///
1911 @safe pure nothrow @nogc version(mir_test) unittest
1912 {
1913     auto slice = ndiota(2, 3);
1914     static immutable array =
1915         [[[0, 0], [0, 1], [0, 2]],
1916          [[1, 0], [1, 1], [1, 2]]];
1917 
1918     assert(slice == array);
1919 }
1920 
1921 ///
1922 @safe pure nothrow version(mir_test) unittest
1923 {
1924     auto im = ndiota(7, 9);
1925 
1926     assert(im[2, 1] == [2, 1]);
1927 
1928     //slicing works correctly
1929     auto cm = im[1 .. $, 4 .. $];
1930     assert(cm[2, 1] == [3, 5]);
1931 }
1932 
1933 version(mir_test) unittest
1934 {
1935     auto r = ndiota(1);
1936     auto d = r.front;
1937     r.popFront;
1938     import std.range.primitives;
1939     static assert(isRandomAccessRange!(typeof(r)));
1940 }
1941 
1942 /++
1943 Evenly spaced numbers over a specified interval.
1944 
1945 Params:
1946     T = floating point or complex numbers type
1947     lengths = list of dimension lengths. Each length must be greater then 1.
1948     intervals = list of [start, end] pairs.
1949 Returns:
1950     `n`-dimensional grid of evenly spaced numbers over specified intervals.
1951 See_also: $(LREF)
1952 +/
1953 auto linspace(T, size_t N)(size_t[N] lengths, T[2][N] intervals...)
1954     if (N && (isFloatingPoint!T || isComplex!T))
1955 {
1956     Repeat!(N, LinspaceField!T) fields;
1957     foreach(i; Iota!N)
1958     {
1959         assert(lengths[i] > 1, "linspace: all lengths must be greater then 1.");
1960         fields[i] = LinspaceField!T(lengths[i], intervals[i][0], intervals[i][1]);
1961     }
1962     static if (N == 1)
1963         return slicedField(fields);
1964     else
1965         return cartesian(fields);
1966 }
1967 
1968 // example from readme
1969 version(mir_test) unittest
1970 {
1971     import mir.ndslice;
1972     // import std.stdio: writefln;
1973 
1974     enum fmt = "%(%(%.2f %)\n%)\n";
1975 
1976     auto a = magic(5).as!float;
1977     // writefln(fmt, a);
1978 
1979     auto b = linspace!float([5, 5], [1f, 2f], [0f, 1f]).map!"a * a + b";
1980     // writefln(fmt, b);
1981 
1982     auto c = slice!float(5, 5);
1983     c[] = transposed(a + b / 2);
1984 }
1985 
1986 /// 1D
1987 @safe pure nothrow
1988 version(mir_test) unittest
1989 {
1990     auto s = linspace!double([5], [1.0, 2.0]);
1991     assert(s == [1.0, 1.25, 1.5, 1.75, 2.0]);
1992 
1993     // reverse order
1994     assert(linspace!double([5], [2.0, 1.0]) == s.retro);
1995 
1996     // remove endpoint
1997     s.popBack;
1998     assert(s == [1.0, 1.25, 1.5, 1.75]);
1999 }
2000 
2001 /// 2D
2002 @safe pure nothrow
2003 version(mir_test) unittest
2004 {
2005     import mir.functional: refTuple;
2006 
2007     auto s = linspace!double([5, 3], [1.0, 2.0], [0.0, 1.0]);
2008 
2009     assert(s == [
2010         [refTuple(1.00, 0.00), refTuple(1.00, 0.5), refTuple(1.00, 1.0)],
2011         [refTuple(1.25, 0.00), refTuple(1.25, 0.5), refTuple(1.25, 1.0)],
2012         [refTuple(1.50, 0.00), refTuple(1.50, 0.5), refTuple(1.50, 1.0)],
2013         [refTuple(1.75, 0.00), refTuple(1.75, 0.5), refTuple(1.75, 1.0)],
2014         [refTuple(2.00, 0.00), refTuple(2.00, 0.5), refTuple(2.00, 1.0)],
2015         ]);
2016 
2017     assert(s.map!"a * b" == [
2018         [0.0, 0.500, 1.00],
2019         [0.0, 0.625, 1.25],
2020         [0.0, 0.750, 1.50],
2021         [0.0, 0.875, 1.75],
2022         [0.0, 1.000, 2.00],
2023         ]);
2024 }
2025 
2026 /// Complex numbers
2027 @safe pure nothrow
2028 version(mir_test) unittest
2029 {
2030     auto s = linspace!cdouble([3], [1.0 + 0i, 2.0 + 4i]);
2031     assert(s == [1.0 + 0i, 1.5 + 2i, 2.0 + 4i]);
2032 }
2033 
2034 /++
2035 Returns a slice with identical elements.
2036 `RepeatSlice` stores only single value.
2037 Params:
2038     lengths = list of dimension lengths
2039 Returns:
2040     `n`-dimensional slice composed of identical values, where `n` is dimension count.
2041 +/
2042 Slice!(FieldIterator!(RepeatField!T), M, Universal)
2043     repeat(T, size_t M)(T value, size_t[M] lengths...) @trusted
2044     if (M && !isSlice!T)
2045 {
2046     size_t[M] ls = lengths;
2047     return typeof(return)(
2048         ls,
2049         sizediff_t[M].init,
2050         typeof(return).Iterator(0, RepeatField!T(cast(RepeatField!T.UT) value)));
2051 }
2052 
2053 /// ditto
2054 Slice!(SliceIterator!(Iterator, N, kind), M, Universal)
2055     repeat
2056     (SliceKind kind, size_t N, Iterator, size_t M)
2057     (Slice!(Iterator, N, kind) slice, size_t[M] lengths...)
2058     if (M)
2059 {
2060     import core.lifetime: move;
2061     size_t[M] ls = lengths;
2062     return typeof(return)(
2063         ls,
2064         sizediff_t[M].init,
2065         typeof(return).Iterator(
2066             slice._structure,
2067             move(slice._iterator)));
2068 }
2069 
2070 ///
2071 @safe pure nothrow
2072 version(mir_test) unittest
2073 {
2074     auto sl = iota(3).repeat(4);
2075     assert(sl == [[0, 1, 2],
2076                   [0, 1, 2],
2077                   [0, 1, 2],
2078                   [0, 1, 2]]);
2079 }
2080 
2081 ///
2082 @safe pure nothrow version(mir_test) unittest
2083 {
2084     import mir.ndslice.dynamic : transposed;
2085 
2086     auto sl = iota(3)
2087         .repeat(4)
2088         .unpack
2089         .universal
2090         .transposed;
2091 
2092     assert(sl == [[0, 0, 0, 0],
2093                   [1, 1, 1, 1],
2094                   [2, 2, 2, 2]]);
2095 }
2096 
2097 ///
2098 @safe pure nothrow version(mir_test) unittest
2099 {
2100     import mir.ndslice.allocation;
2101 
2102     auto sl = iota([3], 6).slice;
2103     auto slC = sl.repeat(2, 3);
2104     sl[1] = 4;
2105     assert(slC == [[[6, 4, 8],
2106                     [6, 4, 8],
2107                     [6, 4, 8]],
2108                    [[6, 4, 8],
2109                     [6, 4, 8],
2110                     [6, 4, 8]]]);
2111 }
2112 
2113 ///
2114 @safe pure nothrow version(mir_test) unittest
2115 {
2116     import mir.primitives: DeepElementType;
2117 
2118     auto sl = repeat(4.0, 2, 3);
2119     assert(sl == [[4.0, 4.0, 4.0],
2120                   [4.0, 4.0, 4.0]]);
2121 
2122     static assert(is(DeepElementType!(typeof(sl)) == double));
2123 
2124     sl[1, 1] = 3;
2125     assert(sl == [[3.0, 3.0, 3.0],
2126                   [3.0, 3.0, 3.0]]);
2127 }
2128 
2129 /++
2130 Cycle repeates 1-dimensional field/range/array/slice in a fixed length 1-dimensional slice.
2131 +/
2132 auto cycle(Field)(Field field, size_t loopLength, size_t length)
2133     if (!isSlice!Field && !is(Field : T[], T))
2134 {
2135     return CycleField!Field(loopLength, field).slicedField(length);
2136 }
2137 
2138 /// ditto
2139 auto cycle(size_t loopLength, Field)(Field field, size_t length)
2140     if (!isSlice!Field && !is(Field : T[], T))
2141 {
2142     static assert(loopLength);
2143     return CycleField!(Field, loopLength)(field).slicedField(length);
2144 }
2145 
2146 /// ditto
2147 auto cycle(Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) slice, size_t length)
2148 {
2149     assert(slice.length);
2150     static if (kind == Universal)
2151         return slice.hideStride.cycle(length);
2152     else
2153         return CycleField!Iterator(slice._lengths[0], slice._iterator).slicedField(length);
2154 }
2155 
2156 /// ditto
2157 auto cycle(size_t loopLength, Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) slice, size_t length)
2158 {
2159     static assert(loopLength);
2160     assert(loopLength <= slice.length);
2161     static if (kind == Universal)
2162         return slice.hideStride.cycle!loopLength(length);
2163     else
2164         return CycleField!(Iterator, loopLength)(slice._iterator).slicedField(length);
2165 }
2166 
2167 /// ditto
2168 auto cycle(T)(T[] array, size_t length)
2169 {
2170     return cycle(array.sliced, length);
2171 }
2172 
2173 /// ditto
2174 auto cycle(size_t loopLength, T)(T[] array, size_t length)
2175 {
2176     return cycle!loopLength(array.sliced, length);
2177 }
2178 
2179 /// ditto
2180 auto cycle(size_t loopLength, T)(T withAsSlice, size_t length)
2181     if (hasAsSlice!T)
2182 {
2183     return cycle!loopLength(withAsSlice.asSlice, length);
2184 }
2185 
2186 ///
2187 @safe pure nothrow version(mir_test) unittest
2188 {
2189     auto slice = iota(3);
2190     assert(slice.cycle(7) == [0, 1, 2, 0, 1, 2, 0]);
2191     assert(slice.cycle!2(7) == [0, 1, 0, 1, 0, 1, 0]);
2192     assert([0, 1, 2].cycle(7) == [0, 1, 2, 0, 1, 2, 0]);
2193     assert([4, 3, 2, 1].cycle!4(7) == [4, 3, 2, 1, 4, 3, 2]);
2194 }
2195 
2196 /++
2197 Strides 1-dimensional slice.
2198 Params:
2199     slice = 1-dimensional unpacked slice.
2200     factor = positive stride size.
2201 Returns:
2202     Contiguous slice with strided iterator.
2203 See_also: $(SUBREF dynamic, strided)
2204 +/
2205 auto stride
2206     (Iterator, size_t N, SliceKind kind)
2207     (Slice!(Iterator, N, kind) slice, ptrdiff_t factor)
2208     if (N == 1)
2209 in
2210 {
2211     assert (factor > 0, "factor must be positive.");
2212 }
2213 do
2214 {
2215     static if (kind == Contiguous)
2216         return slice.universal.stride(factor);
2217     else
2218     {
2219         import mir.ndslice.dynamic: strided;
2220         return slice.strided!0(factor).hideStride;
2221     }
2222 }
2223 
2224 ///ditto
2225 template stride(size_t factor = 2)
2226     if (factor > 1)
2227 {
2228     auto stride
2229         (Iterator, size_t N, SliceKind kind)
2230         (Slice!(Iterator, N, kind) slice)
2231     {
2232         import core.lifetime: move;
2233         static if (N > 1)
2234         {
2235             return stride(slice.move.ipack!1.map!(.stride!factor));
2236         }
2237         else
2238         static if (kind == Contiguous)
2239         {
2240             immutable rem = slice._lengths[0] % factor;
2241             slice._lengths[0] /= factor;
2242             if (rem)
2243                 slice._lengths[0]++;
2244             return Slice!(StrideIterator!(Iterator, factor), 1, kind)(slice._structure, StrideIterator!(Iterator, factor)(move(slice._iterator)));
2245         }
2246         else
2247         {
2248             return .stride(slice.move, factor);
2249         }
2250     }
2251 
2252     /// ditto
2253     auto stride(T)(T[] array)
2254     {
2255         return stride(array.sliced);
2256     }
2257 
2258     /// ditto
2259     auto stride(T)(T withAsSlice)
2260         if (hasAsSlice!T)
2261     {
2262         return stride(withAsSlice.asSlice);
2263     }
2264 }
2265 
2266 /// ditto
2267 auto stride(T)(T[] array, ptrdiff_t factor)
2268 {
2269     return stride(array.sliced, factor);
2270 }
2271 
2272 /// ditto
2273 auto stride(T)(T withAsSlice, ptrdiff_t factor)
2274     if (hasAsSlice!T)
2275 {
2276     return stride(withAsSlice.asSlice, factor);
2277 }
2278 
2279 ///
2280 @safe pure nothrow @nogc version(mir_test) unittest
2281 {
2282     auto slice = iota(6);
2283     static immutable str = [0, 2, 4];
2284     assert(slice.stride(2) == str); // runtime factor
2285     assert(slice.stride!2 == str); // compile time factor
2286     assert(slice.stride == str); // default compile time factor is 2
2287     assert(slice.universal.stride(2) == str);
2288 }
2289 
2290 /// ND-compile time
2291 @safe pure nothrow @nogc version(mir_test) unittest
2292 {
2293     auto slice = iota(4, 6);
2294     static immutable str = [[0, 2, 4], [12, 14, 16]];
2295     assert(slice.stride!2 == str); // compile time factor
2296     assert(slice.stride == str); // default compile time factor is 2
2297 }
2298 
2299 /++
2300 Reverses order of iteration for all dimensions.
2301 Params:
2302     slice = slice, range, or array.
2303 Returns:
2304     Slice/range with reversed order of iteration for all dimensions.
2305 See_also: $(SUBREF dynamic, reversed), $(SUBREF dynamic, allReversed).
2306 +/
2307 auto retro
2308     (Iterator, size_t N, SliceKind kind)
2309     (Slice!(Iterator, N, kind) slice)
2310     @trusted
2311 {
2312     import core.lifetime: move;
2313     static if (kind == Contiguous || kind == Canonical)
2314     {
2315         size_t[slice.N] lengths;
2316         foreach (i; Iota!(slice.N))
2317             lengths[i] = slice._lengths[i];
2318         static if (slice.S)
2319         {
2320             sizediff_t[slice.S] strides;
2321             foreach (i; Iota!(slice.S))
2322                 strides[i] = slice._strides[i];
2323             alias structure = AliasSeq!(lengths, strides);
2324         }
2325         else
2326         {
2327             alias structure = lengths;
2328         }
2329         static if (is(Iterator : RetroIterator!It, It))
2330         {
2331             alias Ret = Slice!(It, N, kind);
2332             slice._iterator._iterator -= slice.lastIndex;
2333             return Ret(structure, slice._iterator._iterator.move);
2334         }
2335         else
2336         {
2337             alias Ret = Slice!(RetroIterator!Iterator, N, kind);
2338             slice._iterator += slice.lastIndex;
2339             return Ret(structure, RetroIterator!Iterator(slice._iterator.move));
2340         }
2341     }
2342     else
2343     {
2344         import mir.ndslice.dynamic: allReversed;
2345         return slice.move.allReversed;
2346     }
2347 }
2348 
2349 /// ditto
2350 auto retro(T)(T[] array)
2351 {
2352     return retro(array.sliced);
2353 }
2354 
2355 /// ditto
2356 auto retro(T)(T withAsSlice)
2357     if (hasAsSlice!T)
2358 {
2359     return retro(withAsSlice.asSlice);
2360 }
2361 
2362 /// ditto
2363 auto retro(Range)(Range r)
2364     if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T))
2365 {
2366     import std.traits: Unqual;
2367 
2368     static if (is(Unqual!Range == Range))
2369     {
2370         import core.lifetime: move;
2371         static if (is(Range : RetroRange!R, R))
2372         {
2373             return move(r._source);
2374         }
2375         else
2376         {
2377             return RetroRange!Range(move(r));
2378         }
2379     }
2380     else
2381     {
2382         return .retro!(Unqual!Range)(r);
2383     }
2384 }
2385 
2386 /// ditto
2387 struct RetroRange(Range)
2388 {
2389     import mir.primitives: hasLength;
2390 
2391     ///
2392     Range _source;
2393 
2394     private enum hasAccessByRef = __traits(compiles, &_source.front);
2395 
2396     @property 
2397     {
2398         bool empty()() const { return _source.empty; }
2399         static if (hasLength!Range)
2400             auto length()() const { return _source.length; }
2401         auto ref front()() { return _source.back; }
2402         auto ref back()() { return _source.front; }
2403         static if (__traits(hasMember, Range, "save"))
2404             auto save()() { return RetroRange(_source.save); }
2405         alias opDollar = length;
2406 
2407         static if (!hasAccessByRef)
2408         {
2409             import std.traits: ForeachType;
2410 
2411             void front()(ForeachType!R val)
2412             {
2413                 import mir.functional: forward;
2414                 _source.back = forward!val;
2415             }
2416 
2417             void back()(ForeachType!R val)
2418             {
2419                 import mir.functional: forward;
2420                 _source.front = forward!val;
2421             }
2422         }
2423     }
2424 
2425     void popFront()() { _source.popBack(); }
2426     void popBack()() { _source.popFront(); }
2427 
2428     static if (is(typeof(_source.moveBack())))
2429     auto moveFront()() { return _source.moveBack(); }
2430 
2431     static if (is(typeof(_source.moveFront())))
2432     auto moveBack()() { return _source.moveFront(); }
2433 }
2434 
2435 ///
2436 @safe pure nothrow @nogc version(mir_test) unittest
2437 {
2438     auto slice = iota(2, 3);
2439     static immutable reversed = [[5, 4, 3], [2, 1, 0]];
2440     assert(slice.retro == reversed);
2441     assert(slice.canonical.retro == reversed);
2442     assert(slice.universal.retro == reversed);
2443 
2444     static assert(is(typeof(slice.retro.retro) == typeof(slice)));
2445     static assert(is(typeof(slice.canonical.retro.retro) == typeof(slice.canonical)));
2446     static assert(is(typeof(slice.universal.retro) == typeof(slice.universal)));
2447 }
2448 
2449 /// Ranges
2450 @safe pure nothrow @nogc version(mir_test) unittest
2451 {
2452     import mir.algorithm.iteration: equal;
2453     import std.range: std_iota = iota;
2454 
2455     assert(std_iota(4).retro.equal(iota(4).retro));
2456     static assert(is(typeof(std_iota(4).retro.retro) == typeof(std_iota(4))));
2457 }
2458 
2459 /++
2460 Bitwise slice over an integral slice.
2461 Params:
2462     slice = a contiguous or canonical slice on top of integral iterator.
2463 Returns: A bitwise slice.
2464 +/
2465 auto bitwise
2466     (Iterator, size_t N, SliceKind kind, I = typeof(Iterator.init[size_t.init]))
2467     (Slice!(Iterator, N, kind) slice)
2468     if (__traits(isIntegral, I) && (kind != Universal || N == 1))
2469 {
2470     import core.lifetime: move;
2471     static if (kind == Universal)
2472     {
2473         return slice.move.flattened.bitwise;
2474     }
2475     else
2476     {
2477         static if (is(Iterator : FieldIterator!Field, Field))
2478         {
2479             enum simplified = true;
2480             alias It = FieldIterator!(BitField!Field);
2481         }
2482         else
2483         {
2484             enum simplified = false;
2485             alias It = FieldIterator!(BitField!Iterator);
2486         }
2487         alias Ret = Slice!(It, N, kind);
2488         auto structure_ = Ret._Structure.init;
2489         foreach(i; Iota!(Ret.N))
2490             structure_[0][i] = slice._lengths[i];
2491         structure_[0][$ - 1] *= I.sizeof * 8;
2492         foreach(i; Iota!(Ret.S))
2493             structure_[1][i] = slice._strides[i];
2494         static if (simplified)
2495             return Ret(structure_, It(slice._iterator._index * I.sizeof * 8, BitField!Field(slice._iterator._field.move)));
2496         else
2497             return Ret(structure_, It(0, BitField!Iterator(slice._iterator.move)));
2498     }
2499 }
2500 
2501 /// ditto
2502 auto bitwise(T)(T[] array)
2503 {
2504     return bitwise(array.sliced);
2505 }
2506 
2507 /// ditto
2508 auto bitwise(T)(T withAsSlice)
2509     if (hasAsSlice!T)
2510 {
2511     return bitwise(withAsSlice.asSlice);
2512 }
2513 
2514 ///
2515 @safe pure nothrow @nogc
2516 version(mir_test) unittest
2517 {
2518     size_t[10] data;
2519     auto bits = data[].bitwise;
2520     assert(bits.length == data.length * size_t.sizeof * 8);
2521     bits[111] = true;
2522     assert(bits[111]);
2523 
2524     bits.popFront;
2525     assert(bits[110]);
2526     bits[] = true;
2527     bits[110] = false;
2528     bits = bits[10 .. $];
2529     assert(bits[100] == false);
2530 }
2531 
2532 @safe pure nothrow @nogc
2533 version(mir_test) unittest
2534 {
2535     size_t[10] data;
2536     auto slice = FieldIterator!(size_t[])(0, data[]).sliced(10);
2537     slice.popFrontExactly(2);
2538     auto bits_normal = data[].sliced.bitwise;
2539     auto bits = slice.bitwise;
2540     assert(bits.length == (data.length - 2) * size_t.sizeof * 8);
2541     bits[111] = true;
2542     assert(bits[111]);
2543     assert(bits_normal[111 + size_t.sizeof * 2 * 8]);
2544     auto ubits = slice.universal.bitwise;
2545     assert(bits.map!"~a" == bits.map!"!a");
2546     static assert (is(typeof(bits.map!"~a") == typeof(bits.map!"!a")));
2547     assert(bits.map!"~a" == bits.map!"!!!a");
2548     static assert (!is(typeof(bits.map!"~a") == typeof(bits.map!"!!!a")));
2549     assert(bits == ubits);
2550 
2551     bits.popFront;
2552     assert(bits[110]);
2553     bits[] = true;
2554     bits[110] = false;
2555     bits = bits[10 .. $];
2556     assert(bits[100] == false);
2557 }
2558 
2559 /++
2560 Bitwise field over an integral field.
2561 Params:
2562     field = an integral field.
2563 Returns: A bitwise field.
2564 +/
2565 auto bitwiseField(Field, I = typeof(Field.init[size_t.init]))(Field field)
2566     if (__traits(isUnsigned, I))
2567 {
2568     import core.lifetime: move;
2569     return BitField!(Field, I)(field.move);
2570 }
2571 
2572 /++
2573 Bitpack slice over an integral slice.
2574 
2575 Bitpack is used to represent unsigned integer slice with fewer number of bits in integer binary representation.
2576 
2577 Params:
2578     pack = counts of bits in the integer.
2579     slice = a contiguous or canonical slice on top of integral iterator.
2580 Returns: A bitpack slice.
2581 +/
2582 auto bitpack
2583     (size_t pack, Iterator, size_t N, SliceKind kind, I = typeof(Iterator.init[size_t.init]))
2584     (Slice!(Iterator, N, kind) slice)
2585     if (__traits(isIntegral, I) && (kind == Contiguous || kind == Canonical) && pack > 1)
2586 {
2587     import core.lifetime: move;
2588     static if (is(Iterator : FieldIterator!Field, Field) && I.sizeof * 8 % pack == 0)
2589     {
2590         enum simplified = true;
2591         alias It = FieldIterator!(BitpackField!(Field, pack));
2592     }
2593     else
2594     {
2595         enum simplified = false;
2596         alias It = FieldIterator!(BitpackField!(Iterator, pack));
2597     }
2598     alias Ret = Slice!(It, N, kind);
2599     auto structure = Ret._Structure.init;
2600     foreach(i; Iota!(Ret.N))
2601         structure[0][i] = slice._lengths[i];
2602     structure[0][$ - 1] *= I.sizeof * 8;
2603     structure[0][$ - 1] /= pack;
2604     foreach(i; Iota!(Ret.S))
2605         structure[1][i] = slice._strides[i];
2606     static if (simplified)
2607         return Ret(structure, It(slice._iterator._index * I.sizeof * 8 / pack, BitpackField!(Field, pack)(slice._iterator._field.move)));
2608     else
2609         return Ret(structure, It(0, BitpackField!(Iterator, pack)(slice._iterator.move)));
2610 }
2611 
2612 /// ditto
2613 auto bitpack(size_t pack, T)(T[] array)
2614 {
2615     return bitpack!pack(array.sliced);
2616 }
2617 
2618 /// ditto
2619 auto bitpack(size_t pack, T)(T withAsSlice)
2620     if (hasAsSlice!T)
2621 {
2622     return bitpack!pack(withAsSlice.asSlice);
2623 }
2624 
2625 ///
2626 @safe pure nothrow @nogc
2627 version(mir_test) unittest
2628 {
2629     size_t[10] data;
2630     // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`.
2631     auto packs = data[].bitpack!6;
2632     assert(packs.length == data.length * size_t.sizeof * 8 / 6);
2633     packs[$ - 1] = 24;
2634     assert(packs[$ - 1] == 24);
2635 
2636     packs.popFront;
2637     assert(packs[$ - 1] == 24);
2638 }
2639 
2640 /++
2641 Bytegroup slice over an integral slice.
2642 
2643 Groups existing slice into fixed length chunks and uses them as data store for destination type.
2644 
2645 Correctly handles scalar types on both little-endian and big-endian platforms.
2646 
2647 Params:
2648     group = count of iterator items used to store the destination type.
2649     DestinationType = deep element type of the result slice.
2650     slice = a contiguous or canonical slice.
2651 Returns: A bytegroup slice.
2652 +/
2653 Slice!(BytegroupIterator!(Iterator, group, DestinationType), N, kind)
2654 bytegroup
2655     (size_t group, DestinationType, Iterator, size_t N, SliceKind kind)
2656     (Slice!(Iterator, N, kind) slice)
2657     if ((kind == Contiguous || kind == Canonical) && group)
2658 {
2659     import core.lifetime: move;
2660     auto structure = slice._structure;
2661     structure[0][$ - 1] /= group;
2662     return typeof(return)(structure, BytegroupIterator!(Iterator, group, DestinationType)(slice._iterator.move));
2663 }
2664 
2665 /// ditto
2666 auto bytegroup(size_t pack, DestinationType, T)(T[] array)
2667 {
2668     return bytegroup!(pack, DestinationType)(array.sliced);
2669 }
2670 
2671 /// ditto
2672 auto bytegroup(size_t pack, DestinationType, T)(T withAsSlice)
2673     if (hasAsSlice!T)
2674 {
2675     return bytegroup!(pack, DestinationType)(withAsSlice.asSlice);
2676 }
2677 
2678 /// 24 bit integers
2679 @safe pure nothrow @nogc
2680 version(mir_test) unittest
2681 {
2682     import mir.ndslice.slice: DeepElementType, sliced;
2683 
2684     ubyte[20] data;
2685     // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`.
2686     auto int24ar = data[].bytegroup!(3, int); // 24 bit integers
2687     assert(int24ar.length == data.length / 3);
2688 
2689     enum checkInt = ((1 << 20) - 1);
2690 
2691     int24ar[3] = checkInt;
2692     assert(int24ar[3] == checkInt);
2693 
2694     int24ar.popFront;
2695     assert(int24ar[2] == checkInt);
2696 
2697     static assert(is(DeepElementType!(typeof(int24ar)) == int));
2698 }
2699 
2700 /// 48 bit integers
2701 @safe pure nothrow @nogc
2702 version(mir_test) unittest
2703 {
2704     import mir.ndslice.slice: DeepElementType, sliced;
2705     ushort[20] data;
2706     // creates a packed unsigned integer slice with max allowed value equal to `2^^6 - 1 == 63`.
2707     auto int48ar = data[].sliced.bytegroup!(3, long); // 48 bit integers
2708     assert(int48ar.length == data.length / 3);
2709 
2710     enum checkInt = ((1L << 44) - 1);
2711 
2712     int48ar[3] = checkInt;
2713     assert(int48ar[3] == checkInt);
2714 
2715     int48ar.popFront;
2716     assert(int48ar[2] == checkInt);
2717 
2718     static assert(is(DeepElementType!(typeof(int48ar)) == long));
2719 }
2720 
2721 /++
2722 Implements the homonym function (also known as `transform`) present
2723 in many languages of functional flavor. The call `map!(fun)(slice)`
2724 returns a slice of which elements are obtained by applying `fun`
2725 for all elements in `slice`. The original slices are
2726 not changed. Evaluation is done lazily.
2727 
2728 Note:
2729     $(SUBREF dynamic, transposed) and
2730     $(SUBREF topology, pack) can be used to specify dimensions.
2731 Params:
2732     fun = One or more functions.
2733 See_Also:
2734     $(LREF cached), $(LREF vmap),  $(LREF rcmap), $(LREF indexed),
2735     $(LREF pairwise), $(LREF subSlices), $(LREF slide), $(LREF zip),
2736     $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function))
2737 +/
2738 template map(fun...)
2739     if (fun.length)
2740 {
2741     import mir.functional: adjoin, naryFun, pipe;
2742     static if (fun.length == 1)
2743     {
2744         static if (__traits(isSame, naryFun!(fun[0]), fun[0]))
2745         {
2746             alias f = fun[0];
2747         @optmath:
2748             /++
2749             Params:
2750                 slice = An ndslice, array, or an input range.
2751             Returns:
2752                 ndslice or an input range with each fun applied to all the elements. If there is more than one
2753                 fun, the element type will be `Tuple` containing one element for each fun.
2754             +/
2755             auto map(Iterator, size_t N, SliceKind kind)
2756                 (Slice!(Iterator, N, kind) slice)
2757             {
2758                 import core.lifetime: move;
2759                 alias MIterator = typeof(_mapIterator!f(slice._iterator));
2760                 import mir.ndslice.traits: isIterator;
2761                 alias testIter = typeof(MIterator.init[0]);
2762                 static assert(isIterator!MIterator, "mir.ndslice.map: probably the lambda function contains a compile time bug.");
2763                 return Slice!(MIterator, N, kind)(slice._structure, _mapIterator!f(slice._iterator.move));
2764             }
2765 
2766             /// ditto
2767             auto map(T)(T[] array)
2768             {
2769                 return map(array.sliced);
2770             }
2771 
2772             /// ditto
2773             auto map(T)(T withAsSlice)
2774                 if (hasAsSlice!T)
2775             {
2776                 return map(withAsSlice.asSlice);
2777             }
2778 
2779             /// ditto
2780             auto map(Range)(Range r)
2781                 if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T))
2782             {
2783                 import core.lifetime: forward;
2784                 import mir.primitives: isInputRange;
2785                 static assert (isInputRange!Range, "map can work with ndslice, array, or an input range.");
2786                 return MapRange!(f, ImplicitlyUnqual!Range)(forward!r);
2787             }
2788         }
2789         else alias map = .map!(staticMap!(naryFun, fun));
2790     }
2791     else alias map = .map!(adjoin!fun);
2792 }
2793 
2794 /// ditto
2795 struct MapRange(alias fun, Range)
2796 {
2797     import std.range.primitives;
2798 
2799     Range _input;
2800 
2801     static if (isInfinite!Range)
2802     {
2803         enum bool empty = false;
2804     }
2805     else
2806     {
2807         bool empty() @property
2808         {
2809             return _input.empty;
2810         }
2811     }
2812 
2813     void popFront()
2814     {
2815         assert(!empty, "Attempting to popFront an empty map.");
2816         _input.popFront();
2817     }
2818 
2819     auto ref front() @property
2820     {
2821         assert(!empty, "Attempting to fetch the front of an empty map.");
2822         return fun(_input.front);
2823     }
2824 
2825     static if (isBidirectionalRange!Range)
2826     auto ref back()() @property
2827     {
2828         assert(!empty, "Attempting to fetch the back of an empty map.");
2829         return fun(_input.back);
2830     }
2831 
2832     static if (isBidirectionalRange!Range)
2833     void popBack()()
2834     {
2835         assert(!empty, "Attempting to popBack an empty map.");
2836         _input.popBack();
2837     }
2838 
2839     static if (hasLength!Range)
2840     auto length() @property
2841     {
2842         return _input.length;
2843     }
2844 
2845     static if (isForwardRange!Range)
2846     auto save()() @property
2847     {
2848         return typeof(this)(_input.save);
2849     }
2850 }
2851 
2852 ///
2853 @safe pure nothrow
2854 version(mir_test) unittest
2855 {
2856     import mir.ndslice.topology : iota;
2857     auto s = iota(2, 3).map!(a => a * 3);
2858     assert(s == [[ 0,  3,  6],
2859                  [ 9, 12, 15]]);
2860 }
2861 
2862 /// String lambdas
2863 @safe pure nothrow
2864 version(mir_test) unittest
2865 {
2866     import mir.ndslice.topology : iota;
2867     assert(iota(2, 3).map!"a * 2" == [[0, 2, 4], [6, 8, 10]]);
2868 }
2869 
2870 /// Input ranges
2871 @safe pure nothrow
2872 version(mir_test) unittest
2873 {
2874     import mir.algorithm.iteration: filter, equal;
2875     assert (6.iota.filter!"a % 2".map!"a * 10".equal([10, 30, 50])); 
2876 }
2877 
2878 /// Packed tensors
2879 @safe pure nothrow
2880 version(mir_test) unittest
2881 {
2882     import mir.ndslice.topology : iota, windows;
2883     import mir.math.sum: sum;
2884 
2885     //  iota        windows     map  sums ( reduce!"a + b" )
2886     //                --------------
2887     //  -------      |  ---    ---  |      ------
2888     // | 0 1 2 |  => || 0 1 || 1 2 ||  => | 8 12 |
2889     // | 3 4 5 |     || 3 4 || 4 5 ||      ------
2890     //  -------      |  ---    ---  |
2891     //                --------------
2892     auto s = iota(2, 3)
2893         .windows(2, 2)
2894         .map!sum;
2895 
2896     assert(s == [[8, 12]]);
2897 }
2898 
2899 /// Zipped tensors
2900 @safe pure nothrow
2901 version(mir_test) unittest
2902 {
2903     import mir.ndslice.topology : iota, zip;
2904 
2905     // 0 1 2
2906     // 3 4 5
2907     auto sl1 = iota(2, 3);
2908     // 1 2 3
2909     // 4 5 6
2910     auto sl2 = iota([2, 3], 1);
2911 
2912     auto z = zip(sl1, sl2);
2913 
2914     assert(zip(sl1, sl2).map!"a + b" == sl1 + sl2);
2915     assert(zip(sl1, sl2).map!((a, b) => a + b) == sl1 + sl2);
2916 }
2917 
2918 /++
2919 Multiple functions can be passed to `map`.
2920 In that case, the element type of `map` is a refTuple containing
2921 one element for each function.
2922 +/
2923 @safe pure nothrow
2924 version(mir_test) unittest
2925 {
2926     import mir.ndslice.topology : iota;
2927 
2928     auto sl = iota(2, 3);
2929     auto s = sl.map!("a + a", "a * a");
2930 
2931     auto sums     = [[0, 2, 4], [6,  8, 10]];
2932     auto products = [[0, 1, 4], [9, 16, 25]];
2933 
2934     assert(s.map!"a[0]" == sl + sl);
2935     assert(s.map!"a[1]" == sl * sl);
2936 }
2937 
2938 /++
2939 `map` can be aliased to a symbol and be used separately:
2940 +/
2941 pure nothrow version(mir_test) unittest
2942 {
2943     import mir.ndslice.topology : iota;
2944 
2945     alias halfs = map!"double(a) / 2";
2946     assert(halfs(iota(2, 3)) == [[0.0, 0.5, 1], [1.5, 2, 2.5]]);
2947 }
2948 
2949 /++
2950 Type normalization
2951 +/
2952 version(mir_test) unittest
2953 {
2954     import mir.functional : pipe;
2955     import mir.ndslice.topology : iota;
2956     auto a = iota(2, 3).map!"a + 10".map!(pipe!("a * 2", "a + 1"));
2957     auto b = iota(2, 3).map!(pipe!("a + 10", "a * 2", "a + 1"));
2958     assert(a == b);
2959     static assert(is(typeof(a) == typeof(b)));
2960 }
2961 
2962 /// Use map with byDim/alongDim to apply functions to each dimension
2963 version(mir_test)
2964 @safe pure
2965 unittest
2966 {
2967     import mir.ndslice.topology: byDim, alongDim;
2968     import mir.ndslice.fuse: fuse;
2969     import mir.math.stat: mean;
2970     import mir.algorithm.iteration: all;
2971     import mir.math.common: approxEqual;
2972 
2973     auto x = [
2974         [0.0, 1.0, 1.5, 2.0, 3.5, 4.25],
2975         [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]
2976     ].fuse;
2977 
2978     // Use byDim/alongDim with map to compute mean of row/column.
2979     assert(x.byDim!0.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6]));
2980     assert(x.byDim!1.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125]));
2981     assert(x.alongDim!1.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6]));
2982     assert(x.alongDim!0.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125]));
2983 }
2984 
2985 /++
2986 Use map with a lambda and with byDim/alongDim, but may need to allocate result. 
2987 This example uses fuse, which allocates. Note: fuse!1 will transpose the result. 
2988 +/
2989 version(mir_test)
2990 @safe pure
2991 unittest {
2992     import mir.ndslice.topology: iota, byDim, alongDim, map;
2993     import mir.ndslice.fuse: fuse;
2994     import mir.ndslice.slice: sliced;
2995     
2996     auto x = [1, 2, 3].sliced;
2997     auto y = [1, 2].sliced;
2998 
2999     auto s1 = iota(2, 3).byDim!0.map!(a => a * x).fuse;
3000     assert(s1 == [[ 0, 2,  6],
3001                   [ 3, 8, 15]]);
3002     auto s2 = iota(2, 3).byDim!1.map!(a => a * y).fuse!1;
3003     assert(s2 == [[ 0, 1,  2],
3004                   [ 6, 8, 10]]);
3005     auto s3 = iota(2, 3).alongDim!1.map!(a => a * x).fuse;
3006     assert(s1 == [[ 0, 2,  6],
3007                   [ 3, 8, 15]]);
3008     auto s4 = iota(2, 3).alongDim!0.map!(a => a * y).fuse!1;
3009     assert(s2 == [[ 0, 1,  2],
3010                   [ 6, 8, 10]]);
3011 }
3012 
3013 ///
3014 pure version(mir_test) unittest
3015 {
3016     import mir.algorithm.iteration: reduce;
3017     import mir.math.common: fmax;
3018     import mir.math.stat: mean;
3019     import mir.math.sum;
3020     /// Returns maximal column average.
3021     auto maxAvg(S)(S matrix) {
3022         return reduce!fmax(0.0, matrix.alongDim!1.map!mean);
3023     }
3024     // 1 2
3025     // 3 4
3026     auto matrix = iota([2, 2], 1);
3027     assert(maxAvg(matrix) == 3.5);
3028 }
3029 
3030 /++
3031 Implements the homonym function (also known as `transform`) present
3032 in many languages of functional flavor. The call `slice.vmap(fun)`
3033 returns a slice of which elements are obtained by applying `fun`
3034 for all elements in `slice`. The original slices are
3035 not changed. Evaluation is done lazily.
3036 
3037 Note:
3038     $(SUBREF dynamic, transposed) and
3039     $(SUBREF topology, pack) can be used to specify dimensions.
3040 Params:
3041     slice = ndslice
3042     callable = callable object, structure, delegate, or function pointer.
3043 See_Also:
3044     $(LREF cached), $(LREF map),  $(LREF rcmap), $(LREF indexed),
3045     $(LREF pairwise), $(LREF subSlices), $(LREF slide), $(LREF zip),
3046     $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function))
3047 +/
3048 @optmath auto vmap(Iterator, size_t N, SliceKind kind, Callable)
3049     (
3050         Slice!(Iterator, N, kind) slice,
3051         Callable callable,
3052     )
3053 {
3054     import core.lifetime: move;
3055     alias It = VmapIterator!(Iterator, Callable);
3056     return Slice!(It, N, kind)(slice._structure, It(slice._iterator.move, callable.move));
3057 }
3058 
3059 /// ditto
3060 auto vmap(T, Callable)(T[] array, Callable callable)
3061 {
3062     import core.lifetime: move;
3063     return vmap(array.sliced, callable.move);
3064 }
3065 
3066 /// ditto
3067 auto vmap(T, Callable)(T withAsSlice, Callable callable)
3068     if (hasAsSlice!T)
3069 {
3070     import core.lifetime: move;
3071     return vmap(withAsSlice.asSlice, callable.move);
3072 }
3073 
3074 ///
3075 @safe pure nothrow
3076 version(mir_test) unittest
3077 {
3078     import mir.ndslice.topology : iota;
3079 
3080     static struct Mul {
3081         double factor; this(double f) { factor = f; }
3082         auto opCall(long x) const {return x * factor; }
3083         auto lightConst()() const @property { return Mul(factor); }
3084     }
3085 
3086     auto callable = Mul(3);
3087     auto s = iota(2, 3).vmap(callable);
3088 
3089     assert(s == [[ 0,  3,  6],
3090                  [ 9, 12, 15]]);
3091 }
3092 
3093 /// Packed tensors.
3094 @safe pure nothrow
3095 version(mir_test) unittest
3096 {
3097     import mir.math.sum: sum;
3098     import mir.ndslice.topology : iota, windows;
3099 
3100     //  iota        windows     vmap  scaled sums
3101     //                --------------
3102     //  -------      |  ---    ---  |      -----
3103     // | 0 1 2 |  => || 0 1 || 1 2 ||  => | 4 6 |
3104     // | 3 4 5 |     || 3 4 || 4 5 ||      -----
3105     //  -------      |  ---    ---  |
3106     //                --------------
3107 
3108     struct Callable
3109     {
3110         double factor;
3111         this(double f) {factor = f;}
3112         auto opCall(S)(S x) { return x.sum * factor; }
3113 
3114         auto lightConst()() const @property { return Callable(factor); }
3115         auto lightImmutable()() immutable @property { return Callable(factor); }
3116     }
3117 
3118     auto callable = Callable(0.5);
3119 
3120     auto s = iota(2, 3)
3121         .windows(2, 2)
3122         .vmap(callable);
3123 
3124     assert(s == [[4, 6]]);
3125 }
3126 
3127 /// Zipped tensors
3128 @safe pure nothrow
3129 version(mir_test) unittest
3130 {
3131     import mir.ndslice.topology : iota, zip;
3132 
3133     struct Callable
3134     {
3135         double factor;
3136         this(double f) {factor = f;}
3137         auto opCall(S, T)(S x, T y) { return x + y * factor; }
3138 
3139         auto lightConst()() const { return Callable(factor); }
3140         auto lightImmutable()() immutable { return Callable(factor); }
3141     }
3142 
3143     auto callable = Callable(10);
3144 
3145     // 0 1 2
3146     // 3 4 5
3147     auto sl1 = iota(2, 3);
3148     // 1 2 3
3149     // 4 5 6
3150     auto sl2 = iota([2, 3], 1);
3151 
3152     auto z = zip(sl1, sl2);
3153 
3154     assert(zip(sl1, sl2).vmap(callable) ==
3155             [[10,  21,  32],
3156              [43,  54,  65]]);
3157 }
3158 
3159 // TODO
3160 /+
3161 Multiple functions can be passed to `vmap`.
3162 In that case, the element type of `vmap` is a refTuple containing
3163 one element for each function.
3164 +/
3165 @safe pure nothrow
3166 version(none) version(mir_test) unittest
3167 {
3168     import mir.ndslice.topology : iota;
3169 
3170     auto s = iota(2, 3).vmap!("a + a", "a * a");
3171 
3172     auto sums     = [[0, 2, 4], [6,  8, 10]];
3173     auto products = [[0, 1, 4], [9, 16, 25]];
3174 
3175     foreach (i; 0..s.length!0)
3176     foreach (j; 0..s.length!1)
3177     {
3178         auto values = s[i, j];
3179         assert(values.a == sums[i][j]);
3180         assert(values.b == products[i][j]);
3181     }
3182 }
3183 
3184 /// Use vmap with byDim/alongDim to apply functions to each dimension
3185 version(mir_test)
3186 @safe pure
3187 unittest
3188 {
3189     import mir.ndslice.fuse: fuse;
3190     import mir.math.stat: mean;
3191     import mir.algorithm.iteration: all;
3192     import mir.math.common: approxEqual;
3193 
3194     auto x = [
3195         [0.0, 1.0, 1.5, 2.0, 3.5, 4.25],
3196         [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]
3197     ].fuse;
3198 
3199     static struct Callable
3200     {
3201         double factor;
3202         this(double f) {factor = f;}
3203         auto opCall(U)(U x) const {return x.mean + factor; }
3204         auto lightConst()() const @property { return Callable(factor); }
3205     }
3206 
3207     auto callable = Callable(0.0);
3208 
3209     // Use byDim/alongDim with map to compute callable of row/column.
3210     assert(x.byDim!0.vmap(callable).all!approxEqual([12.25 / 6, 17.0 / 6]));
3211     assert(x.byDim!1.vmap(callable).all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125]));
3212     assert(x.alongDim!1.vmap(callable).all!approxEqual([12.25 / 6, 17.0 / 6]));
3213     assert(x.alongDim!0.vmap(callable).all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125]));
3214 }
3215 
3216 /++
3217 Use vmap with a lambda and with byDim/alongDim, but may need to allocate result. 
3218 This example uses fuse, which allocates. Note: fuse!1 will transpose the result. 
3219 +/
3220 version(mir_test)
3221 @safe pure
3222 unittest {
3223     import mir.ndslice.topology: iota, alongDim, map;
3224     import mir.ndslice.fuse: fuse;
3225     import mir.ndslice.slice: sliced;
3226 
3227     static struct Mul(T)
3228     {
3229         T factor;
3230         this(T f) { factor = f; }
3231         auto opCall(U)(U x) {return x * factor; }
3232         auto lightConst()() const @property { return Mul!(typeof(factor.lightConst))(factor.lightConst); }
3233     }
3234 
3235     auto a = [1, 2, 3].sliced;
3236     auto b = [1, 2].sliced;
3237     auto A = Mul!(typeof(a))(a);
3238     auto B = Mul!(typeof(b))(b);
3239 
3240     auto x = [
3241         [0, 1, 2],
3242         [3, 4, 5]
3243     ].fuse;
3244 
3245     auto s1 = x.byDim!0.vmap(A).fuse;
3246     assert(s1 == [[ 0, 2,  6],
3247                   [ 3, 8, 15]]);
3248     auto s2 = x.byDim!1.vmap(B).fuse!1;
3249     assert(s2 == [[ 0, 1,  2],
3250                   [ 6, 8, 10]]);
3251     auto s3 = x.alongDim!1.vmap(A).fuse;
3252     assert(s1 == [[ 0, 2,  6],
3253                   [ 3, 8, 15]]);
3254     auto s4 = x.alongDim!0.vmap(B).fuse!1;
3255     assert(s2 == [[ 0, 1,  2],
3256                   [ 6, 8, 10]]);
3257 }
3258 
3259 private auto hideStride
3260     (Iterator, SliceKind kind)
3261     (Slice!(Iterator, 1, kind) slice)
3262 {
3263     import core.lifetime: move;
3264     static if (kind == Universal)
3265         return Slice!(StrideIterator!Iterator)(
3266             slice._lengths,
3267             StrideIterator!Iterator(slice._strides[0], move(slice._iterator)));
3268     else
3269         return slice;
3270 }
3271 
3272 private auto unhideStride
3273     (Iterator, size_t N, SliceKind kind)
3274     (Slice!(Iterator, N, kind) slice)
3275 {
3276     static if (is(Iterator : StrideIterator!It, It))
3277     {
3278         import core.lifetime: move;
3279         static if (kind == Universal)
3280         {
3281             alias Ret = SliceKind!(It, N, Universal);
3282             auto strides = slice._strides;
3283             foreach(i; Iota!(Ret.S))
3284                 strides[i] = slice._strides[i] * slice._iterator._stride;
3285             return Slice!(It, N, Universal)(slice._lengths, strides, slice._iterator._iterator.move);
3286         }
3287         else
3288             return slice.move.universal.unhideStride;
3289     }
3290     else
3291         return slice;
3292 }
3293 
3294 /++
3295 Implements the homonym function (also known as `transform`) present
3296 in many languages of functional flavor. The call `rmap!(fun)(slice)`
3297 returns an RC array (1D) or  RC slice (ND) of which elements are obtained by applying `fun`
3298 for all elements in `slice`. The original slices are
3299 not changed. Evaluation is done eagerly.
3300 
3301 Note:
3302     $(SUBREF dynamic, transposed) and
3303     $(SUBREF topology, pack) can be used to specify dimensions.
3304 Params:
3305     fun = One or more functions.
3306 See_Also:
3307     $(LREF cached), $(LREF map), $(LREF vmap), $(LREF indexed),
3308     $(LREF pairwise), $(LREF subSlices), $(LREF slide), $(LREF zip),
3309     $(HTTP en.wikipedia.org/wiki/Map_(higher-order_function), Map (higher-order function))
3310 +/
3311 template rcmap(fun...)
3312     if (fun.length)
3313 {
3314     import mir.functional: adjoin, naryFun, pipe;
3315     static if (fun.length == 1)
3316     {
3317         static if (__traits(isSame, naryFun!(fun[0]), fun[0]))
3318         {
3319             alias f = fun[0];
3320         @optmath:
3321             /++
3322             Params:
3323                 slice = An ndslice, array, or an input range.
3324             Returns:
3325                 ndslice or an input range with each fun applied to all the elements. If there is more than one
3326                 fun, the element type will be `Tuple` containing one element for each fun.
3327             +/
3328             auto rcmap(Iterator, size_t N, SliceKind kind)
3329                 (Slice!(Iterator, N, kind) slice)
3330             {
3331                 import core.lifetime: move;
3332                 auto shape = slice.shape;
3333                 auto r = slice.move.flattened;
3334                 if (false)
3335                 {
3336                     auto e = f(r.front);
3337                     r.popFront;
3338                     auto d = r.empty;
3339                 }
3340                 return () @trusted
3341                 {
3342                     import mir.rc.array: RCArray;
3343                     import std.traits: Unqual;
3344                     import mir.conv: emplaceRef;
3345 
3346                     alias T = typeof(f(r.front));
3347                     auto ret = RCArray!T(r.length);
3348                     auto next = ret.ptr;
3349                     while (!r.empty)
3350                     {
3351                         emplaceRef(*cast(Unqual!T*)next++, f(r.front));
3352                         r.popFront;
3353                     }
3354                     static if (N == 1)
3355                     {
3356                         return ret;
3357                     }
3358                     else
3359                     {
3360                         return ret.moveToSlice.sliced(shape);
3361                     }
3362                 } ();
3363             }
3364 
3365             /// ditto
3366             auto rcmap(T)(T[] array)
3367             {
3368                 return rcmap(array.sliced);
3369             }
3370 
3371             /// ditto
3372             auto rcmap(T)(T withAsSlice)
3373                 if (hasAsSlice!T)
3374             {
3375                 static if (__traits(hasMember, T, "moveToSlice"))
3376                     return rcmap(withAsSlice.moveToSlice);
3377                 else
3378                     return rcmap(withAsSlice.asSlice);
3379             }
3380 
3381             /// ditto
3382             auto rcmap(Range)(Range r)
3383                 if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T))
3384             {
3385                 import core.lifetime: forward;
3386                 import mir.appender: scopedBuffer;
3387                 import mir.primitives: isInputRange;
3388                 import mir.rc.array: RCArray;
3389                 alias T = typeof(f(r.front));
3390                 auto buffer = scopedBuffer!T;
3391                 while (!r.empty)
3392                 {
3393                     buffer.put(f(r.front));
3394                     r.popFront;
3395                 }
3396                 return () @trusted
3397                 {
3398                     auto ret = RCArray!T(buffer.length, false);
3399                     buffer.moveDataAndEmplaceTo(ret[]);
3400                     return ret;
3401                 } ();
3402             }
3403         }
3404         else alias rcmap = .rcmap!(staticMap!(naryFun, fun));
3405     }
3406     else alias rcmap = .rcmap!(adjoin!fun);
3407 }
3408 
3409 /// Returns RCArray for input ranges and one-dimensional slices.
3410 @safe pure nothrow @nogc
3411 version(mir_test) unittest
3412 {
3413     import mir.algorithm.iteration: filter, equal;
3414     auto factor = 10;
3415     auto step = 20;
3416     assert (3.iota.rcmap!(a => a * factor).moveToSlice.equal(3.iota * factor)); 
3417     assert (6.iota.filter!"a % 2".rcmap!(a => a * factor).moveToSlice.equal([3].iota(factor, step))); 
3418 }
3419 
3420 /// For multidimensional case returns `Slice!(RCI!T, N)`.
3421 @safe pure nothrow @nogc
3422 version(mir_test) unittest
3423 {
3424     import mir.ndslice.topology : iota;
3425     auto factor = 3;
3426     auto s = iota(2, 3).rcmap!(a => a * factor);
3427     assert(s == iota(2, 3) * factor);
3428 }
3429 
3430 /// String lambdas
3431 @safe pure nothrow
3432 version(mir_test) unittest
3433 {
3434     import mir.ndslice.topology : iota;
3435     assert(iota(2, 3).rcmap!"a * 2" == iota(2, 3) * 2);
3436 }
3437 
3438 @safe pure nothrow @nogc
3439 version(mir_test) unittest
3440 {
3441     import mir.algorithm.iteration: filter, equal;
3442     auto factor = 10;
3443     auto step = 20;
3444     assert (3.iota.as!(const int).rcmap!(a => a * factor).moveToSlice.equal(3.iota * factor)); 
3445     assert (6.iota.filter!"a % 2".as!(immutable int).rcmap!(a => a * factor).moveToSlice.equal([3].iota(factor, step))); 
3446 }
3447 
3448 /++
3449 Creates a random access cache for lazyly computed elements.
3450 Params:
3451     original = original ndslice
3452     caches = cached values
3453     flags = array composed of flags that indicates if values are already computed
3454 Returns:
3455     ndslice, which is internally composed of three ndslices: `original`, allocated cache and allocated bit-ndslice.
3456 See_also: $(LREF cachedGC), $(LREF map), $(LREF vmap), $(LREF indexed)
3457 +/
3458 Slice!(CachedIterator!(Iterator, CacheIterator, FlagIterator), N, kind)
3459     cached(Iterator, SliceKind kind, size_t N, CacheIterator, FlagIterator)(
3460         Slice!(Iterator, N, kind) original,
3461         Slice!(CacheIterator, N, kind) caches,
3462         Slice!(FlagIterator, N, kind) flags,
3463     )
3464 {
3465     assert(original.shape == caches.shape, "caches.shape should be equal to original.shape");
3466     assert(original.shape == flags.shape, "flags.shape should be equal to original.shape");
3467     return typeof(return)(
3468         original._structure,
3469         IteratorOf!(typeof(return))(
3470             original._iterator,
3471             caches._iterator,
3472             flags._iterator,
3473         ));
3474 }
3475 
3476 ///
3477 @safe pure nothrow
3478 version(mir_test) unittest
3479 {
3480     import mir.ndslice.topology: cached, iota, map;
3481     import mir.ndslice.allocation: bitSlice, uninitSlice;
3482 
3483     int[] funCalls;
3484 
3485     auto v = 5.iota!int
3486         .map!((i) {
3487             funCalls ~= i;
3488             return 2 ^^ i;
3489         });
3490     auto flags = v.length.bitSlice;
3491     auto cache = v.length.uninitSlice!int;
3492     // cached lazy slice: 1 2 4 8 16
3493     auto sl = v.cached(cache, flags);
3494 
3495     assert(funCalls == []);
3496     assert(sl[1] == 2); // remember result
3497     assert(funCalls == [1]);
3498     assert(sl[1] == 2); // reuse result
3499     assert(funCalls == [1]);
3500 
3501     assert(sl[0] == 1);
3502     assert(funCalls == [1, 0]);
3503     funCalls = [];
3504 
3505     // set values directly
3506     sl[1 .. 3] = 5;
3507     assert(sl[1] == 5);
3508     assert(sl[2] == 5);
3509     // no function calls
3510     assert(funCalls == []);
3511 }
3512 
3513 /// Cache of immutable elements
3514 @safe pure nothrow
3515 version(mir_test) unittest
3516 {
3517     import mir.ndslice.slice: DeepElementType;
3518     import mir.ndslice.topology: cached, iota, map, as;
3519     import mir.ndslice.allocation: bitSlice, uninitSlice;
3520 
3521     int[] funCalls;
3522 
3523     auto v = 5.iota!int
3524         .map!((i) {
3525             funCalls ~= i;
3526             return 2 ^^ i;
3527         })
3528         .as!(immutable int);
3529     auto flags = v.length.bitSlice;
3530     auto cache = v.length.uninitSlice!(immutable int);
3531 
3532     // cached lazy slice: 1 2 4 8 16
3533     auto sl = v.cached(cache, flags);
3534 
3535     static assert(is(DeepElementType!(typeof(sl)) == immutable int));
3536 
3537     assert(funCalls == []);
3538     assert(sl[1] == 2); // remember result
3539     assert(funCalls == [1]);
3540     assert(sl[1] == 2); // reuse result
3541     assert(funCalls == [1]);
3542 
3543     assert(sl[0] == 1);
3544     assert(funCalls == [1, 0]);
3545 }
3546 
3547 /++
3548 Creates a random access cache for lazyly computed elements.
3549 Params:
3550     original = ND Contiguous or 1D Universal ndslice.
3551 Returns:
3552     ndslice, which is internally composed of three ndslices: `original`, allocated cache and allocated bit-ndslice.
3553 See_also: $(LREF cached), $(LREF map), $(LREF vmap), $(LREF indexed)
3554 +/
3555 Slice!(CachedIterator!(Iterator, typeof(Iterator.init[0])*, FieldIterator!(BitField!(size_t*))), N)
3556     cachedGC(Iterator, size_t N)(Slice!(Iterator, N) original) @trusted
3557 {
3558     import std.traits: hasElaborateAssign, Unqual;
3559     import mir.ndslice.allocation: bitSlice, slice, uninitSlice;
3560     alias C = typeof(Iterator.init[0]);
3561     alias UC = Unqual!C;
3562     static if (hasElaborateAssign!UC)
3563         alias newSlice = slice;
3564     else
3565         alias newSlice = uninitSlice;
3566     return typeof(return)(
3567         original._structure,
3568         IteratorOf!(typeof(return))(
3569             original._iterator,
3570             newSlice!C(original._lengths)._iterator,
3571             original._lengths.bitSlice._iterator,
3572             ));
3573 }
3574 
3575 /// ditto
3576 auto cachedGC(Iterator)(Slice!(Iterator,  1, Universal) from)
3577 {
3578     return from.flattened.cachedGC;
3579 }
3580 
3581 /// ditto
3582 auto cachedGC(T)(T withAsSlice)
3583     if (hasAsSlice!T)
3584 {
3585     return cachedGC(withAsSlice.asSlice);
3586 }
3587 
3588 ///
3589 @safe pure nothrow
3590 version(mir_test) unittest
3591 {
3592     import mir.ndslice.topology: cachedGC, iota, map;
3593 
3594     int[] funCalls;
3595 
3596     // cached lazy slice: 1 2 4 8 16
3597     auto sl = 5.iota!int
3598         .map!((i) {
3599             funCalls ~= i;
3600             return 2 ^^ i;
3601         })
3602         .cachedGC;
3603 
3604     assert(funCalls == []);
3605     assert(sl[1] == 2); // remember result
3606     assert(funCalls == [1]);
3607     assert(sl[1] == 2); // reuse result
3608     assert(funCalls == [1]);
3609 
3610     assert(sl[0] == 1);
3611     assert(funCalls == [1, 0]);
3612     funCalls = [];
3613 
3614     // set values directly
3615     sl[1 .. 3] = 5;
3616     assert(sl[1] == 5);
3617     assert(sl[2] == 5);
3618     // no function calls
3619     assert(funCalls == []);
3620 }
3621 
3622 /// Cache of immutable elements
3623 @safe pure nothrow
3624 version(mir_test) unittest
3625 {
3626     import mir.ndslice.slice: DeepElementType;
3627     import mir.ndslice.topology: cachedGC, iota, map, as;
3628 
3629     int[] funCalls;
3630 
3631     // cached lazy slice: 1 2 4 8 16
3632     auto sl = 5.iota!int
3633         .map!((i) {
3634             funCalls ~= i;
3635             return 2 ^^ i;
3636         })
3637         .as!(immutable int)
3638         .cachedGC;
3639 
3640     static assert(is(DeepElementType!(typeof(sl)) == immutable int));
3641 
3642     assert(funCalls == []);
3643     assert(sl[1] == 2); // remember result
3644     assert(funCalls == [1]);
3645     assert(sl[1] == 2); // reuse result
3646     assert(funCalls == [1]);
3647 
3648     assert(sl[0] == 1);
3649     assert(funCalls == [1, 0]);
3650 }
3651 
3652 /++
3653 Convenience function that creates a lazy view,
3654 where each element of the original slice is converted to the type `T`.
3655 It uses $(LREF  map) and $(REF_ALTTEXT $(TT to), to, mir,conv)$(NBSP)
3656 composition under the hood.
3657 Params:
3658     slice = a slice to create a view on.
3659 Returns:
3660     A lazy slice with elements converted to the type `T`.
3661 See_also: $(LREF map), $(LREF vmap)
3662 +/
3663 template as(T)
3664 {
3665     ///
3666     @optmath auto as(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice)
3667     {
3668         static if (is(slice.DeepElement == T))
3669             return slice;
3670         else
3671         static if (is(Iterator : T*))
3672             return slice.toConst;
3673         else
3674         {
3675             import core.lifetime: move;
3676             import mir.conv: to;
3677             return map!(to!T)(slice.move);
3678         }
3679     }
3680 
3681     /// ditto
3682     auto as(S)(S[] array)
3683     {
3684         return as(array.sliced);
3685     }
3686 
3687     /// ditto
3688     auto as(S)(S withAsSlice)
3689         if (hasAsSlice!S)
3690     {
3691         return as(withAsSlice.asSlice);
3692     }
3693 
3694     /// ditto
3695     auto as(Range)(Range r)
3696         if (!hasAsSlice!Range && !isSlice!Range && !is(Range : T[], T))
3697     {
3698         static if (is(ForeachType!Range == T))
3699             return r;
3700         else
3701         {
3702             import core.lifetime: move;
3703             import mir.conv: to;
3704             return map!(to!T)(r.move);
3705         }
3706     }
3707 }
3708 
3709 ///
3710 @safe pure nothrow version(mir_test) unittest
3711 {
3712     import mir.ndslice.slice: Slice;
3713     import mir.ndslice.allocation : slice;
3714     import mir.ndslice.topology : diagonal, as;
3715 
3716     auto matrix = slice!double([2, 2], 0);
3717     auto stringMatrixView = matrix.as!int;
3718     assert(stringMatrixView ==
3719             [[0, 0],
3720              [0, 0]]);
3721 
3722     matrix.diagonal[] = 1;
3723     assert(stringMatrixView ==
3724             [[1, 0],
3725              [0, 1]]);
3726 
3727     /// allocate new slice composed of strings
3728     Slice!(int*, 2) stringMatrix = stringMatrixView.slice;
3729 }
3730 
3731 /// Special behavior for pointers to a constant data.
3732 @safe pure nothrow version(mir_test) unittest
3733 {
3734     import mir.ndslice.allocation : slice;
3735     import mir.ndslice.slice: Contiguous, Slice;
3736 
3737     Slice!(double*, 2)              matrix = slice!double([2, 2], 0);
3738     Slice!(const(double)*, 2) const_matrix = matrix.as!(const double);
3739 }
3740 
3741 /// Ranges
3742 @safe pure nothrow version(mir_test) unittest
3743 {
3744     import mir.algorithm.iteration: filter, equal;
3745     assert(5.iota.filter!"a % 2".as!double.map!"a / 2".equal([0.5, 1.5]));
3746 }
3747 
3748 /++
3749 Takes a field `source` and a slice `indices`, and creates a view of source as if its elements were reordered according to indices.
3750 `indices` may include only a subset of the elements of `source` and may also repeat elements.
3751 
3752 Params:
3753     source = a filed, source of data. `source` must be an array or a pointer, or have `opIndex` primitive. Full random access range API is not required.
3754     indices = a slice, source of indices.
3755 Returns:
3756     n-dimensional slice with the same kind, shape and strides.
3757 
3758 See_also: `indexed` is similar to $(LREF vmap), but a field (`[]`) is used instead of a function (`()`), and order of arguments is reversed.
3759 +/
3760 Slice!(IndexIterator!(Iterator, Field), N, kind)
3761     indexed(Field, Iterator, size_t N, SliceKind kind)
3762     (Field source, Slice!(Iterator, N, kind) indices)
3763 {
3764     import core.lifetime: move;
3765     return typeof(return)(
3766             indices._structure,
3767             IndexIterator!(Iterator, Field)(
3768                 indices._iterator.move,
3769                 source));
3770 }
3771 
3772 /// ditto
3773 auto indexed(Field, S)(Field source, S[] indices)
3774 {
3775     return indexed(source, indices.sliced);
3776 }
3777 
3778 /// ditto
3779 auto indexed(Field, S)(Field source, S indices)
3780     if (hasAsSlice!S)
3781 {
3782     return indexed(source, indices.asSlice);
3783 }
3784 
3785 ///
3786 @safe pure nothrow version(mir_test) unittest
3787 {
3788     auto source = [1, 2, 3, 4, 5];
3789     auto indices = [4, 3, 1, 2, 0, 4];
3790     auto ind = source.indexed(indices);
3791     assert(ind == [5, 4, 2, 3, 1, 5]);
3792 
3793     assert(ind.retro == source.indexed(indices.retro));
3794 
3795     ind[3] += 10; // for index 2
3796     //                0  1   2  3  4
3797     assert(source == [1, 2, 13, 4, 5]);
3798 }
3799 
3800 /++
3801 Maps index pairs to subslices.
3802 Params:
3803     sliceable = pointer, array, ndslice, series, or something sliceable with `[a .. b]`.
3804     slices = ndslice composed of index pairs.
3805 Returns:
3806     ndslice composed of subslices.
3807 See_also: $(LREF chopped), $(LREF pairwise).
3808 +/
3809 Slice!(SubSliceIterator!(Iterator, Sliceable), N, kind)
3810     subSlices(Iterator, size_t N, SliceKind kind, Sliceable)(
3811         Sliceable sliceable,
3812         Slice!(Iterator, N, kind) slices,
3813     )
3814 {
3815     import core.lifetime: move;
3816     return typeof(return)(
3817         slices._structure,
3818         SubSliceIterator!(Iterator, Sliceable)(slices._iterator.move, sliceable.move)
3819     );
3820 }
3821 
3822 /// ditto
3823 auto subSlices(S, Sliceable)(Sliceable sliceable, S[] slices)
3824 {
3825     return subSlices(sliceable, slices.sliced);
3826 }
3827 
3828 /// ditto
3829 auto subSlices(S, Sliceable)(Sliceable sliceable, S slices)
3830     if (hasAsSlice!S)
3831 {
3832     return subSlices(sliceable, slices.asSlice);
3833 }
3834 
3835 ///
3836 @safe pure version(mir_test) unittest
3837 {
3838     import mir.functional: staticArray;
3839     auto subs =[
3840             staticArray(2, 4),
3841             staticArray(2, 10),
3842         ];
3843     auto sliceable = 10.iota;
3844 
3845     auto r = sliceable.subSlices(subs);
3846     assert(r == [
3847         iota([4 - 2], 2),
3848         iota([10 - 2], 2),
3849         ]);
3850 }
3851 
3852 /++
3853 Maps index pairs to subslices.
3854 Params:
3855     bounds = ndslice composed of consequent (`a_i <= a_(i+1)`) pairwise index bounds.
3856     sliceable = pointer, array, ndslice, series, or something sliceable with `[a_i .. a_(i+1)]`.
3857 Returns:
3858     ndslice composed of subslices.
3859 See_also: $(LREF pairwise), $(LREF subSlices).
3860 +/
3861 Slice!(ChopIterator!(Iterator, Sliceable)) chopped(Iterator, Sliceable)(
3862         Sliceable sliceable,
3863         Slice!Iterator bounds,
3864     )
3865 in
3866 {
3867     debug(mir)
3868         foreach(b; bounds.pairwise!"a <= b")
3869             assert(b);
3870 }
3871 do {
3872     import core.lifetime: move;
3873     sizediff_t length = bounds._lengths[0] <= 1 ? 0 : bounds._lengths[0] - 1;
3874     static if (hasLength!Sliceable)
3875     {
3876         if (length && bounds[length - 1] > sliceable.length)
3877         {
3878             version (D_Exceptions)
3879                 throw choppedException;
3880             else
3881                assert(0, choppedExceptionMsg);
3882         }
3883     }
3884 
3885     return typeof(return)([size_t(length)], ChopIterator!(Iterator, Sliceable)(bounds._iterator.move, sliceable.move));
3886 }
3887 
3888 /// ditto
3889 auto chopped(S, Sliceable)(Sliceable sliceable, S[] bounds)
3890 {
3891     return chopped(sliceable, bounds.sliced);
3892 }
3893 
3894 /// ditto
3895 auto chopped(S, Sliceable)(Sliceable sliceable, S bounds)
3896     if (hasAsSlice!S)
3897 {
3898     return chopped(sliceable, bounds.asSlice);
3899 }
3900 
3901 ///
3902 @safe pure version(mir_test) unittest
3903 {
3904     import mir.functional: staticArray;
3905     import mir.ndslice.slice: sliced;
3906     auto pairwiseIndexes = [2, 4, 10].sliced;
3907     auto sliceable = 10.iota;
3908 
3909     auto r = sliceable.chopped(pairwiseIndexes);
3910     assert(r == [
3911         iota([4 - 2], 2),
3912         iota([10 - 4], 4),
3913         ]);
3914 }
3915 
3916 /++
3917 Groups slices into a slice of refTuples. The slices must have identical strides or be 1-dimensional.
3918 Params:
3919     sameStrides = if `true` assumes that all slices has the same strides.
3920     slices = list of slices
3921 Returns:
3922     n-dimensional slice of elements refTuple
3923 See_also: $(SUBREF slice, Slice.strides).
3924 +/
3925 template zip(bool sameStrides = false)
3926 {
3927     /++
3928     Groups slices into a slice of refTuples. The slices must have identical strides or be 1-dimensional.
3929     Params:
3930         slices = list of slices
3931     Returns:
3932         n-dimensional slice of elements refTuple
3933     See_also: $(SUBREF slice, Slice.strides).
3934     +/
3935     @optmath
3936     auto zip(Slices...)(Slices slices)
3937         if (Slices.length > 1 && allSatisfy!(isConvertibleToSlice, Slices))
3938     {
3939         static if (allSatisfy!(isSlice, Slices))
3940         {
3941             enum N = Slices[0].N;
3942             foreach(i, S; Slices[1 .. $])
3943             {
3944                 static assert(S.N == N, "zip: all Slices must have the same dimension count");
3945                 assert(slices[i + 1]._lengths == slices[0]._lengths, "zip: all slices must have the same lengths");
3946                 static if (sameStrides)
3947                     assert(slices[i + 1].strides == slices[0].strides, "zip: all slices must have the same strides when unpacked");
3948             }
3949             static if (!sameStrides && minElem(staticMap!(kindOf, Slices)) != Contiguous)
3950             {
3951                 static assert(N == 1, "zip: cannot zip canonical and universal multidimensional slices if `sameStrides` is false");
3952                 mixin(`return .zip(` ~ _iotaArgs!(Slices.length, "slices[", "].hideStride, ") ~`);`);
3953             }
3954             else
3955             {
3956                 enum kind = maxElem(staticMap!(kindOf, Slices));
3957                 alias Iterator = ZipIterator!(staticMap!(_IteratorOf, Slices));
3958                 alias Ret = Slice!(Iterator, N, kind);
3959                 auto structure = Ret._Structure.init;
3960                 structure[0] = slices[0]._lengths;
3961                 foreach (i; Iota!(Ret.S))
3962                     structure[1][i] = slices[0]._strides[i];
3963                 return Ret(structure, mixin("Iterator(" ~ _iotaArgs!(Slices.length, "slices[", "]._iterator, ") ~ ")"));
3964             }
3965         }
3966         else
3967         {
3968             return .zip(toSlices!slices);
3969         }
3970     }
3971 }
3972 
3973 ///
3974 @safe pure nothrow version(mir_test) unittest
3975 {
3976     import mir.ndslice.allocation : slice;
3977     import mir.ndslice.topology : flattened, iota;
3978 
3979     auto alpha = iota!int(4, 3);
3980     auto beta = slice!int(4, 3).universal;
3981 
3982     auto m = zip!true(alpha, beta);
3983     foreach (r; m)
3984         foreach (e; r)
3985             e.b = e.a;
3986     assert(alpha == beta);
3987 
3988     beta[] = 0;
3989     foreach (e; m.flattened)
3990         e.b = cast(int)e.a;
3991     assert(alpha == beta);
3992 }
3993 
3994 @safe pure nothrow version(mir_test) unittest
3995 {
3996     import mir.ndslice.allocation : slice;
3997     import mir.ndslice.topology : flattened, iota;
3998 
3999     auto alpha = iota!int(4).universal;
4000     auto beta = new int[4];
4001 
4002     auto m = zip(alpha, beta);
4003     foreach (e; m)
4004         e.b = e.a;
4005     assert(alpha == beta);
4006 }
4007 
4008 /++
4009 Selects a slice from a zipped slice.
4010 Params:
4011     name = name of a slice to unzip.
4012     slice = zipped slice
4013 Returns:
4014     unzipped slice
4015 +/
4016 auto unzip
4017     (char name, size_t N, SliceKind kind, Iterators...)
4018     (Slice!(ZipIterator!Iterators, N, kind) slice)
4019 {
4020     import core.lifetime: move;
4021     enum size_t i = name - 'a';
4022     static assert(i < Iterators.length, `unzip: constraint: size_t(name - 'a') < Iterators.length`);
4023     return Slice!(Iterators[i], N, kind)(slice._structure, slice._iterator._iterators[i].move).unhideStride;
4024 }
4025 
4026 /// ditto
4027 auto unzip
4028     (char name, size_t N, SliceKind kind, Iterators...)
4029     (ref Slice!(ZipIterator!Iterators, N, kind) slice)
4030 {
4031     enum size_t i = name - 'a';
4032     static assert(i < Iterators.length, `unzip: constraint: size_t(name - 'a') < Iterators.length`);
4033     return Slice!(Iterators[i], N, kind)(slice._structure, slice._iterator._iterators[i]).unhideStride;
4034 }
4035 
4036 ///
4037 pure nothrow version(mir_test) unittest
4038 {
4039     import mir.ndslice.allocation : slice;
4040     import mir.ndslice.topology : iota;
4041 
4042     auto alpha = iota!int(4, 3);
4043     auto beta = iota!int([4, 3], 1).slice;
4044 
4045     auto m = zip(alpha, beta);
4046 
4047     static assert(is(typeof(unzip!'a'(m)) == typeof(alpha)));
4048     static assert(is(typeof(unzip!'b'(m)) == typeof(beta)));
4049 
4050     assert(m.unzip!'a' == alpha);
4051     assert(m.unzip!'b' == beta);
4052 }
4053 
4054 private enum TotalDim(NdFields...) = [staticMap!(DimensionCount, NdFields)].sum;
4055 
4056 private template applyInner(alias fun, size_t N)
4057 {
4058     static if (N == 0)
4059         alias applyInner = fun;
4060     else
4061     {
4062         import mir.functional: pipe;
4063         alias applyInner = pipe!(zip!true, map!(.applyInner!(fun, N - 1)));
4064     }
4065 }
4066 
4067 /++
4068 Lazy convolution for tensors.
4069 
4070 Suitable for advanced convolution algorithms.
4071 
4072 Params:
4073     params = convolution windows length.
4074     fun = one dimensional convolution function with `params` arity.
4075     SDimensions = dimensions to perform lazy convolution along. Negative dimensions are supported.
4076 See_also: $(LREF slide), $(LREF pairwise), $(LREF diff).
4077 +/
4078 template slideAlong(size_t params, alias fun, SDimensions...)
4079     if (params <= 'z' - 'a' + 1 && SDimensions.length > 0)
4080 {
4081     import mir.functional: naryFun;
4082 
4083     static if (allSatisfy!(isSizediff_t, SDimensions) && params > 1 && __traits(isSame, naryFun!fun, fun))
4084     {
4085     @optmath:
4086         /++
4087         Params: slice = ndslice or array
4088         Returns: lazy convolution result
4089         +/
4090         auto slideAlong(Iterator, size_t N, SliceKind kind)
4091             (Slice!(Iterator, N, kind) slice)
4092         {
4093             import core.lifetime: move;
4094             static if (N > 1 && kind == Contiguous)
4095             {
4096                 return slideAlong(slice.move.canonical);
4097             }
4098             else
4099             static if (N == 1 && kind == Universal)
4100             {
4101                 return slideAlong(slice.move.flattened);
4102             }
4103             else
4104             {
4105                 alias Dimensions = staticMap!(ShiftNegativeWith!N, SDimensions);
4106                 enum dimension = Dimensions[$ - 1];
4107                 size_t len = slice._lengths[dimension] - (params - 1);
4108                 if (sizediff_t(len) <= 0) // overfow
4109                     len = 0;
4110                 slice._lengths[dimension] = len;
4111                 static if (dimension + 1 == N || kind == Universal)
4112                 {
4113                     alias I = SlideIterator!(Iterator, params, fun);
4114                     auto ret = Slice!(I, N, kind)(slice._structure, I(move(slice._iterator)));
4115                 }
4116                 else
4117                 {
4118                     alias Z = ZipIterator!(Repeat!(params, Iterator));
4119                     Z z;
4120                     foreach_reverse (p; Iota!(1, params))
4121                         z._iterators[p] = slice._iterator + slice._strides[dimension] * p;
4122                     z._iterators[0] = move(slice._iterator);
4123                     alias M = MapIterator!(Z, fun);
4124                     auto ret = Slice!(M, N, kind)(slice._structure, M(move(z)));
4125                 }
4126                 static if (Dimensions.length == 1)
4127                 {
4128                     return ret;
4129                 }
4130                 else
4131                 {
4132                     return .slideAlong!(params, fun, Dimensions[0 .. $ - 1])(ret);
4133                 }
4134             }
4135         }
4136 
4137         /// ditto
4138         auto slideAlong(S)(S[] slice)
4139         {
4140             return slideAlong(slice.sliced);
4141         }
4142 
4143         /// ditto
4144         auto slideAlong(S)(S slice)
4145             if (hasAsSlice!S)
4146         {
4147             return slideAlong(slice.asSlice);
4148         }
4149     }
4150     else
4151     static if (params == 1)
4152         alias slideAlong = .map!(naryFun!fun);
4153     else alias slideAlong = .slideAlong!(params, naryFun!fun, staticMap!(toSizediff_t, SDimensions));
4154 }
4155 
4156 ///
4157 @safe pure nothrow @nogc version(mir_test) unittest
4158 {
4159     auto data = [4, 5].iota;
4160 
4161     alias scaled = a => a * 0.25;
4162 
4163     auto v = data.slideAlong!(3, "a + 2 * b + c", 0).map!scaled;
4164     auto h = data.slideAlong!(3, "a + 2 * b + c", 1).map!scaled;
4165 
4166     assert(v == [4, 5].iota[1 .. $ - 1, 0 .. $]);
4167     assert(h == [4, 5].iota[0 .. $, 1 .. $ - 1]);
4168 }
4169 
4170 /++
4171 Lazy convolution for tensors.
4172 
4173 Suitable for simple convolution algorithms.
4174 
4175 Params:
4176     params = windows length.
4177     fun = one dimensional convolution function with `params` arity.
4178 See_also: $(LREF slideAlong), $(LREF withNeighboursSum), $(LREF pairwise), $(LREF diff).
4179 +/
4180 template slide(size_t params, alias fun)
4181     if (params <= 'z' - 'a' + 1)
4182 {
4183     import mir.functional: naryFun;
4184 
4185     static if (params > 1 && __traits(isSame, naryFun!fun, fun))
4186     {
4187     @optmath:
4188         /++
4189         Params: slice = ndslice or array
4190         Returns: lazy convolution result
4191         +/
4192         auto slide(Iterator, size_t N, SliceKind kind)
4193             (Slice!(Iterator, N, kind) slice)
4194         {
4195             import core.lifetime: move;
4196             return slice.move.slideAlong!(params, fun, Iota!N);
4197         }
4198 
4199         /// ditto
4200         auto slide(S)(S[] slice)
4201         {
4202             return slide(slice.sliced);
4203         }
4204 
4205         /// ditto
4206         auto slide(S)(S slice)
4207             if (hasAsSlice!S)
4208         {
4209             return slide(slice.asSlice);
4210         }
4211     }
4212     else
4213     static if (params == 1)
4214         alias slide = .map!(naryFun!fun);
4215     else alias slide = .slide!(params, naryFun!fun);
4216 }
4217 
4218 ///
4219 version(mir_test) unittest
4220 {
4221     auto data = 10.iota;
4222     auto sw = data.slide!(3, "a + 2 * b + c");
4223 
4224     import mir.utility: max;
4225     assert(sw.length == max(0, cast(ptrdiff_t)data.length - 3 + 1));
4226     assert(sw == sw.length.iota.map!"(a + 1) * 4");
4227     assert(sw == [4, 8, 12, 16, 20, 24, 28, 32]);
4228 }
4229 
4230 /++
4231 ND-use case
4232 +/
4233 @safe pure nothrow @nogc version(mir_test) unittest
4234 {
4235     auto data = [4, 5].iota;
4236 
4237     enum factor = 1.0 / 4 ^^ data.shape.length;
4238     alias scaled = a => a * factor;
4239 
4240     auto sw = data.slide!(3, "a + 2 * b + c").map!scaled;
4241 
4242     assert(sw == [4, 5].iota[1 .. $ - 1, 1 .. $ - 1]);
4243 }
4244 
4245 /++
4246 Pairwise map for tensors.
4247 
4248 The computation is performed on request, when the element is accessed.
4249 
4250 Params:
4251     fun = function to accumulate
4252     lag = an integer indicating which lag to use
4253 Returns: lazy ndslice composed of `fun(a_n, a_n+1)` values.
4254 
4255 See_also: $(LREF slide), $(LREF slideAlong), $(LREF subSlices).
4256 +/
4257 alias pairwise(alias fun, size_t lag = 1) = slide!(lag + 1, fun);
4258 
4259 ///
4260 @safe pure nothrow version(mir_test) unittest
4261 {
4262     import mir.ndslice.slice: sliced;
4263     assert([2, 4, 3, -1].sliced.pairwise!"a + b" == [6, 7, 2]);
4264 }
4265 
4266 /// N-dimensional
4267 @safe pure nothrow
4268 version(mir_test) unittest
4269 {
4270     // performs pairwise along each dimension
4271     // 0 1 2 3
4272     // 4 5 6 7
4273     // 8 9 10 11
4274     assert([3, 4].iota.pairwise!"a + b" == [[10, 14, 18], [26, 30, 34]]);
4275 }
4276 
4277 /++
4278 Differences between tensor elements.
4279 
4280 The computation is performed on request, when the element is accessed.
4281 
4282 Params:
4283     lag = an integer indicating which lag to use
4284 Returns: lazy differences.
4285 
4286 See_also: $(LREF slide), $(LREF slide).
4287 +/
4288 alias diff(size_t lag = 1) = pairwise!(('a' + lag) ~ " - a", lag);
4289 
4290 ///
4291 version(mir_test) unittest
4292 {
4293     import mir.ndslice.slice: sliced;
4294     assert([2, 4, 3, -1].sliced.diff == [2, -1, -4]);
4295 }
4296 
4297 /// N-dimensional
4298 @safe pure nothrow @nogc
4299 version(mir_test) unittest
4300 {
4301     // 0 1 2 3
4302     // 4 5 6 7     =>
4303     // 8 9 10 11
4304     
4305     // 1 1 1
4306     // 1 1 1      =>
4307     // 1 1 1
4308 
4309     // 0 0 0
4310     // 0 0 0
4311 
4312     assert([3, 4].iota.diff == repeat(0, [2, 3]));
4313 }
4314 
4315 /// packed slices
4316 version(mir_test) unittest
4317 {
4318     // 0 1  2  3
4319     // 4 5  6  7
4320     // 8 9 10 11
4321     auto s = iota(3, 4);
4322     import std.stdio;
4323     assert(iota(3, 4).byDim!0.diff == [
4324         [4, 4, 4, 4],
4325         [4, 4, 4, 4]]);
4326     assert(iota(3, 4).byDim!1.diff == [
4327         [1, 1, 1],
4328         [1, 1, 1],
4329         [1, 1, 1]]);
4330 }
4331 
4332 /++
4333 Drops borders for all dimensions.
4334 
4335 Params:
4336     slice = ndslice
4337 Returns:
4338     Tensors with striped borders
4339 See_also:
4340     $(LREF universal),
4341     $(LREF assumeCanonical),
4342     $(LREF assumeContiguous).
4343 +/
4344 Slice!(Iterator, N, N > 1 && kind == Contiguous ? Canonical : kind, Labels)
4345     dropBorders
4346     (Iterator, size_t N, SliceKind kind, Labels...)
4347     (Slice!(Iterator, N, kind, Labels) slice)
4348 {
4349     static if (N > 1 && kind == Contiguous)
4350     {
4351         import core.lifetime: move;
4352         auto ret = slice.move.canonical;
4353     }
4354     else
4355     {
4356         alias ret = slice;
4357     }
4358     ret.popFrontAll;
4359     ret.popBackAll;
4360     return ret;
4361 }
4362 
4363 ///
4364 version(mir_test) unittest
4365 {
4366     assert([4, 5].iota.dropBorders == [[6, 7, 8], [11, 12, 13]]);
4367 }
4368 
4369 /++
4370 Lazy zip view of elements packed with sum of their neighbours.
4371 
4372 Params:
4373     fun = neighbours accumulation function.
4374 See_also: $(LREF slide), $(LREF slideAlong).
4375 +/
4376 template withNeighboursSum(alias fun = "a + b")
4377 {
4378     import mir.functional: naryFun;
4379 
4380     static if (__traits(isSame, naryFun!fun, fun))
4381     {
4382     @optmath:
4383         /++
4384         Params:
4385             slice = ndslice or array
4386         Returns:
4387             Lazy zip view of elements packed with sum of their neighbours.
4388         +/
4389         auto withNeighboursSum(Iterator, size_t N, SliceKind kind)
4390             (Slice!(Iterator, N, kind) slice)
4391         {
4392             import core.lifetime: move;
4393             static if (N > 1 && kind == Contiguous)
4394             {
4395                 return withNeighboursSum(slice.move.canonical);
4396             }
4397             else
4398             static if (N == 1 && kind == Universal)
4399             {
4400                 return withNeighboursSum(slice.move.flattened);
4401             }
4402             else
4403             {
4404                 enum around = kind != Universal;
4405                 alias Z = NeighboursIterator!(Iterator, N - around, fun, around);
4406 
4407                 size_t shift;
4408                 foreach (dimension; Iota!N)
4409                 {
4410                     slice._lengths[dimension] -= 2;
4411                     if (sizediff_t(slice._lengths[dimension]) <= 0) // overfow
4412                         slice._lengths[dimension] = 0;
4413                     shift += slice._stride!dimension;
4414                 }                
4415 
4416                 Z z;
4417                 z._iterator = move(slice._iterator);
4418                 z._iterator += shift;
4419                 foreach (dimension; Iota!(N - around))
4420                 {
4421                     z._neighbours[dimension][0] = z._iterator - slice._strides[dimension];
4422                     z._neighbours[dimension][1] = z._iterator + slice._strides[dimension];
4423                 }
4424                 return Slice!(Z, N, kind)(slice._structure, move(z));
4425             }
4426         }
4427 
4428         /// ditto
4429         auto withNeighboursSum(S)(S[] slice)
4430         {
4431             return withNeighboursSum(slice.sliced);
4432         }
4433 
4434         /// ditto
4435         auto withNeighboursSum(S)(S slice)
4436             if (hasAsSlice!S)
4437         {
4438             return withNeighboursSum(slice.asSlice);
4439         }
4440     }
4441     else alias withNeighboursSum = .withNeighboursSum!(naryFun!fun);
4442 }
4443 
4444 ///
4445 @safe pure nothrow @nogc version(mir_test) unittest
4446 {
4447     import mir.ndslice.allocation: slice;
4448     import mir.algorithm.iteration: all;
4449 
4450     auto wn = [4, 5].iota.withNeighboursSum;
4451     assert(wn.all!"a[0] == a[1] * 0.25");
4452     assert(wn.map!"a" == wn.map!"b * 0.25");
4453 }
4454 
4455 @safe pure nothrow @nogc version(mir_test) unittest
4456 {
4457     import mir.ndslice.allocation: slice;
4458     import mir.algorithm.iteration: all;
4459 
4460     auto wn = [4, 5].iota.withNeighboursSum.universal;
4461     assert(wn.all!"a[0] == a[1] * 0.25");
4462     assert(wn.map!"a" == wn.map!"b * 0.25");
4463 }
4464 
4465 /++
4466 Cartesian product.
4467 
4468 Constructs lazy cartesian product $(SUBREF slice, Slice) without memory allocation.
4469 
4470 Params:
4471     fields = list of fields with lengths or ndFields with shapes
4472 Returns: $(SUBREF ndfield, Cartesian)`!NdFields(fields).`$(SUBREF slice, slicedNdField)`;`
4473 +/
4474 auto cartesian(NdFields...)(NdFields fields)
4475     if (NdFields.length > 1 && allSatisfy!(templateOr!(hasShape, hasLength), NdFields))
4476 {
4477     return Cartesian!NdFields(fields).slicedNdField;
4478 }
4479 
4480 /// 1D x 1D
4481 version(mir_test) unittest
4482 {
4483     auto a = [10, 20, 30];
4484     auto b = [ 1,  2,  3];
4485 
4486     auto c = cartesian(a, b)
4487         .map!"a + b";
4488 
4489     assert(c == [
4490         [11, 12, 13],
4491         [21, 22, 23],
4492         [31, 32, 33]]);
4493 }
4494 
4495 /// 1D x 2D
4496 version(mir_test) unittest
4497 {
4498     auto a = [10, 20, 30];
4499     auto b = iota([2, 3], 1);
4500 
4501     auto c = cartesian(a, b)
4502         .map!"a + b";
4503 
4504     assert(c.shape == [3, 2, 3]);
4505 
4506     assert(c == [
4507         [
4508             [11, 12, 13],
4509             [14, 15, 16],
4510         ],
4511         [
4512             [21, 22, 23],
4513             [24, 25, 26],
4514         ],
4515         [
4516             [31, 32, 33],
4517             [34, 35, 36],
4518         ]]);
4519 }
4520 
4521 /// 1D x 1D x 1D
4522 version(mir_test) unittest
4523 {
4524     auto u = [100, 200];
4525     auto v = [10, 20, 30];
4526     auto w = [1, 2];
4527 
4528     auto c = cartesian(u, v, w)
4529         .map!"a + b + c";
4530 
4531     assert(c.shape == [2, 3, 2]);
4532 
4533     assert(c == [
4534         [
4535             [111, 112],
4536             [121, 122],
4537             [131, 132],
4538         ],
4539         [
4540             [211, 212],
4541             [221, 222],
4542             [231, 232],
4543         ]]);
4544 }
4545 
4546 /++
4547 $(LINK2 https://en.wikipedia.org/wiki/Kronecker_product,  Kronecker product).
4548 
4549 Constructs lazy kronecker product $(SUBREF slice, Slice) without memory allocation.
4550 +/
4551 template kronecker(alias fun = product)
4552 {
4553     import mir.functional: naryFun;
4554     static if (__traits(isSame, naryFun!fun, fun))
4555 
4556     /++
4557     Params:
4558         fields = list of either fields with lengths or ndFields with shapes.
4559             All ndFields must have the same dimension count.
4560     Returns:
4561         $(SUBREF ndfield, Kronecker)`!(fun, NdFields)(fields).`$(SUBREF slice, slicedNdField)
4562     +/
4563     @optmath auto kronecker(NdFields...)(NdFields fields)
4564         if (allSatisfy!(hasShape, NdFields) || allSatisfy!(hasLength, NdFields))
4565     {
4566         return Kronecker!(fun, NdFields)(fields).slicedNdField;
4567     }
4568     else
4569         alias kronecker = .kronecker!(naryFun!fun);
4570 }
4571 
4572 /// 2D
4573 version(mir_test) unittest
4574 {
4575     import mir.ndslice.allocation: slice;
4576     import mir.ndslice.slice: sliced;
4577 
4578     // eye
4579     auto a = slice!double([4, 4], 0);
4580     a.diagonal[] = 1;
4581 
4582     auto b = [ 1, -1,
4583               -1,  1].sliced(2, 2);
4584 
4585     auto c = kronecker(a, b);
4586 
4587     assert(c == [
4588         [ 1, -1,  0,  0,  0,  0,  0,  0],
4589         [-1,  1,  0,  0,  0,  0,  0,  0],
4590         [ 0,  0,  1, -1,  0,  0,  0,  0],
4591         [ 0,  0, -1,  1,  0,  0,  0,  0],
4592         [ 0,  0,  0,  0,  1, -1,  0,  0],
4593         [ 0,  0,  0,  0, -1,  1,  0,  0],
4594         [ 0,  0,  0,  0,  0,  0,  1, -1],
4595         [ 0,  0,  0,  0,  0,  0, -1,  1]]);
4596 }
4597 
4598 /// 1D
4599 version(mir_test) unittest
4600 {
4601     auto a = iota([3], 1);
4602 
4603     auto b = [ 1, -1];
4604 
4605     auto c = kronecker(a, b);
4606 
4607     assert(c == [1, -1, 2, -2, 3, -3]);
4608 }
4609 
4610 /// 2D with 3 arguments
4611 version(mir_test) unittest
4612 {
4613     import mir.ndslice.allocation: slice;
4614     import mir.ndslice.slice: sliced;
4615 
4616     auto a = [ 1,  2,
4617                3,  4].sliced(2, 2);
4618 
4619     auto b = [ 1,  0,
4620                0,  1].sliced(2, 2);
4621 
4622     auto c = [ 1, -1,
4623               -1,  1].sliced(2, 2);
4624 
4625     auto d = kronecker(a, b, c);
4626 
4627     assert(d == [
4628         [ 1, -1,  0,  0,  2, -2,  0,  0],
4629         [-1,  1,  0,  0, -2,  2,  0,  0],
4630         [ 0,  0,  1, -1,  0,  0,  2, -2],
4631         [ 0,  0, -1,  1,  0,  0, -2,  2],
4632         [ 3, -3,  0,  0,  4, -4,  0,  0],
4633         [-3,  3,  0,  0, -4,  4,  0,  0],
4634         [ 0,  0,  3, -3,  0,  0,  4, -4],
4635         [ 0,  0, -3,  3,  0,  0, -4,  4]]);
4636 }
4637 
4638 /++
4639 $(HTTPS en.wikipedia.org/wiki/Magic_square, Magic square).
4640 Params:
4641     length = square matrix length.
4642 Returns:
4643     Lazy magic matrix.
4644 +/
4645 auto magic(size_t length)
4646 {
4647     assert(length > 0);
4648     static if (is(size_t == ulong))
4649         assert(length <= uint.max);
4650     else
4651         assert(length <= ushort.max);
4652     import mir.ndslice.field: MagicField;
4653     return MagicField(length).slicedField(length, length);
4654 }
4655 
4656 ///
4657 @safe pure nothrow
4658 version(mir_test) unittest
4659 {
4660     import mir.math.sum;
4661     import mir.ndslice: slice, magic, byDim, map, as, repeat, diagonal, antidiagonal;
4662 
4663     bool isMagic(S)(S matrix)
4664     {
4665         auto n = matrix.length;
4666         auto c = n * (n * n + 1) / 2; // magic number
4667         return // check shape
4668             matrix.length!0 > 0 && matrix.length!0 == matrix.length!1
4669             && // each row sum should equal magic number
4670             matrix.byDim!0.map!sum == c.repeat(n)
4671             && // each columns sum should equal magic number
4672             matrix.byDim!1.map!sum == c.repeat(n)
4673             && // diagonal sum should equal magic number
4674             matrix.diagonal.sum == c
4675             && // antidiagonal sum should equal magic number
4676             matrix.antidiagonal.sum == c;
4677     }
4678 
4679     assert(isMagic(magic(1)));
4680     assert(!isMagic(magic(2))); // 2x2 magic square does not exist
4681     foreach(n; 3 .. 24)
4682         assert(isMagic(magic(n)));
4683     assert(isMagic(magic(3).as!double.slice));
4684 }
4685 
4686 /++
4687 Chops 1D input slice into n chunks with ascending or descending lengths.
4688 
4689 `stairs` can be used to pack and unpack symmetric and triangular matrix storage.
4690 
4691 Note: `stairs` is defined for 1D (packet) input and 2D (general) input.
4692     This part of documentation is for 1D input.
4693 
4694 Params:
4695     type = $(UL
4696         $(LI `"-"` for stairs with lengths `n, n-1, ..., 1`.)
4697         $(LI `"+"` for stairs with lengths `1, 2, ..., n`;)
4698         )
4699     slice = input slice with length equal to `n * (n + 1) / 2`
4700     n = stairs count
4701 Returns:
4702     1D contiguous slice composed of 1D contiguous slices.
4703 
4704 See_also: $(LREF triplets) $(LREF ._stairs.2)
4705 +/
4706 Slice!(StairsIterator!(Iterator, type)) stairs(string type, Iterator)(Slice!Iterator slice, size_t n)
4707     if (type == "+" || type == "-")
4708 {
4709     assert(slice.length == (n + 1) * n / 2, "stairs: slice length must be equal to n * (n + 1) / 2, where n is stairs count.");
4710     static if (type == "+")
4711         size_t length = 1;
4712     else
4713         size_t length = n;
4714     return StairsIterator!(Iterator, type)(length, slice._iterator).sliced(n);
4715 }
4716 
4717 /// ditto
4718 Slice!(StairsIterator!(S*, type))  stairs(string type, S)(S[] slice, size_t n)
4719     if (type == "+" || type == "-")
4720 {
4721     return stairs!type(slice.sliced, n);
4722 }
4723 
4724 /// ditto
4725 auto stairs(string type, S)(S slice, size_t n)
4726     if (hasAsSlice!S && (type == "+" || type == "-"))
4727 {
4728     return stairs!type(slice.asSlice, n);
4729 }
4730 
4731 ///
4732 version(mir_test) unittest
4733 {
4734     import mir.ndslice.topology: iota, stairs;
4735 
4736     auto pck = 15.iota;
4737     auto inc = pck.stairs!"+"(5);
4738     auto dec = pck.stairs!"-"(5);
4739 
4740     assert(inc == [
4741         [0],
4742         [1, 2],
4743         [3, 4, 5],
4744         [6, 7, 8, 9],
4745         [10, 11, 12, 13, 14]]);
4746     assert(inc[1 .. $][2] == [6, 7, 8, 9]);
4747 
4748     assert(dec == [
4749         [0, 1, 2, 3, 4],
4750            [5, 6, 7, 8],
4751             [9, 10, 11],
4752                [12, 13],
4753                    [14]]);
4754     assert(dec[1 .. $][2] == [12, 13]);
4755 
4756     static assert(is(typeof(inc.front) == typeof(pck)));
4757     static assert(is(typeof(dec.front) == typeof(pck)));
4758 }
4759 
4760 /++
4761 Slice composed of rows of lower or upper triangular matrix.
4762 
4763 `stairs` can be used to pack and unpack symmetric and triangular matrix storage.
4764 
4765 Note: `stairs` is defined for 1D (packet) input and 2D (general) input.
4766     This part of documentation is for 2D input.
4767 
4768 Params:
4769     type = $(UL
4770         $(LI `"+"` for stairs with lengths `1, 2, ..., n`, lower matrix;)
4771         $(LI `"-"` for stairs with lengths `n, n-1, ..., 1`, upper matrix.)
4772         )
4773     slice = input slice with length equal to `n * (n + 1) / 2`
4774 Returns:
4775     1D slice composed of 1D contiguous slices.
4776 
4777 See_also: $(LREF _stairs) $(SUBREF dynamic, transposed), $(LREF universal)
4778 +/
4779 auto stairs(string type, Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) slice)
4780     if (type == "+" || type == "-")
4781 {
4782     assert(slice.length!0 == slice.length!1, "stairs: input slice must be a square matrix.");
4783     static if (type == "+")
4784     {
4785         return slice
4786             .pack!1
4787             .map!"a"
4788             .zip([slice.length].iota!size_t(1))
4789             .map!"a[0 .. b]";
4790     }
4791     else
4792     {
4793         return slice
4794             .pack!1
4795             .map!"a"
4796             .zip([slice.length].iota!size_t)
4797             .map!"a[b .. $]";
4798     }
4799 }
4800 
4801 ///
4802 version(mir_test) unittest
4803 {
4804     import mir.ndslice.topology: iota, as, stairs;
4805 
4806     auto gen = [3, 3].iota.as!double;
4807     auto inc = gen.stairs!"+";
4808     auto dec = gen.stairs!"-";
4809 
4810     assert(inc == [
4811         [0],
4812         [3, 4],
4813         [6, 7, 8]]);
4814 
4815     assert(dec == [
4816         [0, 1, 2],
4817            [4, 5],
4818               [8]]);
4819 
4820     static assert(is(typeof(inc.front) == typeof(gen.front)));
4821     static assert(is(typeof(dec.front) == typeof(gen.front)));
4822 
4823     /////////////////////////////////////////
4824     // Pack lower and upper matrix parts
4825     auto n = gen.length;
4826     auto m = n * (n + 1) / 2;
4827     // allocate memory
4828     import mir.ndslice.allocation: uninitSlice;
4829     auto lowerData = m.uninitSlice!double;
4830     auto upperData = m.uninitSlice!double;
4831     // construct packed stairs
4832     auto lower = lowerData.stairs!"+"(n);
4833     auto upper = upperData.stairs!"-"(n);
4834     // copy data
4835     import mir.algorithm.iteration: each;
4836     each!"a[] = b"(lower, inc);
4837     each!"a[] = b"(upper, dec);
4838 
4839     assert(&lower[0][0] is &lowerData[0]);
4840     assert(&upper[0][0] is &upperData[0]);
4841 
4842     assert(lowerData == [0, 3, 4, 6, 7, 8]);
4843     assert(upperData == [0, 1, 2, 4, 5, 8]);
4844 }
4845 
4846 /++
4847 Returns a slice that can be iterated along dimension. Transposes other dimensions on top and then packs them.
4848 
4849 Combines $(LREF byDim) and $(LREF evertPack).
4850 
4851 Params:
4852     SDimensions = dimensions to iterate along, length of d, `1 <= d < n`. Negative dimensions are supported.
4853 Returns:
4854     `(n-d)`-dimensional slice composed of d-dimensional slices
4855 See_also:
4856     $(LREF byDim),
4857     $(LREF iota),
4858     $(SUBREF allocation, slice),
4859     $(LREF ipack),
4860     $(SUBREF dynamic, transposed).
4861 +/
4862 template alongDim(SDimensions...)
4863     if (SDimensions.length > 0)
4864 {
4865     static if (allSatisfy!(isSizediff_t, SDimensions))
4866     {
4867         /++
4868         Params:
4869             slice = input n-dimensional slice, n > d
4870         Returns:
4871             `(n-d)`-dimensional slice composed of d-dimensional slices
4872         +/
4873         @optmath auto alongDim(Iterator, size_t N, SliceKind kind)
4874             (Slice!(Iterator, N, kind) slice)
4875             if (N > SDimensions.length)
4876         {
4877             import core.lifetime: move;
4878             return slice.move.byDim!SDimensions.evertPack;
4879         }
4880     }
4881     else
4882     {
4883         alias alongDim = .alongDim!(staticMap!(toSizediff_t, SDimensions));
4884     }
4885 }
4886 
4887 /// 2-dimensional slice support
4888 @safe @nogc pure nothrow
4889 version(mir_test) unittest
4890 {
4891     import mir.ndslice;
4892 
4893     //  ------------
4894     // | 0  1  2  3 |
4895     // | 4  5  6  7 |
4896     // | 8  9 10 11 |
4897     //  ------------
4898     auto slice = iota(3, 4);
4899     //->
4900     // | 3 |
4901     //->
4902     // | 4 |
4903     size_t[1] shape3 = [3];
4904     size_t[1] shape4 = [4];
4905 
4906     //  ------------
4907     // | 0  1  2  3 |
4908     // | 4  5  6  7 |
4909     // | 8  9 10 11 |
4910     //  ------------
4911     auto x = slice.alongDim!(-1); // -1 is the last dimension index, the same as 1 for this case.
4912     static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t), 1, Universal)));
4913 
4914     assert(x.shape == shape3);
4915     assert(x.front.shape == shape4);
4916     assert(x.front == iota(4));
4917     x.popFront;
4918     assert(x.front == iota([4], 4));
4919 
4920     //  ---------
4921     // | 0  4  8 |
4922     // | 1  5  9 |
4923     // | 2  6 10 |
4924     // | 3  7 11 |
4925     //  ---------
4926     auto y = slice.alongDim!0; // alongDim!(-2) is the same for matrices.
4927     static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal))));
4928 
4929     assert(y.shape == shape4);
4930     assert(y.front.shape == shape3);
4931     assert(y.front == iota([3], 0, 4));
4932     y.popFront;
4933     assert(y.front == iota([3], 1, 4));
4934 }
4935 
4936 /// 3-dimensional slice support, N-dimensional also supported
4937 @safe @nogc pure nothrow
4938 version(mir_test) unittest
4939 {
4940     import mir.ndslice;
4941 
4942     //  ----------------
4943     // | 0   1  2  3  4 |
4944     // | 5   6  7  8  9 |
4945     // | 10 11 12 13 14 |
4946     // | 15 16 17 18 19 |
4947     //  - - - - - - - -
4948     // | 20 21 22 23 24 |
4949     // | 25 26 27 28 29 |
4950     // | 30 31 32 33 34 |
4951     // | 35 36 37 38 39 |
4952     //  - - - - - - - -
4953     // | 40 41 42 43 44 |
4954     // | 45 46 47 48 49 |
4955     // | 50 51 52 53 54 |
4956     // | 55 56 57 58 59 |
4957     //  ----------------
4958     auto slice = iota(3, 4, 5);
4959 
4960     size_t[2] shape45 = [4, 5];
4961     size_t[2] shape35 = [3, 5];
4962     size_t[2] shape34 = [3, 4];
4963     size_t[2] shape54 = [5, 4];
4964     size_t[1] shape3 = [3];
4965     size_t[1] shape4 = [4];
4966     size_t[1] shape5 = [5];
4967 
4968     //  ----------
4969     // |  0 20 40 |
4970     // |  5 25 45 |
4971     // | 10 30 50 |
4972     // | 15 35 55 |
4973     //  - - - - -
4974     // |  1 21 41 |
4975     // |  6 26 46 |
4976     // | 11 31 51 |
4977     // | 16 36 56 |
4978     //  - - - - -
4979     // |  2 22 42 |
4980     // |  7 27 47 |
4981     // | 12 32 52 |
4982     // | 17 37 57 |
4983     //  - - - - -
4984     // |  3 23 43 |
4985     // |  8 28 48 |
4986     // | 13 33 53 |
4987     // | 18 38 58 |
4988     //  - - - - -
4989     // |  4 24 44 |
4990     // |  9 29 49 |
4991     // | 14 34 54 |
4992     // | 19 39 59 |
4993     //  ----------
4994     auto a = slice.alongDim!0.transposed;
4995     static assert(is(typeof(a) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 2, Universal)));
4996 
4997     assert(a.shape == shape54);
4998     assert(a.front.shape == shape4);
4999     assert(a.front.unpack == iota([3, 4], 0, 5).universal.transposed);
5000     a.popFront;
5001     assert(a.front.front == iota([3], 1, 20));
5002 
5003     //  ----------------
5004     // |  0  1  2  3  4 |
5005     // |  5  6  7  8  9 |
5006     // | 10 11 12 13 14 |
5007     // | 15 16 17 18 19 |
5008     //  - - - - - - - -
5009     // | 20 21 22 23 24 |
5010     // | 25 26 27 28 29 |
5011     // | 30 31 32 33 34 |
5012     // | 35 36 37 38 39 |
5013     //  - - - - - - - -
5014     // | 40 41 42 43 44 |
5015     // | 45 46 47 48 49 |
5016     // | 50 51 52 53 54 |
5017     // | 55 56 57 58 59 |
5018     //  ----------------
5019     auto x = slice.alongDim!(1, 2);
5020     static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2), 1, Universal)));
5021 
5022     assert(x.shape == shape3);
5023     assert(x.front.shape == shape45);
5024     assert(x.front == iota([4, 5]));
5025     x.popFront;
5026     assert(x.front == iota([4, 5], (4 * 5)));
5027 
5028     //  ----------------
5029     // |  0  1  2  3  4 |
5030     // | 20 21 22 23 24 |
5031     // | 40 41 42 43 44 |
5032     //  - - - - - - - -
5033     // |  5  6  7  8  9 |
5034     // | 25 26 27 28 29 |
5035     // | 45 46 47 48 49 |
5036     //  - - - - - - - -
5037     // | 10 11 12 13 14 |
5038     // | 30 31 32 33 34 |
5039     // | 50 51 52 53 54 |
5040     //  - - - - - - - -
5041     // | 15 16 17 18 19 |
5042     // | 35 36 37 38 39 |
5043     // | 55 56 57 58 59 |
5044     //  ----------------
5045     auto y = slice.alongDim!(0, 2);
5046     static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Canonical), 1, Universal)));
5047 
5048     assert(y.shape == shape4);
5049     assert(y.front.shape == shape35);
5050     int err;
5051     assert(y.front == slice.universal.strided!1(4).reshape([3, -1], err));
5052     y.popFront;
5053     assert(y.front.front == iota([5], 5));
5054 
5055     //  -------------
5056     // |  0  5 10 15 |
5057     // | 20 25 30 35 |
5058     // | 40 45 50 55 |
5059     //  - - - - - - -
5060     // |  1  6 11 16 |
5061     // | 21 26 31 36 |
5062     // | 41 46 51 56 |
5063     //  - - - - - - -
5064     // |  2  7 12 17 |
5065     // | 22 27 32 37 |
5066     // | 42 47 52 57 |
5067     //  - - - - - - -
5068     // |  3  8 13 18 |
5069     // | 23 28 33 38 |
5070     // | 43 48 53 58 |
5071     //  - - - - - - -
5072     // |  4  9 14 19 |
5073     // | 24 29 34 39 |
5074     // | 44 49 54 59 |
5075     //  -------------
5076     auto z = slice.alongDim!(0, 1);
5077     static assert(is(typeof(z) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Universal))));
5078 
5079     assert(z.shape == shape5);
5080     assert(z.front.shape == shape34);
5081     assert(z.front == iota([3, 4], 0, 5));
5082     z.popFront;
5083     assert(z.front.front == iota([4], 1, 5));
5084 }
5085 
5086 /// Use alongDim to calculate column mean/row mean of 2-dimensional slice
5087 version(mir_test)
5088 @safe pure
5089 unittest
5090 {
5091     import mir.ndslice.topology: alongDim;
5092     import mir.ndslice.fuse: fuse;
5093     import mir.math.stat: mean;
5094     import mir.algorithm.iteration: all;
5095     import mir.math.common: approxEqual;
5096 
5097     auto x = [
5098         [0.0, 1.0, 1.5, 2.0, 3.5, 4.25],
5099         [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]
5100     ].fuse;
5101 
5102     // Use alongDim with map to compute mean of row/column.
5103     assert(x.alongDim!1.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6]));
5104     assert(x.alongDim!0.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125]));
5105 
5106     // FIXME
5107     // Without using map, computes the mean of the whole slice
5108     // assert(x.alongDim!1.mean == x.sliced.mean);
5109     // assert(x.alongDim!0.mean == x.sliced.mean);
5110 }
5111 
5112 /++
5113 Use alongDim and map with a lambda, but may need to allocate result. This example
5114 uses fuse, which allocates. Note: fuse!1 will transpose the result. 
5115 +/
5116 version(mir_test)
5117 @safe pure
5118 unittest {
5119     import mir.ndslice.topology: iota, alongDim, map;
5120     import mir.ndslice.fuse: fuse;
5121     import mir.ndslice.slice: sliced;
5122     
5123     auto x = [1, 2, 3].sliced;
5124     auto y = [1, 2].sliced;
5125 
5126     auto s1 = iota(2, 3).alongDim!1.map!(a => a * x).fuse;
5127     assert(s1 == [[ 0, 2,  6],
5128                   [ 3, 8, 15]]);
5129     auto s2 = iota(2, 3).alongDim!0.map!(a => a * y).fuse!1;
5130     assert(s2 == [[ 0, 1,  2],
5131                   [ 6, 8, 10]]);
5132 }
5133 
5134 /++
5135 Returns a slice that can be iterated by dimension. Transposes dimensions on top and then packs them.
5136 
5137 Combines $(SUBREF dynamic, transposed), $(LREF ipack), and SliceKind Selectors.
5138 
5139 Params:
5140     SDimensions = dimensions to perform iteration on, length of d, `1 <= d <= n`. Negative dimensions are supported.
5141 Returns:
5142     d-dimensional slice composed of `(n-d)`-dimensional slices
5143 See_also:
5144     $(LREF alongDim),
5145     $(SUBREF allocation, slice),
5146     $(LREF ipack),
5147     $(SUBREF dynamic, transposed).
5148 +/
5149 template byDim(SDimensions...)
5150     if (SDimensions.length > 0)
5151 {
5152     static if (allSatisfy!(isSizediff_t, SDimensions))
5153     {
5154         /++
5155         Params:
5156             slice = input n-dimensional slice, n >= d
5157         Returns:
5158             d-dimensional slice composed of `(n-d)`-dimensional slices
5159         +/
5160         @optmath auto byDim(Iterator, size_t N, SliceKind kind)
5161             (Slice!(Iterator, N, kind) slice)
5162             if (N >= SDimensions.length)
5163         {
5164 
5165             alias Dimensions = staticMap!(ShiftNegativeWith!N, SDimensions);
5166 
5167             mixin DimensionsCountCTError;
5168 
5169             static if (N == 1)
5170             {
5171                 return slice;
5172             }
5173             else
5174             {
5175                 import core.lifetime: move;
5176                 import mir.ndslice.dynamic: transposed;
5177                 import mir.algorithm.iteration: all;
5178 
5179                 auto trans = slice
5180                     .move
5181                     .transposed!Dimensions;
5182                 static if (Dimensions.length == N)
5183                 {
5184                     return trans;
5185                 }
5186                 else
5187                 {
5188                     auto ret = trans.move.ipack!(Dimensions.length);
5189                     static if ((kind == Contiguous || kind == Canonical && N - Dimensions.length == 1) && [Dimensions].all!(a => a < Dimensions.length))
5190                     {
5191                         return ret
5192                             .move
5193                             .evertPack
5194                             .assumeContiguous
5195                             .evertPack;
5196                     }
5197                     else
5198                     static if (kind == Canonical && [Dimensions].all!(a => a < N - 1))
5199                     {
5200                         return ret
5201                             .move
5202                             .evertPack
5203                             .assumeCanonical
5204                             .evertPack;
5205                     }
5206                     else
5207                     static if ((kind == Contiguous || kind == Canonical && Dimensions.length == 1) && [Dimensions] == [Iota!(N - Dimensions.length, N)])
5208                     {
5209                         return ret.assumeContiguous;
5210                     }
5211                     else
5212                     static if ((kind == Contiguous || kind == Canonical) && Dimensions[$-1] == N - 1)
5213                     {
5214                         return ret.assumeCanonical;
5215                     }
5216                     else
5217                     {
5218                         return ret;
5219                     }
5220                 }
5221             }
5222         }
5223     }
5224     else
5225     {
5226         alias byDim = .byDim!(staticMap!(toSizediff_t, SDimensions));
5227     }
5228 }
5229 
5230 /// 2-dimensional slice support
5231 @safe @nogc pure nothrow
5232 version(mir_test) unittest
5233 {
5234     import mir.ndslice;
5235 
5236     //  ------------
5237     // | 0  1  2  3 |
5238     // | 4  5  6  7 |
5239     // | 8  9 10 11 |
5240     //  ------------
5241     auto slice = iota(3, 4);
5242     //->
5243     // | 3 |
5244     //->
5245     // | 4 |
5246     size_t[1] shape3 = [3];
5247     size_t[1] shape4 = [4];
5248 
5249     //  ------------
5250     // | 0  1  2  3 |
5251     // | 4  5  6  7 |
5252     // | 8  9 10 11 |
5253     //  ------------
5254     auto x = slice.byDim!0; // byDim!(-2) is the same for matrices.
5255     static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t), 1, Universal)));
5256 
5257     assert(x.shape == shape3);
5258     assert(x.front.shape == shape4);
5259     assert(x.front == iota(4));
5260     x.popFront;
5261     assert(x.front == iota([4], 4));
5262 
5263     //  ---------
5264     // | 0  4  8 |
5265     // | 1  5  9 |
5266     // | 2  6 10 |
5267     // | 3  7 11 |
5268     //  ---------
5269     auto y = slice.byDim!(-1); // -1 is the last dimension index, the same as 1 for this case.
5270     static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal))));
5271 
5272     assert(y.shape == shape4);
5273     assert(y.front.shape == shape3);
5274     assert(y.front == iota([3], 0, 4));
5275     y.popFront;
5276     assert(y.front == iota([3], 1, 4));
5277 }
5278 
5279 /// 3-dimensional slice support, N-dimensional also supported
5280 @safe @nogc pure nothrow
5281 version(mir_test) unittest
5282 {
5283     import mir.ndslice;
5284 
5285     //  ----------------
5286     // | 0   1  2  3  4 |
5287     // | 5   6  7  8  9 |
5288     // | 10 11 12 13 14 |
5289     // | 15 16 17 18 19 |
5290     //  - - - - - - - -
5291     // | 20 21 22 23 24 |
5292     // | 25 26 27 28 29 |
5293     // | 30 31 32 33 34 |
5294     // | 35 36 37 38 39 |
5295     //  - - - - - - - -
5296     // | 40 41 42 43 44 |
5297     // | 45 46 47 48 49 |
5298     // | 50 51 52 53 54 |
5299     // | 55 56 57 58 59 |
5300     //  ----------------
5301     auto slice = iota(3, 4, 5);
5302 
5303     size_t[2] shape45 = [4, 5];
5304     size_t[2] shape35 = [3, 5];
5305     size_t[2] shape34 = [3, 4];
5306     size_t[2] shape54 = [5, 4];
5307     size_t[1] shape3 = [3];
5308     size_t[1] shape4 = [4];
5309     size_t[1] shape5 = [5];
5310 
5311     //  ----------------
5312     // |  0  1  2  3  4 |
5313     // |  5  6  7  8  9 |
5314     // | 10 11 12 13 14 |
5315     // | 15 16 17 18 19 |
5316     //  - - - - - - - -
5317     // | 20 21 22 23 24 |
5318     // | 25 26 27 28 29 |
5319     // | 30 31 32 33 34 |
5320     // | 35 36 37 38 39 |
5321     //  - - - - - - - -
5322     // | 40 41 42 43 44 |
5323     // | 45 46 47 48 49 |
5324     // | 50 51 52 53 54 |
5325     // | 55 56 57 58 59 |
5326     //  ----------------
5327     auto x = slice.byDim!0;
5328     static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2), 1, Universal)));
5329 
5330     assert(x.shape == shape3);
5331     assert(x.front.shape == shape45);
5332     assert(x.front == iota([4, 5]));
5333     x.popFront;
5334     assert(x.front == iota([4, 5], (4 * 5)));
5335 
5336     //  ----------------
5337     // |  0  1  2  3  4 |
5338     // | 20 21 22 23 24 |
5339     // | 40 41 42 43 44 |
5340     //  - - - - - - - -
5341     // |  5  6  7  8  9 |
5342     // | 25 26 27 28 29 |
5343     // | 45 46 47 48 49 |
5344     //  - - - - - - - -
5345     // | 10 11 12 13 14 |
5346     // | 30 31 32 33 34 |
5347     // | 50 51 52 53 54 |
5348     //  - - - - - - - -
5349     // | 15 16 17 18 19 |
5350     // | 35 36 37 38 39 |
5351     // | 55 56 57 58 59 |
5352     //  ----------------
5353     auto y = slice.byDim!1;
5354     static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Canonical), 1, Universal)));
5355 
5356     assert(y.shape == shape4);
5357     assert(y.front.shape == shape35);
5358     int err;
5359     assert(y.front == slice.universal.strided!1(4).reshape([3, -1], err));
5360     y.popFront;
5361     assert(y.front.front == iota([5], 5));
5362 
5363     //  -------------
5364     // |  0  5 10 15 |
5365     // | 20 25 30 35 |
5366     // | 40 45 50 55 |
5367     //  - - - - - - -
5368     // |  1  6 11 16 |
5369     // | 21 26 31 36 |
5370     // | 41 46 51 56 |
5371     //  - - - - - - -
5372     // |  2  7 12 17 |
5373     // | 22 27 32 37 |
5374     // | 42 47 52 57 |
5375     //  - - - - - - -
5376     // |  3  8 13 18 |
5377     // | 23 28 33 38 |
5378     // | 43 48 53 58 |
5379     //  - - - - - - -
5380     // |  4  9 14 19 |
5381     // | 24 29 34 39 |
5382     // | 44 49 54 59 |
5383     //  -------------
5384     auto z = slice.byDim!2;
5385     static assert(is(typeof(z) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 2, Universal))));
5386 
5387     assert(z.shape == shape5);
5388     assert(z.front.shape == shape34);
5389     assert(z.front == iota([3, 4], 0, 5));
5390     z.popFront;
5391     assert(z.front.front == iota([4], 1, 5));
5392 
5393     //  ----------
5394     // |  0 20 40 |
5395     // |  5 25 45 |
5396     // | 10 30 50 |
5397     // | 15 35 55 |
5398     //  - - - - -
5399     // |  1 21 41 |
5400     // |  6 26 46 |
5401     // | 11 31 51 |
5402     // | 16 36 56 |
5403     //  - - - - -
5404     // |  2 22 42 |
5405     // |  7 27 47 |
5406     // | 12 32 52 |
5407     // | 17 37 57 |
5408     //  - - - - -
5409     // |  3 23 43 |
5410     // |  8 28 48 |
5411     // | 13 33 53 |
5412     // | 18 38 58 |
5413     //  - - - - -
5414     // |  4 24 44 |
5415     // |  9 29 49 |
5416     // | 14 34 54 |
5417     // | 19 39 59 |
5418     //  ----------
5419     auto a = slice.byDim!(2, 1);
5420     static assert(is(typeof(a) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 2, Universal)));
5421 
5422     assert(a.shape == shape54);
5423     assert(a.front.shape == shape4);
5424     assert(a.front.unpack == iota([3, 4], 0, 5).universal.transposed);
5425     a.popFront;
5426     assert(a.front.front == iota([3], 1, 20));
5427 }
5428 
5429 /// Use byDim to calculate column mean/row mean of 2-dimensional slice
5430 version(mir_test)
5431 @safe pure
5432 unittest
5433 {
5434     import mir.ndslice.topology: byDim;
5435     import mir.ndslice.fuse: fuse;
5436     import mir.math.stat: mean;
5437     import mir.algorithm.iteration: all;
5438     import mir.math.common: approxEqual;
5439 
5440     auto x = [
5441         [0.0, 1.0, 1.5, 2.0, 3.5, 4.25],
5442         [2.0, 7.5, 5.0, 1.0, 1.5, 0.0]
5443     ].fuse;
5444 
5445     // Use byDim with map to compute mean of row/column.
5446     assert(x.byDim!0.map!mean.all!approxEqual([12.25 / 6, 17.0 / 6]));
5447     assert(x.byDim!1.map!mean.all!approxEqual([1, 4.25, 3.25, 1.5, 2.5, 2.125]));
5448 
5449     // FIXME
5450     // Without using map, computes the mean of the whole slice
5451     // assert(x.byDim!0.mean == x.sliced.mean);
5452     // assert(x.byDim!1.mean == x.sliced.mean);
5453 }
5454 
5455 /++
5456 Use byDim and map with a lambda, but may need to allocate result. This example
5457 uses fuse, which allocates. Note: fuse!1 will transpose the result. 
5458 +/
5459 version(mir_test)
5460 @safe pure
5461 unittest {
5462     import mir.ndslice.topology: iota, byDim, map;
5463     import mir.ndslice.fuse: fuse;
5464     import mir.ndslice.slice: sliced;
5465     
5466     auto x = [1, 2, 3].sliced;
5467     auto y = [1, 2].sliced;
5468 
5469     auto s1 = iota(2, 3).byDim!0.map!(a => a * x).fuse;
5470     assert(s1 == [[ 0, 2,  6],
5471                   [ 3, 8, 15]]);
5472     auto s2 = iota(2, 3).byDim!1.map!(a => a * y).fuse!1;
5473     assert(s2 == [[ 0, 1,  2],
5474                   [ 6, 8, 10]]);
5475 }
5476 
5477 // Ensure works on canonical
5478 @safe @nogc pure nothrow
5479 version(mir_test) unittest
5480 {
5481     import mir.ndslice.topology : iota, canonical;
5482     //  ------------
5483     // | 0  1  2  3 |
5484     // | 4  5  6  7 |
5485     // | 8  9 10 11 |
5486     //  ------------
5487     auto slice = iota(3, 4).canonical;
5488     //->
5489     // | 3 |
5490     //->
5491     // | 4 |
5492     size_t[1] shape3 = [3];
5493     size_t[1] shape4 = [4];
5494 
5495     //  ------------
5496     // | 0  1  2  3 |
5497     // | 4  5  6  7 |
5498     // | 8  9 10 11 |
5499     //  ------------
5500     auto x = slice.byDim!0;
5501     static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t), 1, Universal)));
5502 
5503     assert(x.shape == shape3);
5504     assert(x.front.shape == shape4);
5505     assert(x.front == iota(4));
5506     x.popFront;
5507     assert(x.front == iota([4], 4));
5508 
5509     //  ---------
5510     // | 0  4  8 |
5511     // | 1  5  9 |
5512     // | 2  6 10 |
5513     // | 3  7 11 |
5514     //  ---------
5515     auto y = slice.byDim!1;
5516     static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal))));
5517 
5518     assert(y.shape == shape4);
5519     assert(y.front.shape == shape3);
5520     assert(y.front == iota([3], 0, 4));
5521     y.popFront;
5522     assert(y.front == iota([3], 1, 4));
5523 }
5524 
5525 // Ensure works on universal
5526 @safe @nogc pure nothrow
5527 version(mir_test) unittest
5528 {
5529     import mir.ndslice.topology : iota, universal;
5530     //  ------------
5531     // | 0  1  2  3 |
5532     // | 4  5  6  7 |
5533     // | 8  9 10 11 |
5534     //  ------------
5535     auto slice = iota(3, 4).universal;
5536     //->
5537     // | 3 |
5538     //->
5539     // | 4 |
5540     size_t[1] shape3 = [3];
5541     size_t[1] shape4 = [4];
5542 
5543     //  ------------
5544     // | 0  1  2  3 |
5545     // | 4  5  6  7 |
5546     // | 8  9 10 11 |
5547     //  ------------
5548     auto x = slice.byDim!0;
5549     static assert(is(typeof(x) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 1, Universal)));
5550 
5551     assert(x.shape == shape3);
5552     assert(x.front.shape == shape4);
5553     assert(x.front == iota(4));
5554     x.popFront;
5555     assert(x.front == iota([4], 4));
5556 
5557     //  ---------
5558     // | 0  4  8 |
5559     // | 1  5  9 |
5560     // | 2  6 10 |
5561     // | 3  7 11 |
5562     //  ---------
5563     auto y = slice.byDim!1;
5564     static assert(is(typeof(y) == Slice!(SliceIterator!(IotaIterator!sizediff_t, 1, Universal), 1, Universal)));
5565 
5566     assert(y.shape == shape4);
5567     assert(y.front.shape == shape3);
5568     assert(y.front == iota([3], 0, 4));
5569     y.popFront;
5570     assert(y.front == iota([3], 1, 4));
5571 }
5572 
5573 // 1-dimensional slice support
5574 @safe @nogc pure nothrow
5575 version(mir_test) unittest
5576 {
5577     import mir.ndslice.topology : iota;
5578     //  -------
5579     // | 0 1 2 |
5580     //  -------
5581     auto slice = iota(3);
5582     auto x = slice.byDim!0;
5583     static assert (is(typeof(x) == typeof(slice)));
5584     assert(x == slice);
5585 }
5586 
5587 /++
5588 Constructs a new view of an n-dimensional slice with dimension `axis` removed.
5589 
5590 Throws:
5591     `AssertError` if the length of the corresponding dimension doesn' equal 1.
5592 Params:
5593     axis = dimension to remove, if it is single-dimensional
5594     slice = n-dimensional slice
5595 Returns:
5596     new view of a slice with dimension removed
5597 See_also: $(LREF unsqueeze), $(LREF iota).
5598 +/
5599 template squeeze(sizediff_t axis = 0)
5600 {
5601     Slice!(Iterator, N - 1, kind != Canonical ? kind : ((axis == N - 1 || axis == -1) ? Universal : (N == 2 ? Contiguous : kind)))
5602         squeeze(Iterator, size_t N, SliceKind kind)
5603         (Slice!(Iterator, N, kind) slice)
5604         if (-sizediff_t(N) <= axis && axis < sizediff_t(N) && N > 1)
5605     in {
5606         assert(slice._lengths[axis < 0 ? N + axis : axis] == 1);
5607     }
5608     do {
5609         import mir.utility: swap;
5610         enum sizediff_t a = axis < 0 ? N + axis : axis;
5611         typeof(return) ret;
5612         foreach (i; Iota!(0, a))
5613             ret._lengths[i] = slice._lengths[i];
5614         foreach (i; Iota!(a + 1, N))
5615             ret._lengths[i - 1] = slice._lengths[i];
5616         static if (kind == Universal)
5617         {
5618             foreach (i; Iota!(0, a))
5619                 ret._strides[i] = slice._strides[i];
5620             foreach (i; Iota!(a + 1, N))
5621                 ret._strides[i - 1] = slice._strides[i];
5622         }
5623         else
5624         static if (kind == Canonical)
5625         {
5626             static if (a == N - 1)
5627             {
5628                 foreach (i; Iota!(0, N - 1))
5629                     ret._strides[i] = slice._strides[i];
5630             }
5631             else
5632             {
5633                 foreach (i; Iota!(0, a))
5634                     ret._strides[i] = slice._strides[i];
5635                 foreach (i; Iota!(a + 1, N - 1))
5636                     ret._strides[i - 1] = slice._strides[i];
5637             }
5638         }
5639         swap(ret._iterator, slice._iterator);
5640         return ret;
5641     }
5642 }
5643 
5644 ///
5645 unittest
5646 {
5647     import mir.ndslice.topology : iota;
5648     import mir.ndslice.allocation : slice;
5649 
5650     // [[0, 1, 2]] -> [0, 1, 2]
5651     assert([1, 3].iota.squeeze == [3].iota);
5652     // [[0], [1], [2]] -> [0, 1, 2]
5653     assert([3, 1].iota.squeeze!1 == [3].iota);
5654     assert([3, 1].iota.squeeze!(-1) == [3].iota);
5655 
5656     assert([1, 3].iota.canonical.squeeze == [3].iota);
5657     assert([3, 1].iota.canonical.squeeze!1 == [3].iota);
5658     assert([3, 1].iota.canonical.squeeze!(-1) == [3].iota);
5659 
5660     assert([1, 3].iota.universal.squeeze == [3].iota);
5661     assert([3, 1].iota.universal.squeeze!1 == [3].iota);
5662     assert([3, 1].iota.universal.squeeze!(-1) == [3].iota);
5663 
5664     assert([1, 3, 4].iota.squeeze == [3, 4].iota);
5665     assert([3, 1, 4].iota.squeeze!1 == [3, 4].iota);
5666     assert([3, 4, 1].iota.squeeze!(-1) == [3, 4].iota);
5667 
5668     assert([1, 3, 4].iota.canonical.squeeze == [3, 4].iota);
5669     assert([3, 1, 4].iota.canonical.squeeze!1 == [3, 4].iota);
5670     assert([3, 4, 1].iota.canonical.squeeze!(-1) == [3, 4].iota);
5671 
5672     assert([1, 3, 4].iota.universal.squeeze == [3, 4].iota);
5673     assert([3, 1, 4].iota.universal.squeeze!1 == [3, 4].iota);
5674     assert([3, 4, 1].iota.universal.squeeze!(-1) == [3, 4].iota);
5675 }
5676 
5677 /++
5678 Constructs a view of an n-dimensional slice with a dimension added at `axis`. Used
5679 to unsqueeze a squeezed slice.
5680 
5681 Params:
5682     slice = n-dimensional slice
5683     axis = dimension to be unsqueezed (add new dimension), default values is 0, the first dimension
5684 Returns:
5685     unsqueezed n+1-dimensional slice of the same slice kind
5686 See_also: $(LREF squeeze), $(LREF iota).
5687 +/
5688 Slice!(Iterator, N + 1, kind) unsqueeze(Iterator, size_t N, SliceKind kind)
5689     (Slice!(Iterator, N, kind) slice, sizediff_t axis)
5690 in {
5691     assert(-sizediff_t(N + 1) <= axis && axis <= sizediff_t(N));
5692 }
5693 do {
5694     import mir.utility: swap;
5695     typeof(return) ret;
5696     auto a = axis < 0 ? axis + N + 1 : axis;
5697     foreach (i; 0 .. a)
5698         ret._lengths[i] = slice._lengths[i];
5699     ret._lengths[a] = 1;
5700     foreach (i; a .. N)
5701         ret._lengths[i + 1] = slice._lengths[i];
5702     static if (kind == Universal)
5703     {
5704         foreach (i; 0 .. a)
5705             ret._strides[i] = slice._strides[i];
5706         foreach (i; a .. N)
5707             ret._strides[i + 1] = slice._strides[i];
5708     }
5709     else
5710     static if (kind == Canonical)
5711     {
5712         if (a == N)
5713         {
5714             foreach (i; Iota!(0, N - 1))
5715                 ret._strides[i] = slice._strides[i];
5716             ret._strides[N - 1] = 1;
5717         }
5718         else
5719         {
5720             foreach (i; 0 .. a)
5721                 ret._strides[i] = slice._strides[i];
5722             foreach (i; a .. N - 1)
5723                 ret._strides[i + 1] = slice._strides[i];
5724         }
5725     }
5726     swap(ret._iterator, slice._iterator);
5727     return ret;
5728 }
5729 
5730 /// ditto
5731 template unsqueeze(sizediff_t axis = 0)
5732 {
5733     Slice!(Iterator, N + 1, kind) unsqueeze(Iterator, size_t N, SliceKind kind)
5734         (Slice!(Iterator, N, kind) slice)
5735     in {
5736         assert(-sizediff_t(N + 1) <= axis && axis <= sizediff_t(N));
5737     }
5738     do {
5739         import mir.utility: swap;
5740         typeof(return) ret;
5741         enum a = axis < 0 ? axis + N + 1 : axis;
5742         foreach (i; Iota!a)
5743             ret._lengths[i] = slice._lengths[i];
5744         ret._lengths[a] = 1;
5745         foreach (i; Iota!(a, N))
5746             ret._lengths[i + 1] = slice._lengths[i];
5747         static if (kind == Universal)
5748         {
5749             foreach (i; Iota!a)
5750                 ret._strides[i] = slice._strides[i];
5751             foreach (i; Iota!(a, N))
5752                 ret._strides[i + 1] = slice._strides[i];
5753         }
5754         else
5755         static if (kind == Canonical)
5756         {
5757             static if (a == N)
5758             {
5759                 foreach (i; Iota!(0, N - 1))
5760                     ret._strides[i] = slice._strides[i];
5761                 ret._strides[N - 1] = 1;
5762             }
5763             else
5764             {
5765                 foreach (i; Iota!(0, a))
5766                     ret._strides[i] = slice._strides[i];
5767                 foreach (i; Iota!(a, N - 1))
5768                     ret._strides[i + 1] = slice._strides[i];
5769             }
5770         }
5771         swap(ret._iterator, slice._iterator);
5772         return ret;
5773     }
5774 }
5775 
5776 ///
5777 version (mir_test) 
5778 @safe pure nothrow @nogc
5779 unittest
5780 {
5781     // [0, 1, 2] -> [[0, 1, 2]]
5782     assert([3].iota.unsqueeze == [1, 3].iota);
5783 
5784     assert([3].iota.universal.unsqueeze == [1, 3].iota);
5785     assert([3, 4].iota.unsqueeze == [1, 3, 4].iota);
5786     assert([3, 4].iota.canonical.unsqueeze == [1, 3, 4].iota);
5787     assert([3, 4].iota.universal.unsqueeze == [1, 3, 4].iota);
5788 
5789     // [0, 1, 2] -> [[0], [1], [2]]
5790     assert([3].iota.unsqueeze(-1) == [3, 1].iota);
5791     assert([3].iota.unsqueeze!(-1) == [3, 1].iota);
5792 
5793     assert([3].iota.universal.unsqueeze(-1) == [3, 1].iota);
5794     assert([3].iota.universal.unsqueeze!(-1) == [3, 1].iota);
5795     assert([3, 4].iota.unsqueeze(-1) == [3, 4, 1].iota);
5796     assert([3, 4].iota.unsqueeze!(-1) == [3, 4, 1].iota);
5797     assert([3, 4].iota.canonical.unsqueeze(-1) == [3, 4, 1].iota);
5798     assert([3, 4].iota.canonical.unsqueeze!(-1) == [3, 4, 1].iota);
5799     assert([3, 4].iota.universal.unsqueeze(-1) == [3, 4, 1].iota);
5800     assert([3, 4].iota.universal.unsqueeze!(-1) == [3, 4, 1].iota);
5801 }
5802 
5803 /++
5804 Field (element's member) projection.
5805 
5806 Params:
5807     name = element's member name
5808 Returns:
5809     lazy n-dimensional slice of the same shape
5810 See_also:
5811     $(LREF map)
5812 +/
5813 
5814 template member(string name)
5815     if (name.length)
5816 {
5817     /++
5818     Params:
5819         slice = n-dimensional slice composed of structs, classes or unions
5820     Returns:
5821         lazy n-dimensional slice of the same shape
5822     +/
5823     Slice!(MemberIterator!(Iterator, name), N, kind) member(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice)
5824     {
5825         return typeof(return)(slice._structure, MemberIterator!(Iterator, name)(slice._iterator));
5826     }
5827 
5828     /// ditto
5829     Slice!(MemberIterator!(T*, name)) member(T)(T[] array)
5830     {
5831         return member(array.sliced);
5832     }
5833 
5834     /// ditto
5835     auto member(T)(T withAsSlice)
5836         if (hasAsSlice!T)
5837     {
5838         return member(withAsSlice.asSlice);
5839     }
5840 }
5841 
5842 ///
5843 version(mir_test)
5844 @safe pure unittest
5845 {
5846     // struct, union or class
5847     struct S
5848     {
5849         // Property support
5850         // Getter always must be defined.
5851         double _x;
5852         double x() @property
5853         {
5854             return x;
5855         }
5856         void x(double x) @property
5857         {
5858             _x = x;
5859         }
5860 
5861         /// Field support
5862         double y;
5863 
5864         /// Zero argument function support
5865         double f()
5866         {
5867             return _x * 2;
5868         }
5869     }
5870 
5871     import mir.ndslice.allocation: slice;
5872     import mir.ndslice.topology: iota;
5873 
5874     auto matrix = slice!S(2, 3);
5875     matrix.member!"x"[] = [2, 3].iota;
5876     matrix.member!"y"[] = matrix.member!"f";
5877     assert(matrix.member!"y" == [2, 3].iota * 2);
5878 }
5879 
5880 /++
5881 Functional deep-element wise reduce of a slice composed of fields or iterators.
5882 +/
5883 template orthogonalReduceField(alias fun)
5884 {
5885     import mir.functional: naryFun;
5886     static if (__traits(isSame, naryFun!fun, fun))
5887     {
5888     @optmath:
5889         /++
5890         Params:
5891             slice = Non empty input slice composed of fields or iterators.
5892         Returns:
5893             a lazy field with each element of which is reduced value of element of the same index of all iterators.
5894         +/
5895         OrthogonalReduceField!(Iterator, fun, I) orthogonalReduceField(I, Iterator)(I initialValue, Slice!Iterator slice)
5896         {
5897             return typeof(return)(slice, initialValue);
5898         }
5899 
5900         /// ditto
5901         OrthogonalReduceField!(T*, fun, I) orthogonalReduceField(I, T)(I initialValue, T[] array)
5902         {
5903             return orthogonalReduceField(initialValue, array.sliced);
5904         }
5905 
5906         /// ditto
5907         auto orthogonalReduceField(I, T)(I initialValue, T withAsSlice)
5908             if (hasAsSlice!T)
5909         {
5910             return orthogonalReduceField(initialValue, withAsSlice.asSlice);
5911         }
5912     }
5913     else alias orthogonalReduceField = .orthogonalReduceField!(naryFun!fun);
5914 }
5915 
5916 /// bit array operations
5917 version(mir_test)
5918 unittest
5919 {
5920     import mir.ndslice.slice: slicedField;
5921     import mir.ndslice.allocation: bitSlice;
5922     import mir.ndslice.dynamic: strided;
5923     import mir.ndslice.topology: iota, orthogonalReduceField;
5924     auto len = 100;
5925     auto a = len.bitSlice;
5926     auto b = len.bitSlice;
5927     auto c = len.bitSlice;
5928     a[len.iota.strided!0(7)][] = true;
5929     b[len.iota.strided!0(11)][] = true;
5930     c[len.iota.strided!0(13)][] = true;
5931 
5932     // this is valid since bitslices above are oroginal slices of allocated memory.
5933     auto and =
5934         orthogonalReduceField!"a & b"(size_t.max, [
5935             a.iterator._field._field, // get raw data pointers
5936             b.iterator._field._field,
5937             c.iterator._field._field,
5938         ]) // operation on size_t
5939         .bitwiseField
5940         .slicedField(len);
5941 
5942     assert(and == (a & b & c));
5943 }
5944 
5945 /++
5946 Constructs a lazy view of triplets with `left`, `center`, and `right` members.
5947 
5948 Returns: Slice of the same length composed of $(SUBREF iterator, Triplet) triplets.
5949 The `center` member is type of a slice element.
5950 The `left` and `right` members has the same type as slice.
5951 
5952 The module contains special function $(LREF collapse) to handle
5953 left and right side of triplets in one expression.
5954 
5955 Params:
5956     slice = a slice or an array to iterate over
5957 
5958 Example:
5959 ------
5960 triplets(eeeeee) =>
5961 
5962 ||c|lllll|
5963 |r|c|llll|
5964 |rr|c|lll|
5965 |rrr|c|ll|
5966 |rrrr|c|l|
5967 |rrrrr|c||
5968 ------
5969 
5970 See_also: $(LREF stairs).
5971 +/
5972 Slice!(TripletIterator!(Iterator, kind)) triplets(Iterator, SliceKind kind)(Slice!(Iterator, 1, kind) slice)
5973 {
5974     return typeof(return)(slice.length, typeof(return).Iterator(0, slice));
5975 }
5976 
5977 /// ditto
5978 Slice!(TripletIterator!(T*)) triplets(T)(scope return T[] slice)
5979 {
5980     return .triplets(slice.sliced);
5981 }
5982 
5983 /// ditto
5984 auto triplets(string type, S)(S slice, size_t n)
5985     if (hasAsSlice!S)
5986 {
5987     return .triplets(slice.asSlice);
5988 }
5989 
5990 ///
5991 version(mir_test) unittest
5992 {
5993     import mir.ndslice.slice: sliced;
5994     import mir.ndslice.topology: triplets, member, iota;
5995 
5996     auto a = [4, 5, 2, 8];
5997     auto h = a.triplets;
5998 
5999     assert(h[1].center == 5);
6000     assert(h[1].left == [4]);
6001     assert(h[1].right == [2, 8]);
6002 
6003     h[1].center = 9;
6004     assert(a[1] == 9);
6005 
6006     assert(h.member!"center" == a);
6007 
6008     // `triplets` topology can be used with iota to index a slice
6009     auto s = a.sliced;
6010     auto w = s.length.iota.triplets[1];
6011 
6012     assert(&s[w.center] == &a[1]);
6013     assert(s[w.left].field is a[0 .. 1]);
6014     assert(s[w.right].field is a[2 .. $]);
6015 }