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