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 }