1 // Written in the D programming language. 2 /** 3 This module contains generic _iteration algorithms. 4 $(SCRIPT inhibitQuickIndex = 1;) 5 6 $(BOOKTABLE $(H2 Function), 7 $(TR $(TH Function Name) $(TH Description)) 8 $(T2 all, Checks if all elements satisfy to a predicate.) 9 $(T2 any, Checks if at least one element satisfy to a predicate.) 10 $(T2 cmp, Compares two slices.) 11 $(T2 count, Counts elements in a slices according to a predicate.) 12 $(T2 each, Iterates elements.) 13 $(T2 eachLower, Iterates lower triangle of matrix.) 14 $(T2 eachOnBorder, Iterates elementes on tensors borders and corners.) 15 $(T2 eachUploPair, Iterates upper and lower pairs of elements in square matrix.) 16 $(T2 eachUpper, Iterates upper triangle of matrix.) 17 $(T2 equal, Compares two slices for equality.) 18 $(T2 filter, Filters elements in a range or an ndslice.) 19 $(T2 find, Finds backward index.) 20 $(T2 findIndex, Finds index.) 21 $(T2 fold, Accumulates all elements (different parameter order than `reduce`).) 22 $(T2 isSymmetric, Checks if the matrix is symmetric.) 23 $(T2 maxIndex, Finds index of the maximum.) 24 $(T2 maxPos, Finds backward index of the maximum.) 25 $(T2 minIndex, Finds index of the minimum.) 26 $(T2 minmaxIndex, Finds indices of the minimum and the maximum.) 27 $(T2 minmaxPos, Finds backward indices of the minimum and the maximum.) 28 $(T2 minPos, Finds backward index of the minimum.) 29 $(T2 nBitsToCount, Сount bits until set bit count is reached.) 30 $(T2 reduce, Accumulates all elements.) 31 $(T2 Chequer, Chequer color selector to work with $(LREF each) .) 32 $(T2 uniq, Iterates over the unique elements in a range or an ndslice, which is assumed sorted.) 33 ) 34 35 Transform function is represented by $(NDSLICEREF topology, map). 36 37 All operators are suitable to change slices using `ref` argument qualification in a function declaration. 38 Note, that string lambdas in Mir are `auto ref` functions. 39 40 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 41 Copyright: 2020 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments 42 Authors: Ilya Yaroshenko, John Michael Hall, Andrei Alexandrescu (original Phobos code) 43 44 License: $(HTTP www.apache.org/licenses/LICENSE-2.0, Apache-2.0) 45 Copyright: 2020 Ilya Yaroshenko, Kaleidic Associates Advisory Limited, Symmetry Investments 46 47 Authors: , Ilya Yaroshenko (Mir & BetterC rework). 48 Source: $(PHOBOSSRC std/algorithm/_iteration.d) 49 Macros: 50 NDSLICEREF = $(REF_ALTTEXT $(TT $2), $2, mir, ndslice, $1)$(NBSP) 51 T2=$(TR $(TDNW $(LREF $1)) $(TD $+)) 52 */ 53 module mir.algorithm.iteration; 54 55 import mir.functional: naryFun; 56 import mir.internal.utility; 57 import mir.math.common: optmath; 58 import mir.ndslice.field: BitField; 59 import mir.ndslice.internal; 60 import mir.ndslice.iterator: FieldIterator, RetroIterator; 61 import mir.ndslice.slice; 62 import mir.primitives; 63 import mir.qualifier; 64 import std.meta; 65 import std.traits; 66 67 /++ 68 Chequer color selector to work with $(LREF each) 69 +/ 70 enum Chequer : bool 71 { 72 /// Main diagonal color 73 black, 74 /// First sub-diagonal color 75 red, 76 } 77 78 /// 79 version(mir_test) unittest 80 { 81 import mir.ndslice.allocation: slice; 82 auto s = [5, 4].slice!int; 83 84 Chequer.black.each!"a = 1"(s); 85 assert(s == [ 86 [1, 0, 1, 0], 87 [0, 1, 0, 1], 88 [1, 0, 1, 0], 89 [0, 1, 0, 1], 90 [1, 0, 1, 0], 91 ]); 92 93 Chequer.red.each!((ref b) => b = 2)(s); 94 assert(s == [ 95 [1, 2, 1, 2], 96 [2, 1, 2, 1], 97 [1, 2, 1, 2], 98 [2, 1, 2, 1], 99 [1, 2, 1, 2], 100 ]); 101 102 } 103 104 @optmath: 105 106 /+ 107 Bitslice representation for accelerated bitwise algorithm. 108 1-dimensional contiguousitslice can be split into three chunks: head bits, body chunks, and tail bits. 109 110 Bitslice can have head bits because it has slicing and the zero bit may not be aligned to the zero of a body chunk. 111 +/ 112 private struct BitSliceAccelerator(Field, I = typeof(Field.init[size_t.init])) 113 if (__traits(isUnsigned, I)) 114 { 115 import mir.bitop; 116 import mir.qualifier: lightConst; 117 import mir.ndslice.traits: isIterator; 118 import mir.ndslice.iterator: FieldIterator; 119 import mir.ndslice.field: BitField; 120 121 /// 122 alias U = typeof(I + 1u); 123 /// body bits chunks 124 static if (isIterator!Field) 125 Slice!Field bodyChunks; 126 else 127 Slice!(FieldIterator!Field) bodyChunks; 128 /// head length 129 int headLength; 130 /// tail length 131 int tailLength; 132 133 @optmath: 134 135 this(Slice!(FieldIterator!(BitField!(Field, I))) slice) 136 { 137 enum mask = bitShiftMask!I; 138 enum shift = bitElemShift!I; 139 size_t length = slice.length; 140 size_t index = slice._iterator._index; 141 if (auto hlen = index & mask) 142 { 143 auto l = I.sizeof * 8 - hlen; 144 if (l > length) 145 { 146 // central problem 147 headLength = -cast(int) length; 148 tailLength = cast(int) hlen; 149 goto F; 150 } 151 else 152 { 153 headLength = cast(uint) l; 154 length -= l; 155 index += l; 156 } 157 } 158 tailLength = cast(int) (length & mask); 159 F: 160 length >>= shift; 161 index >>= shift; 162 bodyChunks._lengths[0] = length; 163 static if (isIterator!Field) 164 { 165 bodyChunks._iterator = slice._iterator._field._field; 166 bodyChunks._iterator += index; 167 } 168 else 169 { 170 bodyChunks._iterator._index = index; 171 bodyChunks._iterator._field = slice._iterator._field._field; 172 } 173 } 174 175 scope const: 176 177 bool isCentralProblem() 178 { 179 return headLength < 0; 180 } 181 182 U centralBits() 183 { 184 assert(isCentralProblem); 185 return *bodyChunks._iterator.lightConst >>> tailLength; 186 } 187 188 uint centralLength() 189 { 190 assert(isCentralProblem); 191 return -headLength; 192 } 193 194 /// head bits (last `headLength` bits are valid). 195 U headBits() 196 { 197 assert(!isCentralProblem); 198 if (headLength == 0) 199 return U.init; 200 static if (isIterator!Field) 201 return bodyChunks._iterator.lightConst[-1]; 202 else 203 return bodyChunks._iterator._field.lightConst[bodyChunks._iterator._index - 1]; 204 } 205 206 /// tail bits (first `tailLength` bits are valid). 207 U tailBits() 208 { 209 assert(!isCentralProblem); 210 if (tailLength == 0) 211 return U.init; 212 static if (isIterator!Field) 213 return bodyChunks._iterator.lightConst[bodyChunks.length]; 214 else 215 return bodyChunks._iterator._field.lightConst[bodyChunks._iterator._index + bodyChunks.length]; 216 } 217 218 U negCentralMask() 219 { 220 return U.max << centralLength; 221 } 222 223 U negHeadMask() 224 { 225 return U.max << headLength; 226 } 227 228 U negTailMask() 229 { 230 return U.max << tailLength; 231 } 232 233 U negCentralMaskS() 234 { 235 return U.max >> centralLength; 236 } 237 238 U negHeadMaskS() 239 { 240 return U.max >> headLength; 241 } 242 243 U negTailMaskS() 244 { 245 return U.max >> tailLength; 246 } 247 248 U centralBitsWithRemainingZeros() 249 { 250 return centralBits & ~negCentralMask; 251 } 252 253 U centralBitsWithRemainingZerosS() 254 { 255 return centralBits << (U.sizeof * 8 - centralLength); 256 } 257 258 U headBitsWithRemainingZeros() 259 { 260 return headBits >>> (I.sizeof * 8 - headLength); 261 } 262 263 U headBitsWithRemainingZerosS() 264 { 265 static if (U.sizeof > I.sizeof) 266 return (headBits << (U.sizeof - I.sizeof) * 8) & ~negTailMaskS; 267 else 268 return headBits & ~negTailMaskS; 269 } 270 271 U tailBitsWithRemainingZeros() 272 { 273 return tailBits & ~negTailMask; 274 } 275 276 U tailBitsWithRemainingZerosS() 277 { 278 return tailBits << (U.sizeof * 8 - tailLength); 279 } 280 281 U centralBitsWithRemainingOnes() 282 { 283 return centralBits | negCentralMask; 284 } 285 286 U centralBitsWithRemainingOnesS() 287 { 288 return centralBitsWithRemainingZerosS | negCentralMaskS; 289 } 290 291 U headBitsWithRemainingOnes() 292 { 293 return headBitsWithRemainingZeros | negHeadMask; 294 } 295 296 U headBitsWithRemainingOnesS() 297 { 298 return headBitsWithRemainingZerosS | negHeadMaskS; 299 } 300 301 U tailBitsWithRemainingOnes() 302 { 303 return tailBits | negTailMask; 304 } 305 306 U tailBitsWithRemainingOnesS() 307 { 308 return tailBitsWithRemainingZerosS | negTailMaskS; 309 } 310 311 size_t ctpop() 312 { 313 import mir.bitop: ctpop; 314 if (isCentralProblem) 315 return centralBitsWithRemainingZeros.ctpop; 316 size_t ret; 317 if (headLength) 318 ret = cast(size_t) headBitsWithRemainingZeros.ctpop; 319 if (bodyChunks.length) 320 { 321 auto bc = bodyChunks.lightConst; 322 do 323 { 324 ret += cast(size_t) bc.front.ctpop; 325 bc.popFront; 326 } 327 while(bc.length); 328 } 329 if (tailBits) 330 ret += cast(size_t) tailBitsWithRemainingZeros.ctpop; 331 return ret; 332 } 333 334 bool any() 335 { 336 if (isCentralProblem) 337 return centralBitsWithRemainingZeros != 0; 338 if (headBitsWithRemainingZeros != 0) 339 return true; 340 if (bodyChunks.length) 341 { 342 auto bc = bodyChunks.lightConst; 343 do 344 { 345 if (bc.front != 0) 346 return true; 347 bc.popFront; 348 } 349 while(bc.length); 350 } 351 if (tailBitsWithRemainingZeros != 0) 352 return true; 353 return false; 354 } 355 356 bool all() 357 { 358 if (isCentralProblem) 359 return centralBitsWithRemainingOnes != U.max; 360 size_t ret; 361 if (headBitsWithRemainingOnes != U.max) 362 return false; 363 if (bodyChunks.length) 364 { 365 auto bc = bodyChunks.lightConst; 366 do 367 { 368 if (bc.front != I.max) 369 return false; 370 bc.popFront; 371 } 372 while(bc.length); 373 } 374 if (tailBitsWithRemainingOnes != U.max) 375 return false; 376 return true; 377 } 378 379 size_t cttz() 380 { 381 U v; 382 size_t ret; 383 if (isCentralProblem) 384 { 385 v = centralBitsWithRemainingOnes; 386 if (v) 387 goto R; 388 ret = centralLength; 389 goto L; 390 } 391 v = headBitsWithRemainingOnes; 392 if (v) 393 goto R; 394 ret = headLength; 395 if (bodyChunks.length) 396 { 397 auto bc = bodyChunks.lightConst; 398 do 399 { 400 v = bc.front; 401 if (v) 402 goto R; 403 ret += I.sizeof * 8; 404 bc.popFront; 405 } 406 while(bc.length); 407 } 408 v = tailBitsWithRemainingOnes; 409 if (v) 410 goto R; 411 ret += tailLength; 412 goto L; 413 R: 414 ret += v.cttz; 415 L: 416 return ret; 417 } 418 419 size_t ctlz() 420 { 421 U v; 422 size_t ret; 423 if (isCentralProblem) 424 { 425 v = centralBitsWithRemainingOnes; 426 if (v) 427 goto R; 428 ret = centralLength; 429 goto L; 430 } 431 v = tailBitsWithRemainingOnesS; 432 if (v) 433 goto R; 434 ret = tailLength; 435 if (bodyChunks.length) 436 { 437 auto bc = bodyChunks.lightConst; 438 do 439 { 440 v = bc.back; 441 if (v) 442 goto R; 443 ret += I.sizeof * 8; 444 bc.popBack; 445 } 446 while(bc.length); 447 } 448 v = headBitsWithRemainingOnesS; 449 if (v) 450 goto R; 451 ret += headLength; 452 goto L; 453 R: 454 ret += v.ctlz; 455 L: 456 return ret; 457 } 458 459 sizediff_t nBitsToCount(size_t count) 460 { 461 size_t ret; 462 if (count == 0) 463 return count; 464 U v, cnt; 465 if (isCentralProblem) 466 { 467 v = centralBitsWithRemainingZeros; 468 goto E; 469 } 470 v = headBitsWithRemainingZeros; 471 cnt = v.ctpop; 472 if (cnt >= count) 473 goto R; 474 ret += headLength; 475 count -= cast(size_t) cnt; 476 if (bodyChunks.length) 477 { 478 auto bc = bodyChunks.lightConst; 479 do 480 { 481 v = bc.front; 482 cnt = v.ctpop; 483 if (cnt >= count) 484 goto R; 485 ret += I.sizeof * 8; 486 count -= cast(size_t) cnt; 487 bc.popFront; 488 } 489 while(bc.length); 490 } 491 v = tailBitsWithRemainingZeros; 492 E: 493 cnt = v.ctpop; 494 if (cnt >= count) 495 goto R; 496 return -1; 497 R: 498 return ret + v.nTrailingBitsToCount(count); 499 } 500 501 sizediff_t retroNBitsToCount(size_t count) 502 { 503 if (count == 0) 504 return count; 505 size_t ret; 506 U v, cnt; 507 if (isCentralProblem) 508 { 509 v = centralBitsWithRemainingZerosS; 510 goto E; 511 } 512 v = tailBitsWithRemainingZerosS; 513 cnt = v.ctpop; 514 if (cnt >= count) 515 goto R; 516 ret += tailLength; 517 count -= cast(size_t) cnt; 518 if (bodyChunks.length) 519 { 520 auto bc = bodyChunks.lightConst; 521 do 522 { 523 v = bc.back; 524 cnt = v.ctpop; 525 if (cnt >= count) 526 goto R; 527 ret += I.sizeof * 8; 528 count -= cast(size_t) cnt; 529 bc.popBack; 530 } 531 while(bc.length); 532 } 533 v = headBitsWithRemainingZerosS; 534 E: 535 cnt = v.ctpop; 536 if (cnt >= count) 537 goto R; 538 return -1; 539 R: 540 return ret + v.nLeadingBitsToCount(count); 541 } 542 } 543 544 /++ 545 Сount bits until set bit count is reached. Works with ndslices created with $(REF bitwise, mir,ndslice,topology), $(REF bitSlice, mir,ndslice,allocation). 546 Returns: bit count if set bit count is reached or `-1` otherwise. 547 +/ 548 sizediff_t nBitsToCount(Field, I)(Slice!(FieldIterator!(BitField!(Field, I))) bitSlice, size_t count) 549 { 550 return BitSliceAccelerator!(Field, I)(bitSlice).nBitsToCount(count); 551 } 552 553 ///ditto 554 sizediff_t nBitsToCount(Field, I)(Slice!(RetroIterator!(FieldIterator!(BitField!(Field, I)))) bitSlice, size_t count) 555 { 556 import mir.ndslice.topology: retro; 557 return BitSliceAccelerator!(Field, I)(bitSlice.retro).retroNBitsToCount(count); 558 } 559 560 /// 561 pure unittest 562 { 563 import mir.ndslice.allocation: bitSlice; 564 import mir.ndslice.topology: retro; 565 auto s = bitSlice(1000); 566 s[50] = true; 567 s[100] = true; 568 s[200] = true; 569 s[300] = true; 570 s[400] = true; 571 assert(s.nBitsToCount(4) == 301); 572 assert(s.retro.nBitsToCount(4) == 900); 573 } 574 575 private void checkShapesMatch( 576 string fun = __FUNCTION__, 577 string pfun = __PRETTY_FUNCTION__, 578 Slices...) 579 (scope ref const Slices slices) 580 if (Slices.length > 1) 581 { 582 enum msgShape = "all slices must have the same shape" ~ tailErrorMessage!(fun, pfun); 583 enum N = slices[0].shape.length; 584 foreach (i, Slice; Slices) 585 { 586 static if (i == 0) 587 continue; 588 else 589 static if (slices[i].shape.length == N) 590 assert(slices[i].shape == slices[0].shape, msgShape); 591 else 592 { 593 import mir.ndslice.fuse: fuseShape; 594 static assert(slices[i].fuseShape.length >= N); 595 assert(cast(size_t[N])slices[i].fuseShape[0 .. N] == slices[0].shape, msgShape); 596 } 597 } 598 } 599 600 601 package(mir) template allFlattened(args...) 602 { 603 static if (args.length) 604 { 605 alias arg = args[0]; 606 @optmath @property ls()() 607 { 608 import mir.ndslice.topology: flattened; 609 return flattened(arg); 610 } 611 alias allFlattened = AliasSeq!(ls, allFlattened!(args[1..$])); 612 } 613 else 614 alias allFlattened = AliasSeq!(); 615 } 616 617 private template areAllContiguousSlices(Slices...) 618 { 619 import mir.ndslice.traits: isContiguousSlice; 620 static if (allSatisfy!(isContiguousSlice, Slices)) 621 enum areAllContiguousSlices = Slices[0].N > 1 && areAllContiguousSlicesImpl!(Slices[0].N, Slices[1 .. $]); 622 else 623 enum areAllContiguousSlices = false; 624 } 625 626 private template areAllContiguousSlicesImpl(size_t N, Slices...) 627 { 628 static if (Slices.length == 0) 629 enum areAllContiguousSlicesImpl = true; 630 else 631 enum areAllContiguousSlicesImpl = Slices[0].N == N && areAllContiguousSlicesImpl!(N, Slices[1 .. $]); 632 } 633 634 version(LDC) {} 635 else version(GNU) {} 636 else version (Windows) {} 637 else version (X86_64) 638 { 639 //Compiling with DMD for x86-64 for Linux & OS X with optimizations enabled, 640 //"Tensor mutation on-the-fly" unittest was failing. Disabling inlining 641 //caused it to succeed. 642 //TODO: Rework so this is unnecessary! 643 version = Mir_disable_inlining_in_reduce; 644 } 645 646 version(Mir_disable_inlining_in_reduce) 647 { 648 private enum Mir_disable_inlining_in_reduce = true; 649 650 private template _naryAliases(size_t n) 651 { 652 static if (n == 0) 653 enum _naryAliases = ""; 654 else 655 { 656 enum i = n - 1; 657 enum _naryAliases = _naryAliases!i ~ "alias " ~ cast(char)('a' + i) ~ " = args[" ~ i.stringof ~ "];\n"; 658 } 659 } 660 661 private template nonInlinedNaryFun(alias fun) 662 { 663 import mir.math.common : optmath; 664 static if (is(typeof(fun) : string)) 665 { 666 /// Specialization for string lambdas 667 @optmath auto ref nonInlinedNaryFun(Args...)(auto ref Args args) 668 if (args.length <= 26) 669 { 670 pragma(inline,false); 671 mixin(_naryAliases!(Args.length)); 672 return mixin(fun); 673 } 674 } 675 else static if (is(typeof(fun.opCall) == function)) 676 { 677 @optmath auto ref nonInlinedNaryFun(Args...)(auto ref Args args) 678 if (is(typeof(fun.opCall(args)))) 679 { 680 pragma(inline,false); 681 return fun.opCall(args); 682 } 683 } 684 else 685 { 686 @optmath auto ref nonInlinedNaryFun(Args...)(auto ref Args args) 687 if (is(typeof(fun(args)))) 688 { 689 pragma(inline,false); 690 return fun(args); 691 } 692 } 693 } 694 } 695 else 696 { 697 private enum Mir_disable_inlining_in_reduce = false; 698 } 699 700 S reduceImpl(alias fun, S, Slices...)(S seed, scope Slices slices) 701 { 702 do 703 { 704 static if (DimensionCount!(Slices[0]) == 1) 705 seed = fun(seed, frontOf!slices); 706 else 707 seed = .reduceImpl!fun(seed, frontOf!slices); 708 foreach_reverse(ref slice; slices) 709 slice.popFront; 710 } 711 while(!slices[0].empty); 712 return seed; 713 } 714 715 /++ 716 Implements the homonym function (also known as `accumulate`, 717 `compress`, `inject`, or `fold`) present in various programming 718 languages of functional flavor. The call `reduce!(fun)(seed, slice1, ..., sliceN)` 719 first assigns `seed` to an internal variable `result`, 720 also called the accumulator. Then, for each set of element `x1, ..., xN` in 721 `slice1, ..., sliceN`, `result = fun(result, x1, ..., xN)` gets evaluated. Finally, 722 `result` is returned. 723 724 `reduce` allows to iterate multiple slices in the lockstep. 725 726 Note: 727 $(NDSLICEREF topology, pack) can be used to specify dimensions. 728 Params: 729 fun = A function. 730 See_Also: 731 $(HTTP llvm.org/docs/LangRef.html#fast-math-flags, LLVM IR: Fast Math Flags) 732 733 $(HTTP en.wikipedia.org/wiki/Fold_(higher-order_function), Fold (higher-order function)) 734 +/ 735 template reduce(alias fun) 736 { 737 import mir.functional: naryFun; 738 static if (__traits(isSame, naryFun!fun, fun) 739 && !Mir_disable_inlining_in_reduce) 740 /++ 741 Params: 742 seed = An initial accumulation value. 743 slices = One or more slices, range, and arrays. 744 Returns: 745 the accumulated `result` 746 +/ 747 @optmath auto reduce(S, Slices...)(S seed, scope Slices slices) 748 if (Slices.length) 749 { 750 static if (Slices.length > 1) 751 slices.checkShapesMatch; 752 static if (areAllContiguousSlices!Slices) 753 { 754 import mir.ndslice.topology: flattened; 755 return .reduce!fun(seed, allFlattened!(allLightScope!slices)); 756 } 757 else 758 { 759 if (slices[0].anyEmpty) 760 return cast(Unqual!S) seed; 761 static if (is(S : Unqual!S)) 762 alias UT = Unqual!S; 763 else 764 alias UT = S; 765 return reduceImpl!(fun, UT, Slices)(seed, allLightScope!slices); 766 } 767 } 768 else version(Mir_disable_inlining_in_reduce) 769 //As above, but with inlining disabled. 770 @optmath auto reduce(S, Slices...)(S seed, scope Slices slices) 771 if (Slices.length) 772 { 773 static if (Slices.length > 1) 774 slices.checkShapesMatch; 775 static if (areAllContiguousSlices!Slices) 776 { 777 import mir.ndslice.topology: flattened; 778 return .reduce!fun(seed, allFlattened!(allLightScope!slices)); 779 } 780 else 781 { 782 if (slices[0].anyEmpty) 783 return cast(Unqual!S) seed; 784 static if (is(S : Unqual!S)) 785 alias UT = Unqual!S; 786 else 787 alias UT = S; 788 return reduceImpl!(nonInlinedNaryFun!fun, UT, Slices)(seed, allLightScope!slices); 789 } 790 } 791 else 792 alias reduce = .reduce!(naryFun!fun); 793 } 794 795 /// Ranges and arrays 796 version(mir_test) 797 unittest 798 { 799 auto ar = [1, 2, 3]; 800 auto s = 0.reduce!"a + b"(ar); 801 assert (s == 6); 802 } 803 804 /// Single slice 805 version(mir_test) 806 unittest 807 { 808 import mir.ndslice.topology : iota; 809 810 //| 0 1 2 | => 3 | 811 //| 3 4 5 | => 12 | => 15 812 auto sl = iota(2, 3); 813 814 // sum of all element in the slice 815 auto res = size_t(0).reduce!"a + b"(sl); 816 817 assert(res == 15); 818 } 819 820 /// Multiple slices, dot product 821 version(mir_test) 822 unittest 823 { 824 import mir.ndslice.allocation : slice; 825 import mir.ndslice.topology : as, iota; 826 827 //| 0 1 2 | 828 //| 3 4 5 | 829 auto a = iota([2, 3], 0).as!double.slice; 830 //| 1 2 3 | 831 //| 4 5 6 | 832 auto b = iota([2, 3], 1).as!double.slice; 833 834 alias dot = reduce!"a + b * c"; 835 auto res = dot(0.0, a, b); 836 837 // check the result: 838 import mir.ndslice.topology : flattened; 839 import std.numeric : dotProduct; 840 assert(res == dotProduct(a.flattened, b.flattened)); 841 } 842 843 /// Zipped slices, dot product 844 pure 845 version(mir_test) unittest 846 { 847 import std.typecons : Yes; 848 import std.numeric : dotProduct; 849 import mir.ndslice.allocation : slice; 850 import mir.ndslice.topology : as, iota, zip, universal; 851 import mir.math.common : optmath; 852 853 static @optmath T fmuladd(T, Z)(const T a, Z z) 854 { 855 return a + z.a * z.b; 856 } 857 858 // 0 1 2 859 // 3 4 5 860 auto sl1 = iota(2, 3).as!double.slice.universal; 861 // 1 2 3 862 // 4 5 6 863 auto sl2 = iota([2, 3], 1).as!double.slice; 864 865 // slices must have the same strides for `zip!true`. 866 assert(sl1.strides == sl2.strides); 867 868 auto z = zip!true(sl1, sl2); 869 870 auto dot = reduce!fmuladd(0.0, z); 871 872 assert(dot == dotProduct(iota(6), iota([6], 1))); 873 } 874 875 /// Tensor mutation on-the-fly 876 version(mir_test) 877 unittest 878 { 879 import mir.ndslice.allocation : slice; 880 import mir.ndslice.topology : as, iota; 881 import mir.math.common : optmath; 882 883 static @optmath T fun(T)(const T a, ref T b) 884 { 885 return a + b++; 886 } 887 888 //| 0 1 2 | 889 //| 3 4 5 | 890 auto sl = iota(2, 3).as!double.slice; 891 892 auto res = reduce!fun(double(0), sl); 893 894 assert(res == 15); 895 896 //| 1 2 3 | 897 //| 4 5 6 | 898 assert(sl == iota([2, 3], 1)); 899 } 900 901 /++ 902 Packed slices. 903 904 Computes minimum value of maximum values for each row. 905 +/ 906 version(mir_test) 907 unittest 908 { 909 import mir.math.common; 910 import mir.ndslice.allocation : slice; 911 import mir.ndslice.dynamic : transposed; 912 import mir.ndslice.topology : as, iota, pack, map, universal; 913 914 alias maxVal = (a) => reduce!fmax(-double.infinity, a); 915 alias minVal = (a) => reduce!fmin(double.infinity, a); 916 alias minimaxVal = (a) => minVal(a.pack!1.map!maxVal); 917 918 auto sl = iota(2, 3).as!double.slice; 919 920 // Vectorized computation: row stride equals 1. 921 //| 0 1 2 | => | 2 | 922 //| 3 4 5 | => | 5 | => 2 923 auto res = minimaxVal(sl); 924 assert(res == 2); 925 926 // Common computation: row stride does not equal 1. 927 //| 0 1 2 | | 0 3 | => | 3 | 928 //| 3 4 5 | => | 1 4 | => | 4 | 929 // | 2 5 | => | 5 | => 3 930 auto resT = minimaxVal(sl.universal.transposed); 931 assert(resT == 3); 932 } 933 934 /// Dlang Range API support. 935 version(mir_test) 936 unittest 937 { 938 import mir.algorithm.iteration: each; 939 import std.range: phobos_iota = iota; 940 941 int s; 942 // 0 1 2 3 943 4.phobos_iota.each!(i => s += i); 944 assert(s == 6); 945 } 946 947 @safe pure nothrow @nogc 948 version(mir_test) unittest 949 { 950 import mir.ndslice.topology : iota; 951 auto a = reduce!"a + b"(size_t(7), iota([0, 1], 1)); 952 assert(a == 7); 953 } 954 955 void eachImpl(alias fun, Slices...)(scope Slices slices) 956 { 957 foreach(ref slice; slices) 958 assert(!slice.empty); 959 do 960 { 961 static if (DimensionCount!(Slices[0]) == 1) 962 fun(frontOf!slices); 963 else 964 .eachImpl!fun(frontOf!slices); 965 foreach_reverse(i; Iota!(Slices.length)) 966 slices[i].popFront; 967 } 968 while(!slices[0].empty); 969 } 970 971 void chequerEachImpl(alias fun, Slices...)(Chequer color, scope Slices slices) 972 { 973 foreach(ref slice; slices) 974 assert(!slice.empty); 975 static if (DimensionCount!(Slices[0]) == 1) 976 { 977 if (color) 978 { 979 foreach_reverse(i; Iota!(Slices.length)) 980 slices[i].popFront; 981 if (slices[0].empty) 982 return; 983 } 984 eachImpl!fun(strideOf!slices); 985 } 986 else 987 { 988 do 989 { 990 .chequerEachImpl!fun(color, frontOf!slices); 991 color = cast(Chequer)!color; 992 foreach_reverse(i; Iota!(Slices.length)) 993 slices[i].popFront; 994 } 995 while(!slices[0].empty); 996 } 997 } 998 999 /++ 1000 The call `each!(fun)(slice1, ..., sliceN)` 1001 evaluates `fun` for each set of elements `x1, ..., xN` in 1002 the borders of `slice1, ..., sliceN` respectively. 1003 1004 `each` allows to iterate multiple slices in the lockstep. 1005 1006 Params: 1007 fun = A function. 1008 Note: 1009 $(NDSLICEREF dynamic, transposed) and 1010 $(NDSLICEREF topology, pack) can be used to specify dimensions. 1011 +/ 1012 template eachOnBorder(alias fun) 1013 { 1014 import mir.functional: naryFun; 1015 static if (__traits(isSame, naryFun!fun, fun)) 1016 /++ 1017 Params: 1018 slices = One or more slices. 1019 +/ 1020 @optmath void eachOnBorder(Slices...)(Slices slices) 1021 if (allSatisfy!(isSlice, Slices)) 1022 { 1023 import mir.ndslice.traits: isContiguousSlice; 1024 static if (Slices.length > 1) 1025 slices.checkShapesMatch; 1026 if (!slices[0].anyEmpty) 1027 { 1028 alias N = DimensionCount!(Slices[0]); 1029 static if (N == 1) 1030 { 1031 fun(frontOf!slices); 1032 if (slices[0].length > 1) 1033 fun(backOf!slices); 1034 } 1035 else 1036 static if (anySatisfy!(isContiguousSlice, Slices)) 1037 { 1038 import mir.ndslice.topology: canonical; 1039 template f(size_t i) 1040 { 1041 static if (isContiguousSlice!(Slices[i])) 1042 auto f () { return canonical(slices[i]); } 1043 else 1044 alias f = slices[i]; 1045 } 1046 eachOnBorder(staticMap!(f, Iota!(Slices.length))); 1047 } 1048 else 1049 { 1050 foreach (dimension; Iota!N) 1051 { 1052 eachImpl!fun(frontOfD!(dimension, slices)); 1053 foreach_reverse(ref slice; slices) 1054 slice.popFront!dimension; 1055 if (slices[0].empty!dimension) 1056 return; 1057 eachImpl!fun(backOfD!(dimension, slices)); 1058 foreach_reverse(ref slice; slices) 1059 slice.popBack!dimension; 1060 if (slices[0].empty!dimension) 1061 return; 1062 } 1063 } 1064 } 1065 } 1066 else 1067 alias eachOnBorder = .eachOnBorder!(naryFun!fun); 1068 } 1069 1070 /// 1071 @safe pure nothrow 1072 version(mir_test) unittest 1073 { 1074 import mir.ndslice.allocation : slice; 1075 import mir.ndslice.topology : repeat, iota; 1076 1077 auto sl = [3, 4].iota.slice; 1078 auto zeros = repeat(0, [3, 4]); 1079 1080 sl.eachOnBorder!"a = b"(zeros); 1081 1082 assert(sl == 1083 [[0, 0, 0 ,0], 1084 [0, 5, 6, 0], 1085 [0, 0, 0 ,0]]); 1086 1087 sl.eachOnBorder!"a = 1"; 1088 sl[0].eachOnBorder!"a = 2"; 1089 1090 assert(sl == 1091 [[2, 1, 1, 2], 1092 [1, 5, 6, 1], 1093 [1, 1, 1 ,1]]); 1094 } 1095 1096 /++ 1097 The call `each!(fun)(slice1, ..., sliceN)` 1098 evaluates `fun` for each set of elements `x1, ..., xN` in 1099 `slice1, ..., sliceN` respectively. 1100 1101 `each` allows to iterate multiple slices in the lockstep. 1102 Params: 1103 fun = A function. 1104 Note: 1105 $(NDSLICEREF dynamic, transposed) and 1106 $(NDSLICEREF topology, pack) can be used to specify dimensions. 1107 See_Also: 1108 This is functionally similar to $(LREF reduce) but has not seed. 1109 +/ 1110 template each(alias fun) 1111 { 1112 import mir.functional: naryFun; 1113 static if (__traits(isSame, naryFun!fun, fun)) 1114 { 1115 /++ 1116 Params: 1117 slices = One or more slices, ranges, and arrays. 1118 +/ 1119 @optmath auto each(Slices...)(scope Slices slices) 1120 if (Slices.length && !is(Slices[0] : Chequer)) 1121 { 1122 static if (Slices.length > 1) 1123 slices.checkShapesMatch; 1124 static if (areAllContiguousSlices!Slices) 1125 { 1126 import mir.ndslice.topology: flattened; 1127 .each!fun(allFlattened!(allLightScope!slices)); 1128 } 1129 else 1130 { 1131 if (slices[0].anyEmpty) 1132 return; 1133 eachImpl!fun(allLightScope!slices); 1134 } 1135 } 1136 1137 /++ 1138 Iterates elements of selected $(LREF Chequer) color. 1139 Params: 1140 color = $(LREF Chequer). 1141 slices = One or more slices. 1142 +/ 1143 @optmath auto each(Slices...)(Chequer color, scope Slices slices) 1144 if (Slices.length && allSatisfy!(isSlice, Slices)) 1145 { 1146 static if (Slices.length > 1) 1147 slices.checkShapesMatch; 1148 if (slices[0].anyEmpty) 1149 return; 1150 chequerEachImpl!fun(color, allLightScope!slices); 1151 } 1152 } 1153 else 1154 alias each = .each!(naryFun!fun); 1155 } 1156 1157 /// Ranges and arrays 1158 version(mir_test) 1159 unittest 1160 { 1161 auto ar = [1, 2, 3]; 1162 ar.each!"a *= 2"; 1163 assert (ar == [2, 4, 6]); 1164 } 1165 1166 /// Single slice, multiply-add 1167 version(mir_test) 1168 unittest 1169 { 1170 import mir.ndslice.allocation : slice; 1171 import mir.ndslice.topology : as, iota; 1172 1173 //| 0 1 2 | 1174 //| 3 4 5 | 1175 auto sl = iota(2, 3).as!double.slice; 1176 1177 sl.each!((ref a) { a = a * 10 + 5; }); 1178 1179 assert(sl == 1180 [[ 5, 15, 25], 1181 [35, 45, 55]]); 1182 } 1183 1184 /// Swap two slices 1185 version(mir_test) 1186 unittest 1187 { 1188 import mir.utility : swap; 1189 import mir.ndslice.allocation : slice; 1190 import mir.ndslice.topology : as, iota; 1191 1192 //| 0 1 2 | 1193 //| 3 4 5 | 1194 auto a = iota([2, 3], 0).as!double.slice; 1195 //| 10 11 12 | 1196 //| 13 14 15 | 1197 auto b = iota([2, 3], 10).as!double.slice; 1198 1199 each!swap(a, b); 1200 1201 assert(a == iota([2, 3], 10)); 1202 assert(b == iota([2, 3], 0)); 1203 } 1204 1205 /// Swap two zipped slices 1206 version(mir_test) 1207 unittest 1208 { 1209 import mir.utility : swap; 1210 import mir.ndslice.allocation : slice; 1211 import mir.ndslice.topology : as, zip, iota; 1212 1213 //| 0 1 2 | 1214 //| 3 4 5 | 1215 auto a = iota([2, 3], 0).as!double.slice; 1216 //| 10 11 12 | 1217 //| 13 14 15 | 1218 auto b = iota([2, 3], 10).as!double.slice; 1219 1220 auto z = zip(a, b); 1221 1222 z.each!(z => swap(z.a, z.b)); 1223 1224 assert(a == iota([2, 3], 10)); 1225 assert(b == iota([2, 3], 0)); 1226 } 1227 1228 @safe pure nothrow 1229 version(mir_test) unittest 1230 { 1231 import mir.ndslice.topology : iota; 1232 size_t i; 1233 iota(0, 2).each!((a){i++;}); 1234 assert(i == 0); 1235 } 1236 1237 /++ 1238 The call `eachUploPair!(fun)(matrix)` 1239 evaluates `fun` for each pair (`matrix[j, i]`, `matrix[i, j]`), 1240 for i <= j (default) or i < j (if includeDiagonal is false). 1241 1242 Params: 1243 fun = A function. 1244 includeDiagonal = true if applying function to diagonal, 1245 false (default) otherwise. 1246 +/ 1247 template eachUploPair(alias fun, bool includeDiagonal = false) 1248 { 1249 import mir.functional: naryFun; 1250 static if (__traits(isSame, naryFun!fun, fun)) 1251 { 1252 /++ 1253 Params: 1254 matrix = Square matrix. 1255 +/ 1256 auto eachUploPair(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) matrix) 1257 in 1258 { 1259 assert(matrix.length!0 == matrix.length!1, "matrix must be square."); 1260 } 1261 do 1262 { 1263 static if (kind == Contiguous) 1264 { 1265 import mir.ndslice.topology: canonical; 1266 .eachUploPair!(fun, includeDiagonal)(matrix.canonical); 1267 } 1268 else 1269 { 1270 static if (includeDiagonal == true) 1271 { 1272 if (matrix.length) do 1273 { 1274 eachImpl!fun(matrix.lightScope.front!0, matrix.lightScope.front!1); 1275 matrix.popFront!1; 1276 matrix.popFront!0; 1277 // hint for optimizer 1278 matrix._lengths[1] = matrix._lengths[0]; 1279 } 1280 while (matrix.length); 1281 } 1282 else 1283 { 1284 if (matrix.length) for(;;) 1285 { 1286 assert(!matrix.empty!0); 1287 assert(!matrix.empty!1); 1288 auto l = matrix.lightScope.front!1; 1289 auto u = matrix.lightScope.front!0; 1290 matrix.popFront!1; 1291 matrix.popFront!0; 1292 l.popFront; 1293 u.popFront; 1294 // hint for optimizer 1295 matrix._lengths[1] = matrix._lengths[0] = l._lengths[0] = u._lengths[0]; 1296 if (u.length == 0) 1297 break; 1298 eachImpl!fun(u, l); 1299 } 1300 } 1301 } 1302 } 1303 } 1304 else 1305 { 1306 alias eachUploPair = .eachUploPair!(naryFun!fun, includeDiagonal); 1307 } 1308 } 1309 1310 /// Transpose matrix in place. 1311 version(mir_test) 1312 unittest 1313 { 1314 import mir.ndslice.allocation: slice; 1315 import mir.ndslice.topology: iota, universal; 1316 import mir.ndslice.dynamic: transposed; 1317 import mir.utility: swap; 1318 1319 auto m = iota(4, 4).slice; 1320 1321 m.eachUploPair!swap; 1322 1323 assert(m == iota(4, 4).universal.transposed); 1324 } 1325 1326 /// Reflect Upper matrix part to lower part. 1327 version(mir_test) 1328 unittest 1329 { 1330 import mir.ndslice.allocation: slice; 1331 import mir.ndslice.topology: iota, universal; 1332 import mir.ndslice.dynamic: transposed; 1333 import mir.utility: swap; 1334 1335 // 0 1 2 1336 // 3 4 5 1337 // 6 7 8 1338 auto m = iota(3, 3).slice; 1339 1340 m.eachUploPair!((u, ref l) { l = u; }); 1341 1342 assert(m == [ 1343 [0, 1, 2], 1344 [1, 4, 5], 1345 [2, 5, 8]]); 1346 } 1347 1348 /// Fill lower triangle and diagonal with zeroes. 1349 version(mir_test) 1350 unittest 1351 { 1352 import mir.ndslice.allocation: slice; 1353 import mir.ndslice.topology: iota; 1354 1355 // 1 2 3 1356 // 4 5 6 1357 // 7 8 9 1358 auto m = iota([3, 3], 1).slice; 1359 1360 m.eachUploPair!((u, ref l) { l = 0; }, true); 1361 1362 assert(m == [ 1363 [0, 2, 3], 1364 [0, 0, 6], 1365 [0, 0, 0]]); 1366 } 1367 1368 version(mir_test) 1369 unittest 1370 { 1371 import mir.ndslice.allocation: slice; 1372 import mir.ndslice.topology: iota; 1373 1374 // 0 1 2 1375 // 3 4 5 1376 // 6 7 8 1377 auto m = iota(3, 3).slice; 1378 m.eachUploPair!((u, ref l) { l = l + 1; }, true); 1379 assert(m == [ 1380 [1, 1, 2], 1381 [4, 5, 5], 1382 [7, 8, 9]]); 1383 } 1384 1385 version(mir_test) 1386 unittest 1387 { 1388 import mir.ndslice.allocation: slice; 1389 import mir.ndslice.topology: iota; 1390 1391 // 0 1 2 1392 // 3 4 5 1393 // 6 7 8 1394 auto m = iota(3, 3).slice; 1395 m.eachUploPair!((u, ref l) { l = l + 1; }, false); 1396 1397 assert(m == [ 1398 [0, 1, 2], 1399 [4, 4, 5], 1400 [7, 8, 8]]); 1401 } 1402 1403 /++ 1404 Checks if the matrix is symmetric. 1405 +/ 1406 template isSymmetric(alias fun = "a == b") 1407 { 1408 import mir.functional: naryFun; 1409 static if (__traits(isSame, naryFun!fun, fun)) 1410 /++ 1411 Params: 1412 matrix = 2D ndslice. 1413 +/ 1414 bool isSymmetric(Iterator, SliceKind kind)(Slice!(Iterator, 2, kind) matrix) 1415 { 1416 static if (kind == Contiguous) 1417 { 1418 import mir.ndslice.topology: canonical; 1419 return .isSymmetric!fun(matrix.canonical); 1420 } 1421 else 1422 { 1423 if (matrix.length!0 != matrix.length!1) 1424 return false; 1425 if (matrix.length) do 1426 { 1427 if (!allImpl!fun(matrix.lightScope.front!0, matrix.lightScope.front!1)) 1428 { 1429 return false; 1430 } 1431 matrix.popFront!1; 1432 matrix.popFront!0; 1433 matrix._lengths[1] = matrix._lengths[0]; 1434 } 1435 while (matrix.length); 1436 return true; 1437 } 1438 } 1439 else 1440 alias isSymmetric = .isSymmetric!(naryFun!fun); 1441 } 1442 1443 /// 1444 version(mir_test) 1445 unittest 1446 { 1447 import mir.ndslice.slice: sliced; 1448 import mir.ndslice.topology: iota; 1449 assert(iota(2, 2).isSymmetric == false); 1450 1451 assert( 1452 [1, 2, 1453 2, 3].sliced(2, 2).isSymmetric == true); 1454 } 1455 1456 bool minPosImpl(alias fun, Iterator, size_t N, SliceKind kind)(scope ref size_t[N] backwardIndex, scope ref Iterator iterator, Slice!(Iterator, N, kind) slice) 1457 { 1458 bool found; 1459 do 1460 { 1461 static if (slice.shape.length == 1) 1462 { 1463 if (fun(*slice._iterator, *iterator)) 1464 { 1465 backwardIndex[0] = slice.length; 1466 iterator = slice._iterator; 1467 found = true; 1468 } 1469 } 1470 else 1471 { 1472 if (minPosImpl!(fun, LightScopeOf!Iterator, N - 1, kind)(backwardIndex[1 .. $], iterator, lightScope(slice).front)) 1473 { 1474 backwardIndex[0] = slice.length; 1475 found = true; 1476 } 1477 } 1478 slice.popFront; 1479 } 1480 while(!slice.empty); 1481 return found; 1482 } 1483 1484 bool[2] minmaxPosImpl(alias fun, Iterator, size_t N, SliceKind kind)(scope ref size_t[2][N] backwardIndex, scope ref Iterator[2] iterator, Slice!(Iterator, N, kind) slice) 1485 { 1486 bool[2] found; 1487 do 1488 { 1489 static if (slice.shape.length == 1) 1490 { 1491 if (fun(*slice._iterator, *iterator[0])) 1492 { 1493 backwardIndex[0][0] = slice.length; 1494 iterator[0] = slice._iterator; 1495 found[0] = true; 1496 } 1497 else 1498 if (fun(*iterator[1], *slice._iterator)) 1499 { 1500 backwardIndex[0][1] = slice.length; 1501 iterator[1] = slice._iterator; 1502 found[1] = true; 1503 } 1504 } 1505 else 1506 { 1507 auto r = minmaxPosImpl!(fun, LightScopeOf!Iterator, N - 1, kind)(backwardIndex[1 .. $], iterator, lightScope(slice).front); 1508 if (r[0]) 1509 { 1510 backwardIndex[0][0] = slice.length; 1511 } 1512 if (r[1]) 1513 { 1514 backwardIndex[0][1] = slice.length; 1515 } 1516 } 1517 slice.popFront; 1518 } 1519 while(!slice.empty); 1520 return found; 1521 } 1522 1523 /++ 1524 Finds a positions (ndslices) such that 1525 `position[0].first` is minimal and `position[1].first` is maximal elements in the slice. 1526 1527 Position is sub-ndslice of the same dimension in the right-$(RPAREN)down-$(RPAREN)etc$(LPAREN)$(LPAREN) corner. 1528 1529 Params: 1530 pred = A predicate. 1531 1532 See_also: 1533 $(LREF minmaxIndex), 1534 $(LREF minPos), 1535 $(LREF maxPos), 1536 $(NDSLICEREF slice, Slice.backward). 1537 +/ 1538 template minmaxPos(alias pred = "a < b") 1539 { 1540 import mir.functional: naryFun; 1541 static if (__traits(isSame, naryFun!pred, pred)) 1542 /++ 1543 Params: 1544 slice = ndslice. 1545 Returns: 1546 2 subslices with minimal and maximal `first` elements. 1547 +/ 1548 @optmath Slice!(Iterator, N, kind == Contiguous && N > 1 ? Canonical : kind)[2] 1549 minmaxPos(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) 1550 { 1551 typeof(return) pret; 1552 if (!slice.anyEmpty) 1553 { 1554 size_t[2][N] ret; 1555 auto scopeSlice = lightScope(slice); 1556 auto it = scopeSlice._iterator; 1557 LightScopeOf!Iterator[2] iterator = [it, it]; 1558 minmaxPosImpl!(pred, LightScopeOf!Iterator, N, kind)(ret, iterator, scopeSlice); 1559 foreach (i; Iota!N) 1560 { 1561 pret[0]._lengths[i] = ret[i][0]; 1562 pret[1]._lengths[i] = ret[i][1]; 1563 } 1564 pret[0]._iterator = slice._iterator + (iterator[0] - scopeSlice._iterator); 1565 pret[1]._iterator = slice._iterator + (iterator[1] - scopeSlice._iterator); 1566 } 1567 auto strides = slice.strides; 1568 foreach(i; Iota!(0, pret[0].S)) 1569 { 1570 pret[0]._strides[i] = strides[i]; 1571 pret[1]._strides[i] = strides[i]; 1572 } 1573 return pret; 1574 } 1575 else 1576 alias minmaxPos = .minmaxPos!(naryFun!pred); 1577 } 1578 1579 /// 1580 version(mir_test) 1581 unittest 1582 { 1583 import mir.ndslice.slice: sliced; 1584 auto s = [ 1585 2, 6, 4, -3, 1586 0, -4, -3, 3, 1587 -3, -2, 7, 2, 1588 ].sliced(3, 4); 1589 1590 auto pos = s.minmaxPos; 1591 1592 assert(pos[0] == s[$ - 2 .. $, $ - 3 .. $]); 1593 assert(pos[1] == s[$ - 1 .. $, $ - 2 .. $]); 1594 1595 assert(pos[0].first == -4); 1596 assert(s.backward(pos[0].shape) == -4); 1597 assert(pos[1].first == 7); 1598 assert(s.backward(pos[1].shape) == 7); 1599 } 1600 1601 /++ 1602 Finds a backward indices such that 1603 `slice[indices[0]]` is minimal and `slice[indices[1]]` is maximal elements in the slice. 1604 1605 Params: 1606 pred = A predicate. 1607 1608 See_also: 1609 $(LREF minmaxIndex), 1610 $(LREF minPos), 1611 $(LREF maxPos), 1612 $(REF Slice.backward, mir,ndslice,slice). 1613 +/ 1614 template minmaxIndex(alias pred = "a < b") 1615 { 1616 import mir.functional: naryFun; 1617 static if (__traits(isSame, naryFun!pred, pred)) 1618 /++ 1619 Params: 1620 slice = ndslice. 1621 Returns: 1622 Subslice with minimal (maximal) `first` element. 1623 +/ 1624 @optmath size_t[N][2] minmaxIndex(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) 1625 { 1626 typeof(return) pret = size_t.max; 1627 if (!slice.anyEmpty) 1628 { 1629 auto shape = slice.shape; 1630 size_t[2][N] ret; 1631 foreach (i; Iota!N) 1632 { 1633 ret[i][1] = ret[i][0] = shape[i]; 1634 } 1635 auto scopeSlice = lightScope(slice); 1636 auto it = scopeSlice._iterator; 1637 LightScopeOf!Iterator[2] iterator = [it, it]; 1638 minmaxPosImpl!(pred, LightScopeOf!Iterator, N, kind)(ret, iterator, scopeSlice); 1639 foreach (i; Iota!N) 1640 { 1641 pret[0][i] = slice._lengths[i] - ret[i][0]; 1642 pret[1][i] = slice._lengths[i] - ret[i][1]; 1643 } 1644 } 1645 return pret; 1646 } 1647 else 1648 alias minmaxIndex = .minmaxIndex!(naryFun!pred); 1649 } 1650 1651 /// 1652 version(mir_test) 1653 unittest 1654 { 1655 import mir.ndslice.slice: sliced; 1656 auto s = [ 1657 2, 6, 4, -3, 1658 0, -4, -3, 3, 1659 -3, -2, 7, 8, 1660 ].sliced(3, 4); 1661 1662 auto indices = s.minmaxIndex; 1663 1664 assert(indices == [[1, 1], [2, 3]]); 1665 assert(s[indices[0]] == -4); 1666 assert(s[indices[1]] == 8); 1667 } 1668 1669 /++ 1670 Finds a backward index such that 1671 `slice.backward(index)` is minimal(maximal). 1672 1673 Params: 1674 pred = A predicate. 1675 1676 See_also: 1677 $(LREF minIndex), 1678 $(LREF maxPos), 1679 $(LREF maxIndex), 1680 $(REF Slice.backward, mir,ndslice,slice). 1681 +/ 1682 template minPos(alias pred = "a < b") 1683 { 1684 import mir.functional: naryFun; 1685 static if (__traits(isSame, naryFun!pred, pred)) 1686 /++ 1687 Params: 1688 slice = ndslice. 1689 Returns: 1690 Multidimensional backward index such that element is minimal(maximal). 1691 Backward index equals zeros, if slice is empty. 1692 +/ 1693 @optmath Slice!(Iterator, N, kind == Contiguous && N > 1 ? Canonical : kind) 1694 minPos(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) 1695 { 1696 typeof(return) ret; 1697 auto iterator = slice.lightScope._iterator; 1698 if (!slice.anyEmpty) 1699 { 1700 minPosImpl!(pred, LightScopeOf!Iterator, N, kind)(ret._lengths, iterator, lightScope(slice)); 1701 ret._iterator = slice._iterator + (iterator - slice.lightScope._iterator); 1702 } 1703 auto strides = slice.strides; 1704 foreach(i; Iota!(0, ret.S)) 1705 { 1706 ret._strides[i] = strides[i]; 1707 } 1708 return ret; 1709 } 1710 else 1711 alias minPos = .minPos!(naryFun!pred); 1712 } 1713 1714 /// ditto 1715 template maxPos(alias pred = "a < b") 1716 { 1717 import mir.functional: naryFun, reverseArgs; 1718 alias maxPos = minPos!(reverseArgs!(naryFun!pred)); 1719 } 1720 1721 /// 1722 version(mir_test) 1723 unittest 1724 { 1725 import mir.ndslice.slice: sliced; 1726 auto s = [ 1727 2, 6, 4, -3, 1728 0, -4, -3, 3, 1729 -3, -2, 7, 2, 1730 ].sliced(3, 4); 1731 1732 auto pos = s.minPos; 1733 1734 assert(pos == s[$ - 2 .. $, $ - 3 .. $]); 1735 assert(pos.first == -4); 1736 assert(s.backward(pos.shape) == -4); 1737 1738 pos = s.maxPos; 1739 1740 assert(pos == s[$ - 1 .. $, $ - 2 .. $]); 1741 assert(pos.first == 7); 1742 assert(s.backward(pos.shape) == 7); 1743 } 1744 1745 /++ 1746 Finds an index such that 1747 `slice[index]` is minimal(maximal). 1748 1749 Params: 1750 pred = A predicate. 1751 1752 See_also: 1753 $(LREF minIndex), 1754 $(LREF maxPos), 1755 $(LREF maxIndex). 1756 +/ 1757 template minIndex(alias pred = "a < b") 1758 { 1759 import mir.functional: naryFun; 1760 static if (__traits(isSame, naryFun!pred, pred)) 1761 /++ 1762 Params: 1763 slice = ndslice. 1764 Returns: 1765 Multidimensional index such that element is minimal(maximal). 1766 Index elements equal to `size_t.max`, if slice is empty. 1767 +/ 1768 @optmath size_t[N] minIndex(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) 1769 { 1770 size_t[N] ret = size_t.max; 1771 if (!slice.anyEmpty) 1772 { 1773 ret = slice.shape; 1774 auto scopeSlice = lightScope(slice); 1775 auto iterator = scopeSlice._iterator; 1776 minPosImpl!(pred, LightScopeOf!Iterator, N, kind)(ret, iterator, scopeSlice); 1777 foreach (i; Iota!N) 1778 ret[i] = slice._lengths[i] - ret[i]; 1779 } 1780 return ret; 1781 } 1782 else 1783 alias minIndex = .minIndex!(naryFun!pred); 1784 } 1785 1786 /// ditto 1787 template maxIndex(alias pred = "a < b") 1788 { 1789 import mir.functional: naryFun, reverseArgs; 1790 alias maxIndex = minIndex!(reverseArgs!(naryFun!pred)); 1791 } 1792 1793 /// 1794 version(mir_test) 1795 unittest 1796 { 1797 import mir.ndslice.slice: sliced; 1798 auto s = [ 1799 2, 6, 4, -3, 1800 0, -4, -3, 3, 1801 -3, -2, 7, 8, 1802 ].sliced(3, 4); 1803 1804 auto index = s.minIndex; 1805 1806 assert(index == [1, 1]); 1807 assert(s[index] == -4); 1808 1809 index = s.maxIndex; 1810 1811 assert(index == [2, 3]); 1812 assert(s[index] == 8); 1813 } 1814 1815 /// 1816 version(mir_test) 1817 unittest 1818 { 1819 import mir.ndslice.slice: sliced; 1820 auto s = [ 1821 -8, 6, 4, -3, 1822 0, -4, -3, 3, 1823 -3, -2, 7, 8, 1824 ].sliced(3, 4); 1825 1826 auto index = s.minIndex; 1827 1828 assert(index == [0, 0]); 1829 assert(s[index] == -8); 1830 } 1831 1832 version(mir_test) 1833 unittest 1834 { 1835 import mir.ndslice.slice: sliced; 1836 auto s = [ 1837 0, 1, 2, 3, 1838 4, 5, 6, 7, 1839 8, 9, 10, 11 1840 ].sliced(3, 4); 1841 1842 auto index = s.minIndex; 1843 assert(index == [0, 0]); 1844 assert(s[index] == 0); 1845 1846 index = s.maxIndex; 1847 assert(index == [2, 3]); 1848 assert(s[index] == 11); 1849 } 1850 1851 bool findImpl(alias fun, size_t N, Slices...)(scope ref size_t[N] backwardIndex, Slices slices) 1852 if (Slices.length) 1853 { 1854 static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(FieldIterator!(BitField!(Field, I))), Field, I)) 1855 { 1856 auto cnt = BitSliceAccelerator!(Field, I)(slices[0]).cttz; 1857 if (cnt = -1) 1858 return false; 1859 backwardIndex[0] = slices[0].length - cnt; 1860 } 1861 else 1862 static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(RetroIterator!(FieldIterator!(BitField!(Field, I)))), Field, I)) 1863 { 1864 import mir.ndslice.topology: retro; 1865 auto cnt = BitSliceAccelerator!(Field, I)(slices[0].retro).ctlz; 1866 if (cnt = -1) 1867 return false; 1868 backwardIndex[0] = slices[0].length - cnt; 1869 } 1870 else 1871 { 1872 do 1873 { 1874 static if (DimensionCount!(Slices[0]) == 1) 1875 { 1876 if (fun(frontOf!slices)) 1877 { 1878 backwardIndex[0] = slices[0].length; 1879 return true; 1880 } 1881 } 1882 else 1883 { 1884 if (findImpl!fun(backwardIndex[1 .. $], frontOf!slices)) 1885 { 1886 backwardIndex[0] = slices[0].length; 1887 return true; 1888 } 1889 } 1890 foreach_reverse(ref slice; slices) 1891 slice.popFront; 1892 } 1893 while(!slices[0].empty); 1894 return false; 1895 } 1896 } 1897 1898 /++ 1899 Finds an index such that 1900 `pred(slices[0][index], ..., slices[$-1][index])` is `true`. 1901 1902 Params: 1903 pred = A predicate. 1904 1905 See_also: 1906 $(LREF find), 1907 $(LREF any). 1908 Optimization: 1909 `findIndex!"a"` has accelerated specialization for slices created with $(REF bitwise, mir,ndslice,topology), $(REF bitSlice, mir,ndslice,allocation). 1910 +/ 1911 template findIndex(alias pred) 1912 { 1913 import mir.functional: naryFun; 1914 static if (__traits(isSame, naryFun!pred, pred)) 1915 /++ 1916 Params: 1917 slices = One or more slices. 1918 Returns: 1919 Multidimensional index such that the predicate is true. 1920 Index equals `size_t.max`, if the predicate evaluates `false` for all indices. 1921 Constraints: 1922 All slices must have the same shape. 1923 +/ 1924 @optmath Select!(DimensionCount!(Slices[0]) > 1, size_t[DimensionCount!(Slices[0])], size_t) findIndex(Slices...)(Slices slices) 1925 if (Slices.length) 1926 { 1927 static if (Slices.length > 1) 1928 slices.checkShapesMatch; 1929 size_t[DimensionCount!(Slices[0])] ret = -1; 1930 auto lengths = slices[0].shape; 1931 if (!slices[0].anyEmpty && findImpl!pred(ret, allLightScope!slices)) 1932 foreach (i; Iota!(DimensionCount!(Slices[0]))) 1933 ret[i] = lengths[i] - ret[i]; 1934 static if (DimensionCount!(Slices[0]) > 1) 1935 return ret; 1936 else 1937 return ret[0]; 1938 } 1939 else 1940 alias findIndex = .findIndex!(naryFun!pred); 1941 } 1942 1943 /// Ranges and arrays 1944 version(mir_test) 1945 unittest 1946 { 1947 import std.range : iota; 1948 // 0 1 2 3 4 5 1949 auto sl = iota(5); 1950 size_t index = sl.findIndex!"a == 3"; 1951 1952 assert(index == 3); 1953 assert(sl[index] == 3); 1954 1955 assert(sl.findIndex!(a => a == 8) == size_t.max); 1956 } 1957 1958 /// 1959 @safe pure nothrow @nogc 1960 version(mir_test) unittest 1961 { 1962 import mir.ndslice.topology : iota; 1963 // 0 1 2 1964 // 3 4 5 1965 auto sl = iota(2, 3); 1966 size_t[2] index = sl.findIndex!(a => a == 3); 1967 1968 assert(sl[index] == 3); 1969 1970 index = sl.findIndex!"a == 6"; 1971 assert(index[0] == size_t.max); 1972 assert(index[1] == size_t.max); 1973 } 1974 1975 /++ 1976 Finds a backward index such that 1977 `pred(slices[0].backward(index), ..., slices[$-1].backward(index))` is `true`. 1978 1979 Params: 1980 pred = A predicate. 1981 1982 Optimization: 1983 To check if any element was found 1984 use the last dimension (row index). 1985 This will slightly optimize the code. 1986 -------- 1987 if (backwardIndex) 1988 { 1989 auto elem1 = slice1.backward(backwardIndex); 1990 //... 1991 auto elemK = sliceK.backward(backwardIndex); 1992 } 1993 else 1994 { 1995 // not found 1996 } 1997 -------- 1998 1999 See_also: 2000 $(LREF findIndex), 2001 $(LREF any), 2002 $(REF Slice.backward, mir,ndslice,slice). 2003 2004 Optimization: 2005 `find!"a"` has accelerated specialization for slices created with $(REF bitwise, mir,ndslice,topology), $(REF bitSlice, mir,ndslice,allocation). 2006 +/ 2007 template find(alias pred) 2008 { 2009 import mir.functional: naryFun; 2010 static if (__traits(isSame, naryFun!pred, pred)) 2011 /++ 2012 Params: 2013 slices = One or more slices. 2014 Returns: 2015 Multidimensional backward index such that the predicate is true. 2016 Backward index equals zeros, if the predicate evaluates `false` for all indices. 2017 Constraints: 2018 All slices must have the same shape. 2019 +/ 2020 @optmath Select!(DimensionCount!(Slices[0]) > 1, size_t[DimensionCount!(Slices[0])], size_t) find(Slices...)(auto ref Slices slices) 2021 if (Slices.length && allSatisfy!(hasShape, Slices)) 2022 { 2023 static if (Slices.length > 1) 2024 slices.checkShapesMatch; 2025 size_t[DimensionCount!(Slices[0])] ret; 2026 if (!slices[0].anyEmpty) 2027 findImpl!pred(ret, allLightScope!slices); 2028 static if (DimensionCount!(Slices[0]) > 1) 2029 return ret; 2030 else 2031 return ret[0]; 2032 } 2033 else 2034 alias find = .find!(naryFun!pred); 2035 } 2036 2037 /// Ranges and arrays 2038 version(mir_test) 2039 unittest 2040 { 2041 import std.range : iota; 2042 2043 auto sl = iota(10); 2044 size_t index = sl.find!"a == 3"; 2045 2046 assert(sl[$ - index] == 3); 2047 } 2048 2049 /// 2050 @safe pure nothrow @nogc 2051 version(mir_test) unittest 2052 { 2053 import mir.ndslice.topology : iota; 2054 // 0 1 2 2055 // 3 4 5 2056 auto sl = iota(2, 3); 2057 size_t[2] bi = sl.find!"a == 3"; 2058 assert(sl.backward(bi) == 3); 2059 assert(sl[$ - bi[0], $ - bi[1]] == 3); 2060 2061 bi = sl.find!"a == 6"; 2062 assert(bi[0] == 0); 2063 assert(bi[1] == 0); 2064 } 2065 2066 /// Multiple slices 2067 @safe pure nothrow @nogc 2068 version(mir_test) unittest 2069 { 2070 import mir.ndslice.topology : iota; 2071 2072 // 0 1 2 2073 // 3 4 5 2074 auto a = iota(2, 3); 2075 // 10 11 12 2076 // 13 14 15 2077 auto b = iota([2, 3], 10); 2078 2079 size_t[2] bi = find!((a, b) => a * b == 39)(a, b); 2080 assert(a.backward(bi) == 3); 2081 assert(b.backward(bi) == 13); 2082 } 2083 2084 /// Zipped slices 2085 @safe pure nothrow 2086 version(mir_test) unittest 2087 { 2088 import mir.ndslice.topology : iota, zip; 2089 2090 // 0 1 2 2091 // 3 4 5 2092 auto a = iota(2, 3); 2093 // 10 11 12 2094 // 13 14 15 2095 auto b = iota([2, 3], 10); 2096 2097 size_t[2] bi = zip!true(a, b).find!"a.a * a.b == 39"; 2098 2099 assert(a.backward(bi) == 3); 2100 assert(b.backward(bi) == 13); 2101 } 2102 2103 /// Mutation on-the-fly 2104 pure nothrow 2105 version(mir_test) unittest 2106 { 2107 import mir.ndslice.allocation : slice; 2108 import mir.ndslice.topology : as, iota; 2109 2110 // 0 1 2 2111 // 3 4 5 2112 auto sl = iota(2, 3).as!double.slice; 2113 2114 static bool pred(T)(ref T a) 2115 { 2116 if (a == 5) 2117 return true; 2118 a = 8; 2119 return false; 2120 } 2121 2122 size_t[2] bi = sl.find!pred; 2123 2124 assert(bi == [1, 1]); 2125 assert(sl.backward(bi) == 5); 2126 2127 // sl was changed 2128 assert(sl == [[8, 8, 8], 2129 [8, 8, 5]]); 2130 } 2131 2132 @safe pure nothrow 2133 version(mir_test) unittest 2134 { 2135 import mir.ndslice.topology : iota; 2136 size_t i; 2137 size_t[2] bi = iota(2, 0).find!((elem){i++; return true;}); 2138 assert(i == 0); 2139 assert(bi == [0, 0]); 2140 } 2141 2142 size_t anyImpl(alias fun, Slices...)(scope Slices slices) 2143 if (Slices.length) 2144 { 2145 static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(FieldIterator!(BitField!(Field, I))), Field, I)) 2146 { 2147 return BitSliceAccelerator!(Field, I)(slices[0]).any; 2148 } 2149 else 2150 static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(RetroIterator!(FieldIterator!(BitField!(Field, I)))), Field, I)) 2151 { 2152 // pragma(msg, S); 2153 import mir.ndslice.topology: retro; 2154 return .anyImpl!fun(lightScope(slices[0]).retro); 2155 } 2156 else 2157 { 2158 do 2159 { 2160 static if (DimensionCount!(Slices[0]) == 1) 2161 { 2162 if (fun(frontOf!slices)) 2163 return true; 2164 } 2165 else 2166 { 2167 if (anyImpl!fun(frontOf!slices)) 2168 return true; 2169 } 2170 foreach_reverse(ref slice; slices) 2171 slice.popFront; 2172 } 2173 while(!slices[0].empty); 2174 return false; 2175 } 2176 } 2177 2178 /++ 2179 Like $(LREF find), but only returns whether or not the search was successful. 2180 2181 Params: 2182 pred = The predicate. 2183 Optimization: 2184 `any!"a"` has accelerated specialization for slices created with $(REF bitwise, mir,ndslice,topology), $(REF bitSlice, mir,ndslice,allocation). 2185 +/ 2186 template any(alias pred = "a") 2187 { 2188 import mir.functional: naryFun; 2189 static if (__traits(isSame, naryFun!pred, pred)) 2190 /++ 2191 Params: 2192 slices = One or more slices, ranges, and arrays. 2193 Returns: 2194 `true` if the search was successful and `false` otherwise. 2195 Constraints: 2196 All slices must have the same shape. 2197 +/ 2198 @optmath bool any(Slices...)(scope Slices slices) 2199 if ((Slices.length == 1 || !__traits(isSame, pred, "a")) && Slices.length) 2200 { 2201 static if (Slices.length > 1) 2202 slices.checkShapesMatch; 2203 static if (areAllContiguousSlices!Slices) 2204 { 2205 import mir.ndslice.topology: flattened; 2206 return .any!pred(allFlattened!(allLightScope!slices)); 2207 } 2208 else 2209 { 2210 return !slices[0].anyEmpty && anyImpl!pred(allLightScope!slices); 2211 } 2212 } 2213 else 2214 alias any = .any!(naryFun!pred); 2215 } 2216 2217 /// Ranges and arrays 2218 @safe pure nothrow @nogc 2219 version(mir_test) unittest 2220 { 2221 import std.range : iota; 2222 // 0 1 2 3 4 5 2223 auto r = iota(6); 2224 2225 assert(r.any!"a == 3"); 2226 assert(!r.any!"a == 6"); 2227 } 2228 2229 /// 2230 @safe pure nothrow @nogc 2231 version(mir_test) unittest 2232 { 2233 import mir.ndslice.topology : iota; 2234 // 0 1 2 2235 // 3 4 5 2236 auto sl = iota(2, 3); 2237 2238 assert(sl.any!"a == 3"); 2239 assert(!sl.any!"a == 6"); 2240 } 2241 2242 /// Multiple slices 2243 @safe pure nothrow @nogc 2244 version(mir_test) unittest 2245 { 2246 import mir.ndslice.topology : iota; 2247 2248 // 0 1 2 2249 // 3 4 5 2250 auto a = iota(2, 3); 2251 // 10 11 12 2252 // 13 14 15 2253 auto b = iota([2, 3], 10); 2254 2255 assert(any!((a, b) => a * b == 39)(a, b)); 2256 } 2257 2258 /// Zipped slices 2259 @safe pure nothrow 2260 version(mir_test) unittest 2261 { 2262 import mir.ndslice.topology : iota, zip; 2263 2264 // 0 1 2 2265 // 3 4 5 2266 auto a = iota(2, 3); 2267 // 10 11 12 2268 // 13 14 15 2269 auto b = iota([2, 3], 10); 2270 2271 // slices must have the same strides 2272 2273 assert(zip!true(a, b).any!"a.a * a.b == 39"); 2274 } 2275 2276 /// Mutation on-the-fly 2277 pure nothrow 2278 version(mir_test) unittest 2279 { 2280 import mir.ndslice.allocation : slice; 2281 import mir.ndslice.topology : as, iota; 2282 2283 // 0 1 2 2284 // 3 4 5 2285 auto sl = iota(2, 3).as!double.slice; 2286 2287 static bool pred(T)(ref T a) 2288 { 2289 if (a == 5) 2290 return true; 2291 a = 8; 2292 return false; 2293 } 2294 2295 assert(sl.any!pred); 2296 2297 // sl was changed 2298 assert(sl == [[8, 8, 8], 2299 [8, 8, 5]]); 2300 } 2301 2302 size_t allImpl(alias fun, Slices...)(scope Slices slices) 2303 if (Slices.length) 2304 { 2305 static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(FieldIterator!(BitField!(Field, I))), Field, I)) 2306 { 2307 return BitSliceAccelerator!(LightScopeOf!Field, I)(lightScope(slices[0])).all; 2308 } 2309 else 2310 static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(RetroIterator!(FieldIterator!(BitField!(Field, I)))), Field, I)) 2311 { 2312 // pragma(msg, S); 2313 import mir.ndslice.topology: retro; 2314 return .allImpl!fun(lightScope(slices[0]).retro); 2315 } 2316 else 2317 { 2318 do 2319 { 2320 static if (DimensionCount!(Slices[0]) == 1) 2321 { 2322 if (!fun(frontOf!slices)) 2323 return false; 2324 } 2325 else 2326 { 2327 if (!allImpl!fun(frontOf!slices)) 2328 return false; 2329 } 2330 foreach_reverse(ref slice; slices) 2331 slice.popFront; 2332 } 2333 while(!slices[0].empty); 2334 return true; 2335 } 2336 } 2337 2338 /++ 2339 Checks if all of the elements verify `pred`. 2340 2341 Params: 2342 pred = The predicate. 2343 Optimization: 2344 `all!"a"` has accelerated specialization for slices created with $(REF bitwise, mir,ndslice,topology), $(REF bitSlice, mir,ndslice,allocation). 2345 +/ 2346 template all(alias pred = "a") 2347 { 2348 import mir.functional: naryFun; 2349 static if (__traits(isSame, naryFun!pred, pred)) 2350 /++ 2351 Params: 2352 slices = One or more slices. 2353 Returns: 2354 `true` all of the elements verify `pred` and `false` otherwise. 2355 Constraints: 2356 All slices must have the same shape. 2357 +/ 2358 @optmath bool all(Slices...)(scope Slices slices) 2359 if ((Slices.length == 1 || !__traits(isSame, pred, "a")) && Slices.length) 2360 { 2361 static if (Slices.length > 1) 2362 slices.checkShapesMatch; 2363 static if (areAllContiguousSlices!Slices) 2364 { 2365 import mir.ndslice.topology: flattened; 2366 return .all!pred(allFlattened!(allLightScope!slices)); 2367 } 2368 else 2369 { 2370 return slices[0].anyEmpty || allImpl!pred(allLightScope!slices); 2371 } 2372 } 2373 else 2374 alias all = .all!(naryFun!pred); 2375 } 2376 2377 /// Ranges and arrays 2378 @safe pure nothrow @nogc 2379 version(mir_test) unittest 2380 { 2381 import std.range : iota; 2382 // 0 1 2 3 4 5 2383 auto r = iota(6); 2384 2385 assert(r.all!"a < 6"); 2386 assert(!r.all!"a < 5"); 2387 } 2388 2389 /// 2390 @safe pure nothrow 2391 version(mir_test) unittest 2392 { 2393 import mir.ndslice.topology : iota; 2394 2395 // 0 1 2 2396 // 3 4 5 2397 auto sl = iota(2, 3); 2398 2399 assert(sl.all!"a < 6"); 2400 assert(!sl.all!"a < 5"); 2401 } 2402 2403 /// Multiple slices 2404 @safe pure nothrow 2405 version(mir_test) unittest 2406 { 2407 import mir.ndslice.topology : iota; 2408 2409 // 0 1 2 2410 // 3 4 5 2411 auto sl = iota(2, 3); 2412 2413 assert(all!"a - b == 0"(sl, sl)); 2414 } 2415 2416 /// Zipped slices 2417 @safe pure nothrow 2418 version(mir_test) unittest 2419 { 2420 import mir.ndslice.topology : iota, zip; 2421 2422 // 0 1 2 2423 // 3 4 5 2424 auto sl = iota(2, 3); 2425 2426 2427 assert(zip!true(sl, sl).all!"a.a - a.b == 0"); 2428 } 2429 2430 /// Mutation on-the-fly 2431 pure nothrow 2432 version(mir_test) unittest 2433 { 2434 import mir.ndslice.allocation : slice; 2435 import mir.ndslice.topology : as, iota; 2436 2437 // 0 1 2 2438 // 3 4 5 2439 auto sl = iota(2, 3).as!double.slice; 2440 2441 static bool pred(T)(ref T a) 2442 { 2443 if (a < 4) 2444 { 2445 a = 8; 2446 return true; 2447 } 2448 return false; 2449 } 2450 2451 assert(!sl.all!pred); 2452 2453 // sl was changed 2454 assert(sl == [[8, 8, 8], 2455 [8, 4, 5]]); 2456 } 2457 2458 @safe pure nothrow 2459 version(mir_test) unittest 2460 { 2461 import mir.ndslice.topology : iota; 2462 size_t i; 2463 assert(iota(2, 0).all!((elem){i++; return true;})); 2464 assert(i == 0); 2465 } 2466 2467 /++ 2468 Counts elements in slices according to the `fun`. 2469 Params: 2470 fun = A predicate. 2471 2472 Optimization: 2473 `count!"a"` has accelerated specialization for slices created with $(REF bitwise, mir,ndslice,topology), $(REF bitSlice, mir,ndslice,allocation). 2474 +/ 2475 template count(alias fun) 2476 { 2477 import mir.functional: naryFun; 2478 static if (__traits(isSame, naryFun!fun, fun)) 2479 /++ 2480 Params: 2481 slices = One or more slices, ranges, and arrays. 2482 2483 Returns: The number elements according to the `fun`. 2484 2485 Constraints: 2486 All slices must have the same shape. 2487 +/ 2488 @optmath size_t count(Slices...)(scope Slices slices) 2489 if (Slices.length) 2490 { 2491 static if (Slices.length > 1) 2492 slices.checkShapesMatch; 2493 static if (__traits(isSame, fun, naryFun!"true")) 2494 { 2495 return slices[0].elementCount; 2496 } 2497 else 2498 static if (areAllContiguousSlices!Slices) 2499 { 2500 import mir.ndslice.topology: flattened; 2501 return .count!fun(allFlattened!(allLightScope!slices)); 2502 } 2503 else 2504 { 2505 if (slices[0].anyEmpty) 2506 return 0; 2507 return countImpl!(fun)(allLightScope!slices); 2508 } 2509 } 2510 else 2511 alias count = .count!(naryFun!fun); 2512 2513 } 2514 2515 /// Ranges and arrays 2516 @safe pure nothrow @nogc 2517 version(mir_test) unittest 2518 { 2519 import std.range : iota; 2520 // 0 1 2 3 4 5 2521 auto r = iota(6); 2522 2523 assert(r.count!"true" == 6); 2524 assert(r.count!"a" == 5); 2525 assert(r.count!"a % 2" == 3); 2526 } 2527 2528 /// Single slice 2529 version(mir_test) 2530 unittest 2531 { 2532 import mir.ndslice.topology : iota; 2533 2534 //| 0 1 2 | 2535 //| 3 4 5 | 2536 auto sl = iota(2, 3); 2537 2538 assert(sl.count!"true" == 6); 2539 assert(sl.count!"a" == 5); 2540 assert(sl.count!"a % 2" == 3); 2541 } 2542 2543 /// Accelerated set bit count 2544 version(mir_test) 2545 unittest 2546 { 2547 import mir.ndslice.topology: retro, iota, bitwise; 2548 import mir.ndslice.allocation: slice; 2549 2550 //| 0 1 2 | 2551 //| 3 4 5 | 2552 auto sl = iota!size_t(2, 3).bitwise; 2553 2554 assert(sl.count!"true" == 6 * size_t.sizeof * 8); 2555 2556 assert(sl.slice.count!"a" == 7); 2557 2558 // accelerated 2559 assert(sl.count!"a" == 7); 2560 assert(sl.retro.count!"a" == 7); 2561 2562 auto sl2 = iota!ubyte([6], 128).bitwise; 2563 // accelerated 2564 assert(sl2.count!"a" == 13); 2565 assert(sl2[4 .. $].count!"a" == 13); 2566 assert(sl2[4 .. $ - 1].count!"a" == 12); 2567 assert(sl2[4 .. $ - 1].count!"a" == 12); 2568 assert(sl2[41 .. $ - 1].count!"a" == 1); 2569 } 2570 2571 unittest 2572 { 2573 import mir.ndslice.allocation: slice; 2574 import mir.ndslice.topology: bitwise, assumeFieldsHaveZeroShift; 2575 auto sl = slice!uint([6]).bitwise; 2576 auto slb = slice!ubyte([6]).bitwise; 2577 slb[4] = true; 2578 auto d = slb[4]; 2579 auto c = assumeFieldsHaveZeroShift(slb & ~slb); 2580 // pragma(msg, typeof(c)); 2581 assert(!sl.any); 2582 assert((~sl).all); 2583 // pragma(msg, typeof(~slb)); 2584 // pragma(msg, typeof(~slb)); 2585 // assert(sl.findIndex); 2586 } 2587 2588 /++ 2589 Compares two or more slices for equality, as defined by predicate `pred`. 2590 2591 See_also: $(NDSLICEREF slice, Slice.opEquals) 2592 2593 Params: 2594 pred = The predicate. 2595 +/ 2596 template equal(alias pred = "a == b") 2597 { 2598 import mir.functional: naryFun; 2599 static if (__traits(isSame, naryFun!pred, pred)) 2600 { 2601 /++ 2602 Params: 2603 slices = Two or more ndslices, ranges, and arrays. 2604 2605 Returns: 2606 `true` any of the elements verify `pred` and `false` otherwise. 2607 +/ 2608 bool equal(Slices...)(scope Slices slices) 2609 if (Slices.length >= 2) 2610 { 2611 import mir.internal.utility; 2612 static if (allSatisfy!(hasShape, Slices)) 2613 { 2614 auto shape0 = slices[0].shape; 2615 enum N = DimensionCount!(Slices[0]); 2616 foreach (ref slice; slices[1 .. $]) 2617 { 2618 if (slice.shape != shape0) 2619 goto False; 2620 } 2621 return all!pred(allLightScope!slices); 2622 } 2623 else 2624 { 2625 for(;;) 2626 { 2627 auto empty = slices[0].empty; 2628 foreach (ref slice; slices[1 .. $]) 2629 { 2630 if (slice.empty != empty) 2631 goto False; 2632 } 2633 if (empty) 2634 return true; 2635 if (!pred(frontOf!slices)) 2636 goto False; 2637 foreach (ref slice; slices) 2638 slice.popFront; 2639 } 2640 } 2641 False: return false; 2642 } 2643 } 2644 else 2645 alias equal = .equal!(naryFun!pred); 2646 } 2647 2648 /// Ranges and arrays 2649 @safe pure nothrow 2650 version(mir_test) unittest 2651 { 2652 import std.range : iota; 2653 auto r = iota(6); 2654 assert(r.equal([0, 1, 2, 3, 4, 5])); 2655 } 2656 2657 /// 2658 @safe pure nothrow @nogc 2659 version(mir_test) unittest 2660 { 2661 import mir.ndslice.allocation : slice; 2662 import mir.ndslice.topology : iota; 2663 2664 // 0 1 2 2665 // 3 4 5 2666 auto sl1 = iota(2, 3); 2667 // 1 2 3 2668 // 4 5 6 2669 auto sl2 = iota([2, 3], 1); 2670 2671 assert(equal(sl1, sl1)); 2672 assert(sl1 == sl1); //can also use opEquals for two Slices 2673 assert(equal!"2 * a == b + c"(sl1, sl1, sl1)); 2674 2675 assert(equal!"a < b"(sl1, sl2)); 2676 2677 assert(!equal(sl1[0 .. $ - 1], sl1)); 2678 assert(!equal(sl1[0 .. $, 0 .. $ - 1], sl1)); 2679 } 2680 2681 @safe pure nothrow @nogc 2682 version(mir_test) unittest 2683 { 2684 import mir.math.common: approxEqual; 2685 import mir.ndslice.allocation: rcslice; 2686 import mir.ndslice.topology: as, iota; 2687 2688 auto x = 5.iota.as!double.rcslice; 2689 auto y = x.rcslice; 2690 2691 assert(equal(x, y)); 2692 assert(equal!approxEqual(x, y)); 2693 } 2694 2695 ptrdiff_t cmpImpl(alias pred, A, B) 2696 (scope A sl1, scope B sl2) 2697 if (DimensionCount!A == DimensionCount!B) 2698 { 2699 for (;;) 2700 { 2701 static if (DimensionCount!A == 1) 2702 { 2703 import mir.functional : naryFun; 2704 if (naryFun!pred(sl1.front, sl2.front)) 2705 return -1; 2706 if (naryFun!pred(sl2.front, sl1.front)) 2707 return 1; 2708 } 2709 else 2710 { 2711 if (auto res = .cmpImpl!pred(sl1.front, sl2.front)) 2712 return res; 2713 } 2714 sl1.popFront; 2715 if (sl1.empty) 2716 return -cast(ptrdiff_t)(sl2.length > 1); 2717 sl2.popFront; 2718 if (sl2.empty) 2719 return 1; 2720 } 2721 } 2722 2723 /++ 2724 Performs three-way recursive lexicographical comparison on two slices according to predicate `pred`. 2725 Iterating `sl1` and `sl2` in lockstep, `cmp` compares each `N-1` dimensional element `e1` of `sl1` 2726 with the corresponding element `e2` in `sl2` recursively. 2727 If one of the slices has been finished,`cmp` returns a negative value if `sl1` has fewer elements than `sl2`, 2728 a positive value if `sl1` has more elements than `sl2`, 2729 and `0` if the ranges have the same number of elements. 2730 2731 Params: 2732 pred = The predicate. 2733 +/ 2734 template cmp(alias pred = "a < b") 2735 { 2736 import mir.functional: naryFun; 2737 static if (__traits(isSame, naryFun!pred, pred)) 2738 /++ 2739 Params: 2740 sl1 = First slice, range, or array. 2741 sl2 = Second slice, range, or array. 2742 2743 Returns: 2744 `0` if both ranges compare equal. 2745 Negative value if the first differing element of `sl1` is less than the corresponding 2746 element of `sl2` according to `pred`. 2747 Positive value if the first differing element of `sl2` is less than the corresponding 2748 element of `sl1` according to `pred`. 2749 +/ 2750 auto cmp(A, B) 2751 (scope A sl1, scope B sl2) 2752 if (DimensionCount!A == DimensionCount!B) 2753 { 2754 auto b = sl2.anyEmpty; 2755 if (sl1.anyEmpty) 2756 { 2757 if (!b) 2758 return -1; 2759 auto sh1 = sl1.shape; 2760 auto sh2 = sl2.shape; 2761 foreach (i; Iota!(DimensionCount!A)) 2762 if (sh1[i] != sh2[i]) 2763 return sh1[i] > sh2[i] ? 1 : -1; 2764 return 0; 2765 } 2766 if (b) 2767 return 1; 2768 return cmpImpl!pred(lightScope(sl1), lightScope(sl2)); 2769 } 2770 else 2771 alias cmp = .cmp!(naryFun!pred); 2772 } 2773 2774 /// Ranges and arrays 2775 @safe pure nothrow 2776 version(mir_test) unittest 2777 { 2778 import std.range : iota; 2779 2780 // 0 1 2 3 4 5 2781 auto r1 = iota(0, 6); 2782 // 1 2 3 4 5 6 2783 auto r2 = iota(1, 7); 2784 2785 assert(cmp(r1, r1) == 0); 2786 assert(cmp(r1, r2) < 0); 2787 assert(cmp!"a >= b"(r1, r2) > 0); 2788 } 2789 2790 /// 2791 @safe pure nothrow @nogc 2792 version(mir_test) unittest 2793 { 2794 import mir.ndslice.topology : iota; 2795 2796 // 0 1 2 2797 // 3 4 5 2798 auto sl1 = iota(2, 3); 2799 // 1 2 3 2800 // 4 5 6 2801 auto sl2 = iota([2, 3], 1); 2802 2803 assert(cmp(sl1, sl1) == 0); 2804 assert(cmp(sl1, sl2) < 0); 2805 assert(cmp!"a >= b"(sl1, sl2) > 0); 2806 } 2807 2808 @safe pure nothrow @nogc 2809 version(mir_test) unittest 2810 { 2811 import mir.ndslice.topology : iota; 2812 2813 auto sl1 = iota(2, 3); 2814 auto sl2 = iota([2, 3], 1); 2815 2816 assert(cmp(sl1[0 .. $ - 1], sl1) < 0); 2817 assert(cmp(sl1, sl1[0 .. $, 0 .. $ - 1]) > 0); 2818 2819 assert(cmp(sl1[0 .. $ - 2], sl1) < 0); 2820 assert(cmp(sl1, sl1[0 .. $, 0 .. $ - 3]) > 0); 2821 assert(cmp(sl1[0 .. $, 0 .. $ - 3], sl1[0 .. $, 0 .. $ - 3]) == 0); 2822 assert(cmp(sl1[0 .. $, 0 .. $ - 3], sl1[0 .. $ - 1, 0 .. $ - 3]) > 0); 2823 assert(cmp(sl1[0 .. $ - 1, 0 .. $ - 3], sl1[0 .. $, 0 .. $ - 3]) < 0); 2824 } 2825 2826 size_t countImpl(alias fun, Slices...)(scope Slices slices) 2827 { 2828 size_t ret; 2829 alias S = Slices[0]; 2830 import mir.functional: naryFun; 2831 import mir.ndslice.iterator: FieldIterator, RetroIterator; 2832 import mir.ndslice.field: BitField; 2833 static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(FieldIterator!(BitField!(Field, I))), Field, I)) 2834 { 2835 ret = BitSliceAccelerator!(Field, I)(slices[0]).ctpop; 2836 } 2837 else 2838 static if (__traits(isSame, fun, naryFun!"a") && is(S : Slice!(RetroIterator!(FieldIterator!(BitField!(Field, I)))), Field, I)) 2839 { 2840 // pragma(msg, S); 2841 import mir.ndslice.topology: retro; 2842 ret = .countImpl!fun(lightScope(slices[0]).retro); 2843 } 2844 else 2845 do 2846 { 2847 static if (DimensionCount!(Slices[0]) == 1) 2848 { 2849 if(fun(frontOf!slices)) 2850 ret++; 2851 } 2852 else 2853 ret += .countImpl!fun(frontOf!slices); 2854 foreach_reverse(ref slice; slices) 2855 slice.popFront; 2856 } 2857 while(!slices[0].empty); 2858 return ret; 2859 } 2860 2861 /++ 2862 Returns: max length across all dimensions. 2863 +/ 2864 size_t maxLength(S)(auto ref scope S s) 2865 if (hasShape!S) 2866 { 2867 auto shape = s.shape; 2868 size_t length = 0; 2869 foreach(i; Iota!(shape.length)) 2870 if (shape[i] > length) 2871 length = shape[i]; 2872 return length; 2873 } 2874 2875 /++ 2876 The call `eachLower!(fun)(slice1, ..., sliceN)` evaluates `fun` on the lower 2877 triangle in `slice1, ..., sliceN` respectively. 2878 2879 `eachLower` allows iterating multiple slices in the lockstep. 2880 2881 Params: 2882 fun = A function 2883 See_Also: 2884 This is functionally similar to $(LREF each). 2885 +/ 2886 template eachLower(alias fun) 2887 { 2888 import mir.functional : naryFun; 2889 2890 static if (__traits(isSame, naryFun!fun, fun)) 2891 { 2892 /++ 2893 Params: 2894 inputs = One or more two-dimensional slices and an optional 2895 integer, `k`. 2896 2897 The value `k` determines which diagonals will have the function 2898 applied: 2899 For k = 0, the function is also applied to the main diagonal 2900 For k = 1 (default), only the non-main diagonals below the main 2901 diagonal will have the function applied. 2902 For k > 1, fewer diagonals below the main diagonal will have the 2903 function applied. 2904 For k < 0, more diagonals above the main diagonal will have the 2905 function applied. 2906 +/ 2907 void eachLower(Inputs...)(scope Inputs inputs) 2908 if (((Inputs.length > 1) && 2909 (isIntegral!(Inputs[$ - 1]))) || 2910 (Inputs.length)) 2911 { 2912 import mir.ndslice.traits : isMatrix; 2913 2914 size_t val; 2915 2916 static if ((Inputs.length > 1) && (isIntegral!(Inputs[$ - 1]))) 2917 { 2918 immutable(sizediff_t) k = inputs[$ - 1]; 2919 alias Slices = Inputs[0..($ - 1)]; 2920 alias slices = inputs[0..($ - 1)]; 2921 } 2922 else 2923 { 2924 enum sizediff_t k = 1; 2925 alias Slices = Inputs; 2926 alias slices = inputs; 2927 } 2928 2929 static assert (allSatisfy!(isMatrix, Slices), 2930 "eachLower: Every slice input must be a two-dimensional slice"); 2931 static if (Slices.length > 1) 2932 slices.checkShapesMatch; 2933 if (slices[0].anyEmpty) 2934 return; 2935 2936 foreach(ref slice; slices) 2937 assert(!slice.empty); 2938 2939 immutable(size_t) m = slices[0].length!0; 2940 immutable(size_t) n = slices[0].length!1; 2941 2942 if ((n + k) < m) 2943 { 2944 val = m - (n + k); 2945 .eachImpl!fun(selectBackOf!(val, slices)); 2946 } 2947 2948 size_t i; 2949 2950 if (k > 0) 2951 { 2952 foreach(ref slice; slices) 2953 slice.popFrontExactly!0(k); 2954 i = k; 2955 } 2956 2957 do 2958 { 2959 val = i - k + 1; 2960 .eachImpl!fun(frontSelectFrontOf!(val, slices)); 2961 2962 foreach(ref slice; slices) 2963 slice.popFront!0; 2964 i++; 2965 } while ((i < (n + k)) && (i < m)); 2966 } 2967 } 2968 else 2969 { 2970 alias eachLower = .eachLower!(naryFun!fun); 2971 } 2972 } 2973 2974 pure nothrow 2975 version(mir_test) unittest 2976 { 2977 import mir.ndslice.allocation: slice; 2978 import mir.ndslice.topology: iota, canonical, universal; 2979 alias AliasSeq(T...) = T; 2980 2981 pure nothrow 2982 void test(alias func)() 2983 { 2984 //| 1 2 3 | 2985 //| 4 5 6 | 2986 //| 7 8 9 | 2987 auto m = func(iota([3, 3], 1).slice); 2988 m.eachLower!"a = 0"(0); 2989 assert(m == [ 2990 [0, 2, 3], 2991 [0, 0, 6], 2992 [0, 0, 0]]); 2993 } 2994 2995 @safe pure nothrow @nogc 2996 T identity(T)(T x) 2997 { 2998 return x; 2999 } 3000 3001 alias kinds = AliasSeq!(identity, canonical, universal); 3002 test!(kinds[0]); 3003 test!(kinds[1]); 3004 test!(kinds[2]); 3005 } 3006 3007 /// 3008 pure nothrow 3009 version(mir_test) unittest 3010 { 3011 import mir.ndslice.allocation: slice; 3012 import mir.ndslice.topology: iota; 3013 3014 //| 1 2 3 | 3015 //| 4 5 6 | 3016 //| 7 8 9 | 3017 auto m = iota([3, 3], 1).slice; 3018 m.eachLower!"a = 0"; 3019 assert(m == [ 3020 [1, 2, 3], 3021 [0, 5, 6], 3022 [0, 0, 9]]); 3023 } 3024 3025 pure nothrow 3026 version(mir_test) unittest 3027 { 3028 import mir.ndslice.allocation: slice; 3029 import mir.ndslice.topology: iota; 3030 3031 //| 1 2 3 | 3032 //| 4 5 6 | 3033 //| 7 8 9 | 3034 auto m = iota([3, 3], 1).slice; 3035 m.eachLower!"a = 0"(-1); 3036 assert(m == [ 3037 [0, 0, 3], 3038 [0, 0, 0], 3039 [0, 0, 0]]); 3040 } 3041 3042 pure nothrow 3043 version(mir_test) unittest 3044 { 3045 import mir.ndslice.allocation: slice; 3046 import mir.ndslice.topology: iota; 3047 3048 //| 1 2 3 | 3049 //| 4 5 6 | 3050 //| 7 8 9 | 3051 auto m = iota([3, 3], 1).slice; 3052 m.eachLower!"a = 0"(2); 3053 assert(m == [ 3054 [1, 2, 3], 3055 [4, 5, 6], 3056 [0, 8, 9]]); 3057 } 3058 3059 pure nothrow 3060 version(mir_test) unittest 3061 { 3062 import mir.ndslice.allocation: slice; 3063 import mir.ndslice.topology: iota; 3064 3065 //| 1 2 3 | 3066 //| 4 5 6 | 3067 //| 7 8 9 | 3068 auto m = iota([3, 3], 1).slice; 3069 m.eachLower!"a = 0"(-2); 3070 assert(m == [ 3071 [0, 0, 0], 3072 [0, 0, 0], 3073 [0, 0, 0]]); 3074 } 3075 3076 pure nothrow 3077 version(mir_test) unittest 3078 { 3079 import mir.ndslice.allocation: slice; 3080 import mir.ndslice.topology: iota; 3081 3082 //| 1 2 3 4 | 3083 //| 5 6 7 8 | 3084 //| 9 10 11 12 | 3085 auto m = iota([3, 4], 1).slice; 3086 m.eachLower!"a = 0"(0); 3087 assert(m == [ 3088 [0, 2, 3, 4], 3089 [0, 0, 7, 8], 3090 [0, 0, 0, 12]]); 3091 } 3092 3093 pure nothrow 3094 version(mir_test) unittest 3095 { 3096 import mir.ndslice.allocation: slice; 3097 import mir.ndslice.topology: iota; 3098 3099 //| 1 2 3 4 | 3100 //| 5 6 7 8 | 3101 //| 9 10 11 12 | 3102 auto m = iota([3, 4], 1).slice; 3103 m.eachLower!"a = 0"; 3104 assert(m == [ 3105 [1, 2, 3, 4], 3106 [0, 6, 7, 8], 3107 [0, 0, 11, 12]]); 3108 } 3109 3110 pure nothrow 3111 version(mir_test) unittest 3112 { 3113 import mir.ndslice.allocation: slice; 3114 import mir.ndslice.topology: iota; 3115 3116 //| 1 2 3 4 | 3117 //| 5 6 7 8 | 3118 //| 9 10 11 12 | 3119 auto m = iota([3, 4], 1).slice; 3120 m.eachLower!"a = 0"(-1); 3121 assert(m == [ 3122 [0, 0, 3, 4], 3123 [0, 0, 0, 8], 3124 [0, 0, 0, 0]]); 3125 } 3126 3127 pure nothrow 3128 version(mir_test) unittest 3129 { 3130 import mir.ndslice.allocation: slice; 3131 import mir.ndslice.topology: iota; 3132 3133 //| 1 2 3 4 | 3134 //| 5 6 7 8 | 3135 //| 9 10 11 12 | 3136 auto m = iota([3, 4], 1).slice; 3137 m.eachLower!"a = 0"(2); 3138 assert(m == [ 3139 [1, 2, 3, 4], 3140 [5, 6, 7, 8], 3141 [0, 10, 11, 12]]); 3142 } 3143 3144 pure nothrow 3145 version(mir_test) unittest 3146 { 3147 import mir.ndslice.allocation: slice; 3148 import mir.ndslice.topology: iota; 3149 3150 //| 1 2 3 4 | 3151 //| 5 6 7 8 | 3152 //| 9 10 11 12 | 3153 auto m = iota([3, 4], 1).slice; 3154 m.eachLower!"a = 0"(-2); 3155 assert(m == [ 3156 [0, 0, 0, 4], 3157 [0, 0, 0, 0], 3158 [0, 0, 0, 0]]); 3159 } 3160 3161 pure nothrow 3162 version(mir_test) unittest 3163 { 3164 import mir.ndslice.allocation: slice; 3165 import mir.ndslice.topology: iota; 3166 3167 //| 1 2 3 | 3168 //| 4 5 6 | 3169 //| 7 8 9 | 3170 //| 10 11 12 | 3171 auto m = iota([4, 3], 1).slice; 3172 m.eachLower!"a = 0"(0); 3173 assert(m == [ 3174 [0, 2, 3], 3175 [0, 0, 6], 3176 [0, 0, 0], 3177 [0, 0, 0]]); 3178 } 3179 3180 pure nothrow 3181 version(mir_test) unittest 3182 { 3183 import mir.ndslice.allocation: slice; 3184 import mir.ndslice.topology: iota; 3185 3186 //| 1 2 3 | 3187 //| 4 5 6 | 3188 //| 7 8 9 | 3189 //| 10 11 12 | 3190 auto m = iota([4, 3], 1).slice; 3191 m.eachLower!"a = 0"; 3192 assert(m == [ 3193 [1, 2, 3], 3194 [0, 5, 6], 3195 [0, 0, 9], 3196 [0, 0, 0]]); 3197 } 3198 3199 pure nothrow 3200 version(mir_test) unittest 3201 { 3202 import mir.ndslice.allocation: slice; 3203 import mir.ndslice.topology: iota; 3204 3205 //| 1 2 3 | 3206 //| 4 5 6 | 3207 //| 7 8 9 | 3208 //| 10 11 12 | 3209 auto m = iota([4, 3], 1).slice; 3210 m.eachLower!"a = 0"(-1); 3211 assert(m == [ 3212 [0, 0, 3], 3213 [0, 0, 0], 3214 [0, 0, 0], 3215 [0, 0, 0]]); 3216 } 3217 3218 pure nothrow 3219 version(mir_test) unittest 3220 { 3221 import mir.ndslice.allocation: slice; 3222 import mir.ndslice.topology: iota; 3223 3224 //| 1 2 3 | 3225 //| 4 5 6 | 3226 //| 7 8 9 | 3227 //| 10 11 12 | 3228 auto m = iota([4, 3], 1).slice; 3229 m.eachLower!"a = 0"(2); 3230 assert(m == [ 3231 [1, 2, 3], 3232 [4, 5, 6], 3233 [0, 8, 9], 3234 [0, 0, 12]]); 3235 } 3236 3237 pure nothrow 3238 version(mir_test) unittest 3239 { 3240 import mir.ndslice.allocation: slice; 3241 import mir.ndslice.topology: iota; 3242 3243 //| 1 2 3 | 3244 //| 4 5 6 | 3245 //| 7 8 9 | 3246 //| 10 11 12 | 3247 auto m = iota([4, 3], 1).slice; 3248 m.eachLower!"a = 0"(-2); 3249 assert(m == [ 3250 [0, 0, 0], 3251 [0, 0, 0], 3252 [0, 0, 0], 3253 [0, 0, 0]]); 3254 } 3255 3256 /// Swap two slices 3257 pure nothrow 3258 version(mir_test) unittest 3259 { 3260 import mir.utility : swap; 3261 import mir.ndslice.allocation : slice; 3262 import mir.ndslice.topology : as, iota; 3263 3264 //| 0 1 2 | 3265 //| 3 4 5 | 3266 //| 6 7 8 | 3267 auto a = iota([3, 3]).as!double.slice; 3268 //| 10 11 12 | 3269 //| 13 14 15 | 3270 //| 16 17 18 | 3271 auto b = iota([3, 3], 10).as!double.slice; 3272 3273 eachLower!swap(a, b); 3274 3275 assert(a == [ 3276 [ 0, 1, 2], 3277 [13, 4, 5], 3278 [16, 17, 8]]); 3279 assert(b == [ 3280 [10, 11, 12], 3281 [ 3, 14, 15], 3282 [ 6, 7, 18]]); 3283 } 3284 3285 /// Swap two zipped slices 3286 pure nothrow 3287 version(mir_test) unittest 3288 { 3289 import mir.utility : swap; 3290 import mir.ndslice.allocation : slice; 3291 import mir.ndslice.topology : as, zip, iota; 3292 3293 //| 0 1 2 | 3294 //| 3 4 5 | 3295 //| 6 7 8 | 3296 auto a = iota([3, 3]).as!double.slice; 3297 //| 10 11 12 | 3298 //| 13 14 15 | 3299 //| 16 17 18 | 3300 auto b = iota([3, 3], 10).as!double.slice; 3301 3302 auto z = zip(a, b); 3303 3304 z.eachLower!(z => swap(z.a, z.b)); 3305 3306 assert(a == [ 3307 [ 0, 1, 2], 3308 [13, 4, 5], 3309 [16, 17, 8]]); 3310 assert(b == [ 3311 [10, 11, 12], 3312 [ 3, 14, 15], 3313 [ 6, 7, 18]]); 3314 } 3315 3316 /++ 3317 The call `eachUpper!(fun)(slice1, ..., sliceN)` evaluates `fun` on the upper 3318 triangle in `slice1, ..., sliceN`, respectively. 3319 3320 `eachUpper` allows iterating multiple slices in the lockstep. 3321 3322 Params: 3323 fun = A function 3324 See_Also: 3325 This is functionally similar to $(LREF each). 3326 +/ 3327 template eachUpper(alias fun) 3328 { 3329 import mir.functional: naryFun; 3330 3331 static if (__traits(isSame, naryFun!fun, fun)) 3332 { 3333 /++ 3334 Params: 3335 inputs = One or more two-dimensional slices and an optional 3336 integer, `k`. 3337 3338 The value `k` determines which diagonals will have the function 3339 applied: 3340 For k = 0, the function is also applied to the main diagonal 3341 For k = 1 (default), only the non-main diagonals above the main 3342 diagonal will have the function applied. 3343 For k > 1, fewer diagonals below the main diagonal will have the 3344 function applied. 3345 For k < 0, more diagonals above the main diagonal will have the 3346 function applied. 3347 +/ 3348 void eachUpper(Inputs...)(scope Inputs inputs) 3349 if (((Inputs.length > 1) && 3350 (isIntegral!(Inputs[$ - 1]))) || 3351 (Inputs.length)) 3352 { 3353 import mir.ndslice.traits : isMatrix; 3354 3355 size_t val; 3356 3357 static if ((Inputs.length > 1) && (isIntegral!(Inputs[$ - 1]))) 3358 { 3359 immutable(sizediff_t) k = inputs[$ - 1]; 3360 alias Slices = Inputs[0..($ - 1)]; 3361 alias slices = inputs[0..($ - 1)]; 3362 } 3363 else 3364 { 3365 enum sizediff_t k = 1; 3366 alias Slices = Inputs; 3367 alias slices = inputs; 3368 } 3369 3370 static assert (allSatisfy!(isMatrix, Slices), 3371 "eachUpper: Every slice input must be a two-dimensional slice"); 3372 static if (Slices.length > 1) 3373 slices.checkShapesMatch; 3374 if (slices[0].anyEmpty) 3375 return; 3376 3377 foreach(ref slice; slices) 3378 assert(!slice.empty); 3379 3380 immutable(size_t) m = slices[0].length!0; 3381 immutable(size_t) n = slices[0].length!1; 3382 3383 size_t i; 3384 3385 if (k < 0) 3386 { 3387 val = -k; 3388 .eachImpl!fun(selectFrontOf!(val, slices)); 3389 3390 foreach(ref slice; slices) 3391 slice.popFrontExactly!0(-k); 3392 i = -k; 3393 } 3394 3395 do 3396 { 3397 val = (n - k) - i; 3398 .eachImpl!fun(frontSelectBackOf!(val, slices)); 3399 3400 foreach(ref slice; slices) 3401 slice.popFront; 3402 i++; 3403 } while ((i < (n - k)) && (i < m)); 3404 } 3405 } 3406 else 3407 { 3408 alias eachUpper = .eachUpper!(naryFun!fun); 3409 } 3410 } 3411 3412 pure nothrow 3413 version(mir_test) unittest 3414 { 3415 import mir.ndslice.allocation: slice; 3416 import mir.ndslice.topology: iota, canonical, universal; 3417 3418 pure nothrow 3419 void test(alias func)() 3420 { 3421 //| 1 2 3 | 3422 //| 4 5 6 | 3423 //| 7 8 9 | 3424 auto m = func(iota([3, 3], 1).slice); 3425 m.eachUpper!"a = 0"(0); 3426 assert(m == [ 3427 [0, 0, 0], 3428 [4, 0, 0], 3429 [7, 8, 0]]); 3430 } 3431 3432 @safe pure nothrow @nogc 3433 T identity(T)(T x) 3434 { 3435 return x; 3436 } 3437 3438 alias kinds = AliasSeq!(identity, canonical, universal); 3439 test!(kinds[0]); 3440 test!(kinds[1]); 3441 test!(kinds[2]); 3442 } 3443 3444 /// 3445 pure nothrow 3446 version(mir_test) unittest 3447 { 3448 import mir.ndslice.allocation: slice; 3449 import mir.ndslice.topology: iota; 3450 3451 //| 1 2 3 | 3452 //| 4 5 6 | 3453 //| 7 8 9 | 3454 auto m = iota([3, 3], 1).slice; 3455 m.eachUpper!"a = 0"; 3456 assert(m == [ 3457 [1, 0, 0], 3458 [4, 5, 0], 3459 [7, 8, 9]]); 3460 } 3461 3462 pure nothrow 3463 version(mir_test) unittest 3464 { 3465 import mir.ndslice.allocation: slice; 3466 import mir.ndslice.topology: iota; 3467 3468 //| 1 2 3 | 3469 //| 4 5 6 | 3470 //| 7 8 9 | 3471 auto m = iota([3, 3], 1).slice; 3472 m.eachUpper!"a = 0"(-1); 3473 assert(m == [ 3474 [0, 0, 0], 3475 [0, 0, 0], 3476 [7, 0, 0]]); 3477 } 3478 3479 pure nothrow 3480 version(mir_test) unittest 3481 { 3482 import mir.ndslice.allocation: slice; 3483 import mir.ndslice.topology: iota; 3484 3485 //| 1 2 3 | 3486 //| 4 5 6 | 3487 //| 7 8 9 | 3488 auto m = iota([3, 3], 1).slice; 3489 m.eachUpper!"a = 0"(2); 3490 assert(m == [ 3491 [1, 2, 0], 3492 [4, 5, 6], 3493 [7, 8, 9]]); 3494 } 3495 3496 pure nothrow 3497 version(mir_test) unittest 3498 { 3499 import mir.ndslice.allocation: slice; 3500 import mir.ndslice.topology: iota; 3501 3502 //| 1 2 3 | 3503 //| 4 5 6 | 3504 //| 7 8 9 | 3505 auto m = iota([3, 3], 1).slice; 3506 m.eachUpper!"a = 0"(-2); 3507 assert(m == [ 3508 [0, 0, 0], 3509 [0, 0, 0], 3510 [0, 0, 0]]); 3511 } 3512 3513 pure nothrow 3514 version(mir_test) unittest 3515 { 3516 import mir.ndslice.allocation: slice; 3517 import mir.ndslice.topology: iota; 3518 3519 //| 1 2 3 4 | 3520 //| 5 6 7 8 | 3521 //| 9 10 11 12 | 3522 auto m = iota([3, 4], 1).slice; 3523 m.eachUpper!"a = 0"(0); 3524 assert(m == [ 3525 [0, 0, 0, 0], 3526 [5, 0, 0, 0], 3527 [9, 10, 0, 0]]); 3528 } 3529 3530 pure nothrow 3531 version(mir_test) unittest 3532 { 3533 import mir.ndslice.allocation: slice; 3534 import mir.ndslice.topology: iota; 3535 3536 //| 1 2 3 4 | 3537 //| 5 6 7 8 | 3538 //| 9 10 11 12 | 3539 auto m = iota([3, 4], 1).slice; 3540 m.eachUpper!"a = 0"; 3541 assert(m == [ 3542 [1, 0, 0, 0], 3543 [5, 6, 0, 0], 3544 [9, 10, 11, 0]]); 3545 } 3546 3547 pure nothrow 3548 version(mir_test) unittest 3549 { 3550 import mir.ndslice.allocation: slice; 3551 import mir.ndslice.topology: iota; 3552 3553 //| 1 2 3 4 | 3554 //| 5 6 7 8 | 3555 //| 9 10 11 12 | 3556 auto m = iota([3, 4], 1).slice; 3557 m.eachUpper!"a = 0"(-1); 3558 assert(m == [ 3559 [0, 0, 0, 0], 3560 [0, 0, 0, 0], 3561 [9, 0, 0, 0]]); 3562 } 3563 3564 pure nothrow 3565 version(mir_test) unittest 3566 { 3567 import mir.ndslice.allocation: slice; 3568 import mir.ndslice.topology: iota; 3569 3570 //| 1 2 3 4 | 3571 //| 5 6 7 8 | 3572 //| 9 10 11 12 | 3573 auto m = iota([3, 4], 1).slice; 3574 m.eachUpper!"a = 0"(2); 3575 assert(m == [ 3576 [1, 2, 0, 0], 3577 [5, 6, 7, 0], 3578 [9, 10, 11, 12]]); 3579 } 3580 3581 pure nothrow 3582 version(mir_test) unittest 3583 { 3584 import mir.ndslice.allocation: slice; 3585 import mir.ndslice.topology: iota; 3586 3587 //| 1 2 3 4 | 3588 //| 5 6 7 8 | 3589 //| 9 10 11 12 | 3590 auto m = iota([3, 4], 1).slice; 3591 m.eachUpper!"a = 0"(-2); 3592 assert(m == [ 3593 [0, 0, 0, 0], 3594 [0, 0, 0, 0], 3595 [0, 0, 0, 0]]); 3596 } 3597 3598 pure nothrow 3599 version(mir_test) unittest 3600 { 3601 import mir.ndslice.allocation: slice; 3602 import mir.ndslice.topology: iota; 3603 3604 //| 1 2 3 | 3605 //| 4 5 6 | 3606 //| 7 8 9 | 3607 //| 10 11 12 | 3608 auto m = iota([4, 3], 1).slice; 3609 m.eachUpper!"a = 0"(0); 3610 assert(m == [ 3611 [0, 0, 0], 3612 [4, 0, 0], 3613 [7, 8, 0], 3614 [10, 11, 12]]); 3615 } 3616 3617 pure nothrow 3618 version(mir_test) unittest 3619 { 3620 import mir.ndslice.allocation: slice; 3621 import mir.ndslice.topology: iota; 3622 3623 //| 1 2 3 | 3624 //| 4 5 6 | 3625 //| 7 8 9 | 3626 //| 10 11 12 | 3627 auto m = iota([4, 3], 1).slice; 3628 m.eachUpper!"a = 0"; 3629 assert(m == [ 3630 [1, 0, 0], 3631 [4, 5, 0], 3632 [7, 8, 9], 3633 [10, 11, 12]]); 3634 } 3635 3636 pure nothrow 3637 version(mir_test) unittest 3638 { 3639 import mir.ndslice.allocation: slice; 3640 import mir.ndslice.topology: iota; 3641 3642 //| 1 2 3 | 3643 //| 4 5 6 | 3644 //| 7 8 9 | 3645 //| 10 11 12 | 3646 auto m = iota([4, 3], 1).slice; 3647 m.eachUpper!"a = 0"(-1); 3648 assert(m == [ 3649 [0, 0, 0], 3650 [0, 0, 0], 3651 [7, 0, 0], 3652 [10, 11, 0]]); 3653 } 3654 3655 pure nothrow 3656 version(mir_test) unittest 3657 { 3658 import mir.ndslice.allocation: slice; 3659 import mir.ndslice.topology: iota; 3660 3661 //| 1 2 3 | 3662 //| 4 5 6 | 3663 //| 7 8 9 | 3664 //| 10 11 12 | 3665 auto m = iota([4, 3], 1).slice; 3666 m.eachUpper!"a = 0"(2); 3667 assert(m == [ 3668 [1, 2, 0], 3669 [4, 5, 6], 3670 [7, 8, 9], 3671 [10, 11, 12]]); 3672 } 3673 3674 pure nothrow 3675 version(mir_test) unittest 3676 { 3677 import mir.ndslice.allocation: slice; 3678 import mir.ndslice.topology: iota; 3679 3680 //| 1 2 3 | 3681 //| 4 5 6 | 3682 //| 7 8 9 | 3683 //| 10 11 12 | 3684 auto m = iota([4, 3], 1).slice; 3685 m.eachUpper!"a = 0"(-2); 3686 assert(m == [ 3687 [0, 0, 0], 3688 [0, 0, 0], 3689 [0, 0, 0], 3690 [10, 0, 0]]); 3691 } 3692 3693 /// Swap two slices 3694 pure nothrow 3695 version(mir_test) unittest 3696 { 3697 import mir.utility : swap; 3698 import mir.ndslice.allocation : slice; 3699 import mir.ndslice.topology : as, iota; 3700 3701 //| 0 1 2 | 3702 //| 3 4 5 | 3703 //| 6 7 8 | 3704 auto a = iota([3, 3]).as!double.slice; 3705 //| 10 11 12 | 3706 //| 13 14 15 | 3707 //| 16 17 18 | 3708 auto b = iota([3, 3], 10).as!double.slice; 3709 3710 eachUpper!swap(a, b); 3711 3712 assert(a == [ 3713 [0, 11, 12], 3714 [3, 4, 15], 3715 [6, 7, 8]]); 3716 assert(b == [ 3717 [10, 1, 2], 3718 [13, 14, 5], 3719 [16, 17, 18]]); 3720 } 3721 3722 /// Swap two zipped slices 3723 pure nothrow 3724 version(mir_test) unittest 3725 { 3726 import mir.utility : swap; 3727 import mir.ndslice.allocation : slice; 3728 import mir.ndslice.topology : as, zip, iota; 3729 3730 //| 0 1 2 | 3731 //| 3 4 5 | 3732 //| 6 7 8 | 3733 auto a = iota([3, 3]).as!double.slice; 3734 //| 10 11 12 | 3735 //| 13 14 15 | 3736 //| 16 17 18 | 3737 auto b = iota([3, 3], 10).as!double.slice; 3738 3739 auto z = zip(a, b); 3740 3741 z.eachUpper!(z => swap(z.a, z.b)); 3742 3743 assert(a == [ 3744 [0, 11, 12], 3745 [3, 4, 15], 3746 [6, 7, 8]]); 3747 assert(b == [ 3748 [10, 1, 2], 3749 [13, 14, 5], 3750 [16, 17, 18]]); 3751 } 3752 3753 // uniq 3754 /** 3755 Lazily iterates unique consecutive elements of the given range (functionality 3756 akin to the $(HTTP wikipedia.org/wiki/_Uniq, _uniq) system 3757 utility). Equivalence of elements is assessed by using the predicate 3758 $(D pred), by default $(D "a == b"). The predicate is passed to 3759 $(REF nary, mir,functional), and can either accept a string, or any callable 3760 that can be executed via $(D pred(element, element)). If the given range is 3761 bidirectional, $(D uniq) also yields a 3762 `std,range,primitives`. 3763 Params: 3764 pred = Predicate for determining equivalence between range elements. 3765 */ 3766 template uniq(alias pred = "a == b") 3767 { 3768 static if (__traits(isSame, naryFun!pred, pred)) 3769 { 3770 /++ 3771 Params: 3772 r = An input range of elements to filter. 3773 Returns: 3774 An input range of 3775 consecutively unique elements in the original range. If `r` is also a 3776 forward range or bidirectional range, the returned range will be likewise. 3777 +/ 3778 Uniq!(naryFun!pred, Range) uniq(Range)(Range r) 3779 if (isInputRange!Range && !isSlice!Range) 3780 { 3781 import core.lifetime: move; 3782 return typeof(return)(r.move); 3783 } 3784 3785 /// ditto 3786 auto uniq(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) 3787 { 3788 import mir.ndslice.topology: flattened; 3789 import core.lifetime: move; 3790 auto r = slice.move.flattened; 3791 return Uniq!(pred, typeof(r))(move(r)); 3792 } 3793 } 3794 else 3795 alias uniq = .uniq!(naryFun!pred); 3796 } 3797 3798 /// 3799 @safe version(mir_test) unittest 3800 { 3801 int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; 3802 assert(equal(uniq(arr), [ 1, 2, 3, 4, 5 ])); 3803 3804 import std.algorithm.mutation : copy; 3805 // Filter duplicates in-place using copy 3806 arr.length -= arr.uniq.copy(arr).length; 3807 assert(arr == [ 1, 2, 3, 4, 5 ]); 3808 3809 // Note that uniqueness is only determined consecutively; duplicated 3810 // elements separated by an intervening different element will not be 3811 // eliminated: 3812 assert(equal(uniq([ 1, 1, 2, 1, 1, 3, 1]), [1, 2, 1, 3, 1])); 3813 } 3814 3815 /// N-dimensional case 3816 version(mir_test) 3817 @safe pure unittest 3818 { 3819 import mir.ndslice.fuse; 3820 import mir.ndslice.topology: byDim, map, iota; 3821 3822 auto matrix = [ [1, 2, 2], [2, 2, 3], [4, 4, 4] ].fuse; 3823 3824 assert(matrix.uniq.equal([ 1, 2, 3, 4 ])); 3825 3826 // unique elements for each row 3827 assert(matrix.byDim!0.map!uniq.equal!equal([ [1, 2], [2, 3], [4] ])); 3828 } 3829 3830 /++ 3831 Authros: $(HTTP erdani.com, Andrei Alexandrescu) (original Phobos code), Ilya Yaroshenko (betterC rework) 3832 +/ 3833 struct Uniq(alias pred, Range) 3834 { 3835 Range _input; 3836 3837 ref opSlice() inout 3838 { 3839 return this; 3840 } 3841 3842 void popFront() scope 3843 { 3844 assert(!empty, "Attempting to popFront an empty uniq."); 3845 auto last = _input.front; 3846 do 3847 { 3848 _input.popFront(); 3849 } 3850 while (!_input.empty && pred(last, _input.front)); 3851 } 3852 3853 auto ref front() @property 3854 { 3855 assert(!empty, "Attempting to fetch the front of an empty uniq."); 3856 return _input.front; 3857 } 3858 3859 import std.range.primitives: isBidirectionalRange; 3860 3861 static if (isBidirectionalRange!Range) 3862 { 3863 void popBack() scope 3864 { 3865 assert(!empty, "Attempting to popBack an empty uniq."); 3866 auto last = _input.back; 3867 do 3868 { 3869 _input.popBack(); 3870 } 3871 while (!_input.empty && pred(last, _input.back)); 3872 } 3873 3874 auto ref back() scope return @property 3875 { 3876 assert(!empty, "Attempting to fetch the back of an empty uniq."); 3877 return _input.back; 3878 } 3879 } 3880 3881 static if (isInfinite!Range) 3882 { 3883 enum bool empty = false; // Propagate infiniteness. 3884 } 3885 else 3886 { 3887 @property bool empty() const { return _input.empty; } 3888 } 3889 3890 import std.range.primitives: isForwardRange; 3891 3892 static if (isForwardRange!Range) 3893 { 3894 @property typeof(this) save() scope return 3895 { 3896 return typeof(this)(_input.save); 3897 } 3898 } 3899 } 3900 3901 version(none) 3902 @safe version(mir_test) unittest 3903 { 3904 import std.internal.test.dummyrange; 3905 import std.range; 3906 3907 int[] arr = [ 1, 2, 2, 2, 2, 3, 4, 4, 4, 5 ]; 3908 auto r = uniq(arr); 3909 static assert(isForwardRange!(typeof(r))); 3910 3911 assert(equal(r, [ 1, 2, 3, 4, 5 ][])); 3912 assert(equal(retro(r), retro([ 1, 2, 3, 4, 5 ][]))); 3913 3914 foreach (DummyType; AllDummyRanges) 3915 { 3916 DummyType d; 3917 auto u = uniq(d); 3918 assert(equal(u, [1,2,3,4,5,6,7,8,9,10])); 3919 3920 static assert(d.rt == RangeType.Input || isForwardRange!(typeof(u))); 3921 3922 static if (d.rt >= RangeType.Bidirectional) 3923 { 3924 assert(equal(retro(u), [10,9,8,7,6,5,4,3,2,1])); 3925 } 3926 } 3927 } 3928 3929 @safe version(mir_test) unittest // https://issues.dlang.org/show_bug.cgi?id=17264 3930 { 3931 const(int)[] var = [0, 1, 1, 2]; 3932 assert(var.uniq.equal([0, 1, 2])); 3933 } 3934 3935 @safe version(mir_test) unittest { 3936 import mir.ndslice.allocation; 3937 import mir.math.common: approxEqual; 3938 auto x = rcslice!double(2); 3939 auto y = rcslice!double(2); 3940 x[] = [2, 3]; 3941 y[] = [2, 3]; 3942 assert(equal!approxEqual(x,y)); 3943 } 3944 3945 /++ 3946 Implements the higher order filter function. The predicate is passed to 3947 `mir.functional.naryFun`, and can either accept a string, or any callable 3948 that can be executed via `pred(element)`. 3949 Params: 3950 pred = Function to apply to each element of range 3951 Returns: 3952 `filter!(pred)(range)` returns a new range containing only elements `x` in `range` for 3953 which `pred(x)` returns `true`. 3954 See_Also: 3955 $(HTTP en.wikipedia.org/wiki/Filter_(higher-order_function), Filter (higher-order function)) 3956 Note: 3957 $(RED User and library code MUST call `empty` method ahead each call of pair or one of `front` and `popFront` methods.) 3958 +/ 3959 template filter(alias pred = "a") 3960 { 3961 static if (__traits(isSame, naryFun!pred, pred)) 3962 { 3963 /++ 3964 Params: 3965 r = An input range of elements to filter. 3966 Returns: 3967 A new range containing only elements `x` in `range` for which `predicate(x)` returns `true`. 3968 +/ 3969 Filter!(naryFun!pred, Range) filter(Range)(Range r) 3970 if (isInputRange!Range && !isSlice!Range) 3971 { 3972 import core.lifetime: move; 3973 return typeof(return)(r.move); 3974 } 3975 3976 /// ditto 3977 auto filter(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) 3978 { 3979 import mir.ndslice.topology: flattened; 3980 import core.lifetime: move; 3981 auto r = slice.move.flattened; 3982 return Filter!(pred, typeof(r))(move(r)); 3983 } 3984 } 3985 else 3986 alias filter = .filter!(naryFun!pred); 3987 } 3988 3989 /// ditto 3990 struct Filter(alias pred, Range) 3991 { 3992 Range _input; 3993 version(assert) bool _freshEmpty; 3994 3995 ref opSlice() inout 3996 { 3997 return this; 3998 } 3999 4000 void popFront() scope 4001 { 4002 assert(!_input.empty, "Attempting to popFront an empty Filter."); 4003 version(assert) assert(_freshEmpty, "Attempting to pop the front of a Filter without calling '.empty' method ahead."); 4004 version(assert) _freshEmpty = false; 4005 _input.popFront; 4006 } 4007 4008 auto ref front() @property 4009 { 4010 assert(!_input.empty, "Attempting to fetch the front of an empty Filter."); 4011 version(assert) assert(_freshEmpty, "Attempting to fetch the front of a Filter without calling '.empty' method ahead."); 4012 return _input.front; 4013 } 4014 4015 bool empty() @property 4016 { 4017 version(assert) _freshEmpty = true; 4018 for (;;) 4019 { 4020 if (auto r = _input.empty) 4021 return true; 4022 if (pred(_input.front)) 4023 return false; 4024 _input.popFront; 4025 } 4026 } 4027 4028 import std.range.primitives: isForwardRange; 4029 static if (isForwardRange!Range) 4030 { 4031 @property typeof(this) save() scope return 4032 { 4033 return typeof(this)(_input.save); 4034 } 4035 } 4036 } 4037 4038 /// 4039 version(mir_test) 4040 @safe pure nothrow unittest 4041 { 4042 int[] arr = [ 0, 1, 2, 3, 4, 5 ]; 4043 4044 // Filter below 3 4045 auto small = filter!(a => a < 3)(arr); 4046 assert(equal(small, [ 0, 1, 2 ])); 4047 4048 // Filter again, but with Uniform Function Call Syntax (UFCS) 4049 auto sum = arr.filter!(a => a < 3); 4050 assert(equal(sum, [ 0, 1, 2 ])); 4051 4052 // Filter with the default predicate 4053 auto nonZeros = arr.filter; 4054 assert(equal(nonZeros, [ 1, 2, 3, 4, 5 ])); 4055 4056 // In combination with concatenation() to span multiple ranges 4057 import mir.ndslice.concatenation; 4058 4059 int[] a = [ 3, -2, 400 ]; 4060 int[] b = [ 100, -101, 102 ]; 4061 auto r = concatenation(a, b).filter!(a => a > 0); 4062 assert(equal(r, [ 3, 400, 100, 102 ])); 4063 4064 // Mixing convertible types is fair game, too 4065 double[] c = [ 2.5, 3.0 ]; 4066 auto r1 = concatenation(c, a, b).filter!(a => cast(int) a != a); 4067 assert(equal(r1, [ 2.5 ])); 4068 } 4069 4070 /// N-dimensional filtering 4071 version(mir_test) 4072 @safe pure unittest 4073 { 4074 import mir.ndslice.fuse; 4075 import mir.ndslice.topology: byDim, map; 4076 4077 auto matrix = 4078 [[ 3, -2, 400 ], 4079 [ 100, -101, 102 ]].fuse; 4080 4081 alias filterPositive = filter!"a > 0"; 4082 4083 // filter all elements in the matrix 4084 auto r = filterPositive(matrix); 4085 assert(equal(r, [ 3, 400, 100, 102 ])); 4086 4087 // filter all elements for each row 4088 auto rr = matrix.byDim!0.map!filterPositive; 4089 assert(equal!equal(rr, [ [3, 400], [100, 102] ])); 4090 4091 // filter all elements for each column 4092 auto rc = matrix.byDim!1.map!filterPositive; 4093 assert(equal!equal(rc, [ [3, 100], [], [400, 102] ])); 4094 } 4095 4096 /++ 4097 Implements the higher order filter and map function. The predicate and map functions are passed to 4098 `mir.functional.naryFun`, and can either accept a string, or any callable 4099 that can be executed via `pred(element)` and `map(element)`. 4100 Params: 4101 pred = Filter function to apply to each element of range (optional) 4102 map = Map function to apply to each element of range 4103 Returns: 4104 `rcfilter!(pred)(range)` returns a new RCArray containing only elements `map(x)` in `range` for 4105 which `pred(x)` returns `true`. 4106 See_Also: 4107 $(HTTP en.wikipedia.org/wiki/Filter_(higher-order_function), Filter (higher-order function)) 4108 +/ 4109 template rcfilter(alias pred = "a", alias map = "a") 4110 { 4111 static if (__traits(isSame, naryFun!pred, pred) && __traits(isSame, naryFun!map, map)) 4112 { 4113 /++ 4114 Params: 4115 r = An input range of elements to filter. 4116 Returns: 4117 A new range containing only elements `x` in `range` for which `predicate(x)` returns `true`. 4118 +/ 4119 auto rcfilter(Range)(Range r) 4120 if (isIterable!Range && (!isSlice!Range || DimensionCount!Range == 1)) 4121 { 4122 import core.lifetime: forward; 4123 import mir.appender: scopedBuffer; 4124 import mir.primitives: isInputRange; 4125 import mir.rc.array: RCArray; 4126 4127 alias T = typeof(map(r.front)); 4128 auto buffer = scopedBuffer!T; 4129 foreach (ref e; r) 4130 { 4131 if (pred(e)) 4132 { 4133 static if (__traits(isSame, naryFun!"a", map)) 4134 buffer.put(forward!e); 4135 else 4136 buffer.put(map(forward!e)); 4137 } 4138 } 4139 return () @trusted 4140 { 4141 auto ret = RCArray!T(buffer.length); 4142 buffer.moveDataAndEmplaceTo(ret[]); 4143 return ret; 4144 } (); 4145 } 4146 4147 /// ditto 4148 auto rcfilter(Iterator, size_t N, SliceKind kind)(Slice!(Iterator, N, kind) slice) 4149 if (N > 1) 4150 { 4151 import mir.ndslice.topology: flattened; 4152 import core.lifetime: move; 4153 return rcfilter(slice.move.flattened); 4154 } 4155 } 4156 else 4157 alias rcfilter = .rcfilter!(naryFun!pred, naryFun!map); 4158 } 4159 4160 /// 4161 version(mir_test) 4162 @safe pure nothrow @nogc unittest 4163 { 4164 import mir.ndslice.topology: iota; 4165 4166 auto val = 3; 4167 auto factor = 5; 4168 // Filter iota 2x3 matrix below 3 4169 assert(iota(2, 3).rcfilter!(a => a < val).moveToSlice.equal(val.iota)); 4170 // Filter and map below 3 4171 assert(6.iota.rcfilter!(a => a < val, a => a * factor).moveToSlice.equal(val.iota * factor)); 4172 } 4173 4174 version(mir_test) 4175 @safe pure nothrow @nogc unittest 4176 { 4177 import mir.ndslice.topology: iota, as; 4178 4179 auto val = 3; 4180 auto factor = 5; 4181 // Filter iota 2x3 matrix below 3 4182 assert(iota(2, 3).as!(const int).rcfilter!(a => a < val).moveToSlice.equal(val.iota)); 4183 // Filter and map below 3 4184 assert(6.iota.as!(immutable int).rcfilter!(a => a < val, a => a * factor).moveToSlice.equal(val.iota * factor)); 4185 } 4186 4187 /++ 4188 Implements the homonym function (also known as `accumulate`, $(D 4189 compress), `inject`, or `foldl`) present in various programming 4190 languages of functional flavor. The call `fold!(fun)(slice, seed)` 4191 first assigns `seed` to an internal variable `result`, 4192 also called the accumulator. Then, for each element `x` in $(D 4193 slice), `result = fun(result, x)` gets evaluated. Finally, $(D 4194 result) is returned. 4195 4196 Params: 4197 fun = the predicate function to apply to the elements 4198 4199 See_Also: 4200 $(HTTP en.wikipedia.org/wiki/Fold_(higher-order_function), Fold (higher-order function)) 4201 $(LREF sum) is similar to `fold!((a, b) => a + b)` that offers 4202 precise summing of floating point numbers. 4203 This is functionally equivalent to $(LREF reduce) with the argument order 4204 reversed. 4205 +/ 4206 template fold(alias fun) 4207 { 4208 /++ 4209 Params: 4210 slice = A slice, range, and array. 4211 seed = An initial accumulation value. 4212 Returns: 4213 the accumulated result 4214 +/ 4215 @optmath auto fold(Slice, S)(scope Slice slice, S seed) 4216 { 4217 import core.lifetime: move; 4218 return reduce!fun(seed, slice.move); 4219 } 4220 } 4221 4222 /// 4223 version(mir_test) 4224 @safe pure nothrow 4225 unittest 4226 { 4227 import mir.ndslice.slice: sliced; 4228 import mir.ndslice.topology: map; 4229 4230 auto arr = [1, 2, 3, 4, 5].sliced; 4231 4232 // Sum all elements 4233 assert(arr.fold!((a, b) => a + b)(0) == 15); 4234 assert(arr.fold!((a, b) => a + b)(6) == 21); 4235 4236 // Can be used in a UFCS chain 4237 assert(arr.map!(a => a + 1).fold!((a, b) => a + b)(0) == 20); 4238 4239 // Return the last element of any range 4240 assert(arr.fold!((a, b) => b)(0) == 5); 4241 } 4242 4243 /// Works for matrices 4244 version(mir_test) 4245 @safe pure 4246 unittest 4247 { 4248 import mir.ndslice.fuse: fuse; 4249 4250 auto arr = [ 4251 [1, 2, 3], 4252 [4, 5, 6] 4253 ].fuse; 4254 4255 assert(arr.fold!((a, b) => a + b)(0) == 21); 4256 } 4257 4258 version(mir_test) 4259 @safe pure nothrow 4260 unittest 4261 { 4262 import mir.ndslice.topology: map; 4263 4264 int[] arr = [1, 2, 3, 4, 5]; 4265 4266 // Sum all elements 4267 assert(arr.fold!((a, b) => a + b)(0) == 15); 4268 assert(arr.fold!((a, b) => a + b)(6) == 21); 4269 4270 // Can be used in a UFCS chain 4271 assert(arr.map!(a => a + 1).fold!((a, b) => a + b)(0) == 20); 4272 4273 // Return the last element of any range 4274 assert(arr.fold!((a, b) => b)(0) == 5); 4275 } 4276 4277 version(mir_test) 4278 @safe pure nothrow 4279 unittest 4280 { 4281 int[] arr = [1]; 4282 static assert(!is(typeof(arr.fold!()(0)))); 4283 static assert(!is(typeof(arr.fold!(a => a)(0)))); 4284 static assert(is(typeof(arr.fold!((a, b) => a)(0)))); 4285 assert(arr.length == 1); 4286 } 4287 4288 unittest 4289 { 4290 import mir.rc.array: RCArray; 4291 import mir.algorithm.iteration: minmaxPos, minPos, maxPos, minmaxIndex, minIndex, maxIndex; 4292 4293 static immutable a = [0.0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; 4294 4295 auto x = RCArray!double(12); 4296 foreach(i, ref e; x) 4297 e = a[i]; 4298 auto y = x.asSlice; 4299 auto z0 = y.minmaxPos; 4300 auto z1 = y.minPos; 4301 auto z2 = y.maxPos; 4302 auto z3 = y.minmaxIndex; 4303 auto z4 = y.minIndex; 4304 auto z5 = y.maxIndex; 4305 }