1 /** Extensions to std.range. 2 License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0). 3 */ 4 5 module nxt.range_ex; 6 7 import core.internal.traits: hasElaborateDestructor; 8 import std.traits : isSomeString, isNarrowString; 9 import std.range: hasSlicing, hasLength, isInfinite, isInputRange, isBidirectionalRange, ElementType, isRandomAccessRange; 10 import std.traits: hasUnsharedAliasing, isScalarType; 11 12 public import nxt.slicing; 13 14 enum hasPureCopy(T) = (isScalarType!T || // TODO remove? 15 (!hasUnsharedAliasing!T && 16 !hasElaborateDestructor!T)); 17 18 enum hasStealableElements(R) = (hasPureCopy!(ElementType!R)); // TODO recurse 19 20 /* template hasStealableElements(T...) */ 21 /* { */ 22 /* import std.range: ElementType; */ 23 /* import std.typecons : Rebindable; */ 24 25 /* static if (is(ElementType!T)) */ 26 /* { */ 27 /* enum hasStealableElements = true; */ 28 /* } */ 29 /* else static if (is(T[0] R: Rebindable!R)) */ 30 /* { */ 31 /* enum hasStealableElements = hasStealableElements!R; */ 32 /* } */ 33 /* else */ 34 /* { */ 35 /* template unsharedDelegate(T) */ 36 /* { */ 37 /* enum bool unsharedDelegate = isDelegate!T */ 38 /* && !is(T == shared) */ 39 /* && !is(T == shared) */ 40 /* && !is(T == immutable) */ 41 /* && !is(FunctionTypeOf!T == shared) */ 42 /* && !is(FunctionTypeOf!T == immutable); */ 43 /* } */ 44 45 /* enum hasStealableElements = */ 46 /* hasRawUnsharedAliasing!(T[0]) || */ 47 /* anySatisfy!(unsharedDelegate, RepresentationTypeTuple!(T[0])) || */ 48 /* hasUnsharedObjects!(T[0]) || */ 49 /* hasStealableElements!(T[1..$]); */ 50 /* } */ 51 /* } */ 52 53 /** Steal front from $(D r) destructively and return it. 54 See_Also: http://forum.dlang.org/thread/jkbhlezbcrufowxtthmy@forum.dlang.org#post-konhvblwbmpdrbeqhyuv:40forum.dlang.org 55 See_Also: http://forum.dlang.org/thread/onibkzepudfisxtrigsi@forum.dlang.org#post-dafmzroxvaeejyxrkbon:40forum.dlang.org 56 */ 57 ElementType!R frontPop(R)(ref R r) 58 if (isInputRange!R && 59 hasStealableElements!R) 60 { 61 import std.range: moveFront, popFront; 62 /* scope(success) r.popFront(); */ 63 /* return r.moveFront(); */ 64 auto e = r.moveFront(); 65 66 r.popFront(); 67 68 import std.traits : hasIndirections; 69 static if (hasIndirections!(typeof(return))) // TODO better trait? 70 { 71 import core.lifetime : move; 72 return move(e); 73 } 74 else 75 { 76 return e; 77 } 78 } 79 alias stealFront = frontPop; 80 alias pullFront = frontPop; 81 alias takeFront = frontPop; 82 83 version(unittest) 84 { 85 import nxt.array_help : s; 86 } 87 88 @safe pure nothrow unittest 89 { 90 auto x = [11, 22]; 91 assert(x.frontPop() == 11); assert(x == [22]); 92 assert(x.frontPop() == 22); assert(x == []); 93 } 94 95 @safe pure nothrow unittest 96 { 97 auto x = ["a", "b"]; 98 assert(x.frontPop() == "a"); assert(x == ["b"]); 99 } 100 101 @safe pure nothrow unittest 102 { 103 struct V { int x, y; } 104 auto x = [V(11, 12), 105 V(21, 22)]; 106 assert(x.frontPop() == V(11, 12)); assert(x == [V(21, 22)]); 107 assert(x.frontPop() == V(21, 22)); assert(x == []); 108 } 109 110 /** Steal back from $(D r) destructively and return it. 111 See_Also: http://forum.dlang.org/thread/jkbhlezbcrufowxtthmy@forum.dlang.org#post-konhvblwbmpdrbeqhyuv:40forum.dlang.org 112 See_Also: http://forum.dlang.org/thread/onibkzepudfisxtrigsi@forum.dlang.org#post-dafmzroxvaeejyxrkbon:40forum.dlang.org 113 */ 114 ElementType!R backPop(R)(ref R r) 115 if (isInputRange!R && 116 hasStealableElements!R) 117 { 118 import std.range: moveBack, popBack; 119 /* scope(success) r.popBack(); */ 120 /* return r.moveBack(); */ 121 auto e = r.moveBack(); 122 123 r.popBack(); 124 125 import std.traits : hasIndirections; 126 static if (hasIndirections!(typeof(return))) // TODO better trait? 127 { 128 import core.lifetime : move; 129 return move(e); 130 } 131 else 132 { 133 return e; 134 } 135 } 136 alias stealBack = backPop; 137 alias pullBack = backPop; 138 alias takeBack = backPop; 139 140 @safe pure nothrow unittest 141 { 142 auto x = [11, 22]; 143 assert(x.backPop() == 22); assert(x == [11]); 144 assert(x.backPop() == 11); assert(x == []); 145 } 146 147 @safe pure nothrow unittest 148 { 149 auto x = ["a", "b"]; 150 assert(x.backPop() == "b"); assert(x == ["a"]); 151 } 152 153 @safe pure nothrow unittest 154 { 155 struct V { int x, y; } 156 auto x = [V(11, 12), 157 V(21, 22)]; 158 assert(x.backPop() == V(21, 22)); assert(x == [V(11, 12)]); 159 assert(x.backPop() == V(11, 12)); assert(x == []); 160 } 161 162 /** Sliding Splitter. 163 * 164 * See_Also: http://forum.dlang.org/thread/dndicafxfubzmndehzux@forum.dlang.org 165 * See_Also: http://forum.dlang.org/thread/uzrbmjonrkixojzflbig@forum.dlang.org#epost-viwkavbmwouiquoqwntm:40forum.dlang.org 166 * 167 * TODO Use size_t for _lower and _upper instead and reserve _upper = size_t.max 168 * for emptyness? 169 * 170 * TODO Should lower and upper operate on code units instead of code point if 171 * isNarrowString!Range. ? 172 * 173 * TODO generalize with stride 174 */ 175 struct SlidingSplitter(Range) 176 if (isSomeString!Range || 177 (hasSlicing!Range && 178 !isInfinite!Range)) 179 { 180 import std.range: isForwardRange; 181 import core.internal.traits : Unqual; 182 import std.typecons : Tuple, tuple; 183 alias R = Unqual!Range; 184 185 this(R)(R data, size_t lower = 0) 186 in { assert(lower <= data.length); } 187 do 188 { 189 _data = data; 190 static if (hasSlicing!Range) // TODO should we use isSomeString here instead? 191 { 192 _lower = lower; 193 _upper = data.length; 194 } 195 else 196 { 197 while (lower) 198 { 199 popFront; 200 --lower; 201 } 202 } 203 _upper = data.length; 204 } 205 206 this(R)(R data, size_t lower, size_t upper) 207 in { assert(lower <= upper + 1 || // the extra + 1 makes empty initialization (lower + 1 == upper) possible in for example opSlice below 208 ((lower <= data.length) && 209 (upper <= data.length))); } 210 do 211 { 212 _data = data; 213 _lower = lower; 214 _upper = upper; 215 } 216 217 @property Tuple!(R, R) front() 218 { 219 return typeof(return)(_data[0 .. _lower], 220 _data[_lower .. $]); 221 } 222 223 void popFront() 224 { 225 static if (isNarrowString!R) 226 { 227 if (_lower < _upper) 228 { 229 import std.utf: stride; 230 _lower += stride(_data, _lower); 231 } 232 else // when we can't decode beyond 233 { 234 ++_lower; // so just indicate we're beyond back 235 } 236 } 237 else 238 { 239 ++_lower; 240 } 241 } 242 243 static if (!isInfinite!R) 244 { 245 @property Tuple!(R, R) back() 246 { 247 return typeof(return)(_data[0 .. _upper], 248 _data[_upper .. $]); 249 } 250 void popBack() 251 { 252 static if (isNarrowString!R) 253 { 254 if (_lower < _upper) 255 { 256 import std.utf: strideBack; 257 _upper -= strideBack(_data, _upper); 258 } 259 else // when we can't decode beyond 260 { 261 --_upper; // so just indicate we're beyond front 262 } 263 } 264 else 265 { 266 --_upper; 267 } 268 } 269 } 270 271 static if (isForwardRange!R) 272 { 273 @property auto save() 274 { 275 import std.range: save; 276 return typeof(this)(_data.save, _lower, _upper); 277 } 278 } 279 280 static if (isInfinite!R) 281 { 282 enum bool empty = false; // propagate infiniteness 283 } 284 else 285 { 286 @property bool empty() const 287 { 288 return _upper < _lower; 289 } 290 } 291 292 static if (hasSlicing!R) 293 { 294 Tuple!(R, R) opIndex(size_t i) 295 in { assert(i < length); } 296 do 297 { 298 return typeof(return)(_data[0 .. _lower + i], 299 _data[_lower + i .. _upper]); 300 } 301 302 typeof(this) opSlice(size_t lower, size_t upper) 303 { 304 if (lower == upper) 305 { 306 return slidingSplitter(_data, 307 _upper + 1, // defines empty intialization 308 _upper); 309 } 310 else 311 { 312 return slidingSplitter(_data, 313 _lower + lower, 314 _lower + (upper - 1)); 315 } 316 } 317 318 // TODO Should length be provided if isNarrowString!Range? 319 @property size_t length() const 320 { 321 return _upper - _lower + 1; 322 } 323 } 324 325 private R _data; 326 private ptrdiff_t _lower; 327 private ptrdiff_t _upper; 328 } 329 330 auto slidingSplitter(R)(R data, size_t lower = 0) 331 { 332 return SlidingSplitter!R(data, lower, data.length); 333 } 334 335 auto slidingSplitter(R)(R data, size_t lower, size_t upper) 336 { 337 return SlidingSplitter!R(data, lower, upper); 338 } 339 340 @safe pure nothrow unittest 341 { 342 import std.typecons : tuple; 343 import std.conv: to; 344 345 auto x = [1, 2, 3]; 346 347 import std.range: isInputRange, isForwardRange, isBidirectionalRange, isRandomAccessRange; 348 349 static assert(isInputRange!(SlidingSplitter!(typeof(x)))); 350 static assert(isForwardRange!(SlidingSplitter!(typeof(x)))); 351 // static assert(isBidirectionalRange!(SlidingSplitter!(typeof(x)))); 352 static assert(isRandomAccessRange!(SlidingSplitter!(typeof(x)))); 353 static assert(!isRandomAccessRange!(SlidingSplitter!string)); 354 static assert(!isRandomAccessRange!(SlidingSplitter!wstring)); 355 static assert(isRandomAccessRange!(SlidingSplitter!dstring)); 356 357 auto y = SlidingSplitter!(typeof(x))(x); 358 359 for (size_t i; i < y.length; ++i) 360 { 361 assert(y[i] == tuple(x[0 .. i], 362 x[i .. 3])); 363 } 364 365 assert(y.front == tuple([], x)); 366 assert(!y.empty); 367 assert(x.length + 1 == y.length); 368 369 assert(!y.empty); assert(y.front == tuple(x[0 .. 0], x[0 .. 3])); y.popFront(); 370 assert(!y.empty); assert(y.front == tuple(x[0 .. 1], x[1 .. 3])); y.popFront(); 371 assert(!y.empty); assert(y.front == tuple(x[0 .. 2], x[2 .. 3])); y.popFront(); 372 assert(!y.empty); assert(y.front == tuple(x[0 .. 3], x[3 .. 3])); y.popFront(); 373 y.popFront(); assert(y.empty); 374 } 375 376 @safe pure unittest // forwards 377 { 378 import std.conv: to; 379 380 size_t lower = 2; 381 382 const name = "Nordlöw"; 383 auto name8 = name.to! string.slidingSplitter(lower); 384 auto name16 = name.to!wstring.slidingSplitter(lower); 385 auto name32 = name.to!dstring.slidingSplitter(lower); 386 387 static assert(!__traits(compiles, { name8.length >= 0; } )); 388 static assert(!__traits(compiles, { name16.length >= 0; } )); 389 assert(name32.length); 390 391 foreach (ch; name8) 392 { 393 static foreach (ix; 0 .. ch.length) // for each part in split 394 { 395 import std.algorithm: equal; 396 assert(ch[ix].equal(name16.front[ix])); 397 assert(ch[ix].equal(name32.front[ix])); 398 399 } 400 name16.popFront(); 401 name32.popFront(); 402 } 403 } 404 405 @safe pure unittest // backwards 406 { 407 import std.conv: to; 408 import std.range: retro; 409 410 size_t lower = 2; 411 412 auto name = "Nordlöw"; 413 auto name8 = name.to! string.slidingSplitter(lower).retro; 414 auto name16 = name.to!wstring.slidingSplitter(lower).retro; 415 auto name32 = name.to!dstring.slidingSplitter(lower).retro; 416 417 foreach (ch; name8) 418 { 419 import std.algorithm: equal; 420 static foreach (ix; 0 .. ch.length) // for each part in split 421 { 422 assert(ch[ix].equal(name16.front[ix])); 423 assert(ch[ix].equal(name32.front[ix])); 424 } 425 name16.popFront(); 426 name32.popFront(); 427 } 428 } 429 430 @safe pure nothrow unittest // radial 431 { 432 auto x = [1, 2, 3]; 433 import std.range: radial; 434 import std.typecons : tuple; 435 auto s = x.slidingSplitter; 436 auto r = s.radial; 437 assert(!r.empty); assert(r.front == tuple(x[0 .. 1], x[1 .. 3])); r.popFront(); 438 assert(!r.empty); assert(r.front == tuple(x[0 .. 2], x[2 .. 3])); r.popFront(); 439 assert(!r.empty); assert(r.front == tuple(x[0 .. 0], x[0 .. 3])); r.popFront(); 440 assert(!r.empty); assert(r.front == tuple(x[0 .. 3], x[3 .. 3])); r.popFront(); 441 assert(r.empty); 442 } 443 444 /** Ring Buffer. 445 * 446 * See_Also: http://forum.dlang.org/thread/ltpaqk$2dav$1@digitalmars.com 447 * TODO inout 448 */ 449 struct RingBuffer(T) 450 { 451 this(T[] data, size_t length = 0) 452 { 453 enforce(data.length, "empty ring buffer is prohibited"); 454 enforce(length <= data.length, "buffer length shall not be more 455 than buffer capacity"); 456 _data = data; 457 _beginIndex = 0; 458 _length = length; 459 } 460 461 auto opSlice() const 462 { 463 return cycle(_data[0 .. _length]).take(_length); 464 } 465 466 @property auto length() { return _length; } 467 468 private: 469 T[] _data; 470 size_t _beginIndex; 471 size_t _length; 472 } 473 474 /** Same as $(D iota) but with explicit conversion to type $(D T). 475 See_Also: http://forum.dlang.org/thread/mailman.955.1444358510.22025.digitalmars-d@puremagic.com?page=1 476 */ 477 auto iotaOf(T, B, E, S)(B begin = T.min, 478 E end = T.max, 479 S step = 1) 480 { 481 import std.range : iota; 482 import std.algorithm.iteration : map; 483 import std.conv : to; 484 return iota(begin, end, step).map!(a => cast(T)a); 485 } 486 487 // @safe pure unittest 488 // { 489 // import std.array : array; 490 // import std.exception : assertThrown; 491 // import std.meta : AliasSeq; 492 // foreach (T; AliasSeq!(ubyte, ushort, uint, ulong)) 493 // { 494 // auto x = iotaOf!T(0, T.max + 1); 495 // import nxt.traits_ex : ElementTypeOf; 496 // static assert(is(ElementTypeOf!(x) == T)); 497 // } 498 // } 499 500 auto iotaOfExceptional(T, B, E, S)(B begin = T.min, E end = T.max, S step = 1) 501 { 502 import std.range : iota; 503 import std.algorithm.iteration : map; 504 import std.conv : to; 505 return iota(begin, end, step).map!(a => a.to!T); 506 } 507 508 // @safe pure unittest 509 // { 510 // import std.array : array; 511 // import std.exception : assertThrown; 512 // import std.conv; 513 // alias T = ubyte; 514 // auto x = iotaOfExceptional!T(0, T.max + 1); 515 // import nxt.traits_ex : ElementTypeOf; 516 // static assert(is(ElementTypeOf!() == T)); 517 // assertThrown!ConvOverflowException(iotaOfExceptional!T(0, T.max + 1 + 1).array); 518 // } 519 520 /** Return Array of Key-Value Pairs of Associative Array $(D aa). 521 * 522 * See_Also: https://github.com/D-Programming-Language/druntime/pull/574 523 * See_Also: http://forum.dlang.org/thread/dxotcrutrlmszlidufcr@forum.dlang.org?page=2#post-fhkgitmifgnompkqiscd:40forum.dlang.org 524 */ 525 auto pairs(Key, Value)(Value[Key] aa) 526 { 527 import std.typecons: Tuple, tuple; 528 Tuple!(Key,Value)[] arr; 529 arr.reserve(aa.length); 530 foreach (key; aa.byKey) 531 { 532 arr ~= tuple(key, aa[key]); 533 } 534 return arr; 535 } 536 alias items = pairs; // TODO Is this Python-style naming better? 537 538 unittest 539 { 540 string[int] x; 541 x[0] = "a"; 542 import std.typecons : tuple; 543 assert(x.pairs == [tuple(0, "a")]); 544 } 545 546 import std.traits: isInstanceOf, Unqual; 547 import std.range: SortedRange; 548 import std.meta: allSatisfy, staticMap; 549 550 /// Is the `CommonType` of the `ElementType`s of the ranges `Rs`. 551 template CommonElementType(Rs...) 552 { 553 import std.traits: CommonType; 554 import std.range: ElementType; 555 alias CommonElementType = CommonType!(staticMap!(ElementType, Rs)); 556 } 557 558 /// 559 @safe pure nothrow @nogc unittest 560 { 561 static assert(is(CommonElementType!(int[], double[]) == double)); 562 } 563 564 enum bool haveCommonElementType(Types...) = !is(CommonElementType!Types == void); 565 566 /// Is `true` iff the ranges `Rs` have a common element type. 567 @safe pure nothrow @nogc unittest 568 { 569 static assert(haveCommonElementType!(bool[], int[])); 570 static assert(!haveCommonElementType!(bool[], int[], string[])); 571 } 572 573 alias isSortedRange(R) = isInstanceOf!(SortedRange, R); // TODO Or use: __traits(isSame, TemplateOf!R, SortedRange) 574 575 /** True if R is a `SortedRange` 576 * 577 * SeeAlso: 578 * `std.range.SortedRange` 579 */ 580 template isSortedRange_alt(R) 581 { 582 import std.range.primitives : SortedRange; 583 enum isSortedRange = is(R : SortedRange!U, U...); 584 } 585 586 /// 587 unittest 588 { 589 import std.algorithm : sort; 590 import std.range : assumeSorted; 591 static assert(isSortedRange!(typeof([0, 1, 2])) == false); 592 static assert(isSortedRange!(typeof([0, 1, 2].sort)) == true); 593 static assert(isSortedRange!(typeof([0, 1, 2].assumeSorted)) == true); 594 static assert(isSortedRange!int == false); 595 } 596 597 /// See_Also: http://forum.dlang.org/post/gkdqakdogqevwzntpgtu@forum.dlang.org 598 template genTypeList(T, size_t n) 599 { 600 import std.meta : AliasSeq; 601 static if (n <= 1) 602 alias genTypeList = T; 603 else 604 alias genTypeList = AliasSeq!(T, genTypeList!(T, n - 1)); 605 } 606 607 /** Return Static Array $(D arr) as a $(D Tuple). 608 * 609 * See_Also: http://forum.dlang.org/post/gkdqakdogqevwzntpgtu@forum.dlang.org 610 * Check if std.conv.to() support conversion from T[n] to std.typecons.Tuple(T, ...). 611 */ 612 auto asTuple(T, size_t n)(ref T[n] arr) 613 { 614 import std.typecons : Tuple; 615 return Tuple!(genTypeList!(T, n))(arr); 616 } 617 618 /** Return: Adjacent $(D N)-Tuples of $(D r). 619 * 620 * TODO: Support ref return via $(D zip) for non-const case. 621 * TODO Use a ring buffer instead of copy? 622 * TODO Add a variant of adjacentTuples that return a static array instead? 623 * See_Also: http://forum.dlang.org/post/gkdqakdogqevwzntpgtu@forum.dlang.org 624 */ 625 auto adjacentTuples(size_t N, R)(R r) 626 if (N >= 2 && 627 isInputRange!R) 628 { 629 struct Range(R) 630 { 631 import core.internal.traits : Unqual; 632 import std.typecons : Tuple; 633 alias E = Unqual!(ElementType!R); 634 enum M = N - 1; // temporary order 635 alias P = E[M]; 636 alias T = Tuple!(genTypeList!(E, N)); // TODO functionize 637 638 this(R r) 639 { 640 this._source = r; 641 foreach (i; 0 .. M) 642 { 643 if (!empty) 644 popFront; 645 } 646 } 647 648 static if (isInfinite!R) 649 { 650 enum bool empty = false; // propagate infiniteness 651 } 652 else 653 { 654 bool empty() @property // TODO can't empty be const when R is a MapResult? 655 { 656 import std.range.primitives : empty; 657 return _source.empty; 658 } 659 } 660 661 auto ref front() @property 662 { 663 import std.range.primitives : front; 664 T t; 665 t[0 .. M] = _prevs.asTuple; 666 t[M] = _source.front; 667 return t; 668 } 669 670 void popFront() 671 { 672 static if (N >= 3) 673 { 674 // TODO use static foreach to do left-shifting 675 676 // Need $(D copy) because $(D source) and $(D dest) may overlap. 677 // See_Also: http://dlang.org/arrays.html#overlapping-copying 678 import std.algorithm.mutation : copy; 679 copy(_prevs[1 .. M], _prevs[0 .. M - 1]); 680 } 681 682 import std.range.primitives : front, popFront; 683 _prevs[M - 1] = _source.front; 684 _source.popFront(); 685 } 686 687 private: 688 P _prevs; 689 R _source; 690 } 691 return Range!R(r); 692 } 693 694 auto adjacentPairs(R)(R r) 695 if (isInputRange!R) 696 { 697 return adjacentTuples!(2, R)(r); 698 } 699 700 auto adjacentTriples(R)(R r) 701 if (isInputRange!R) 702 { 703 return adjacentTuples!(3, R)(r); 704 } 705 706 /// 707 @safe pure nothrow @nogc unittest 708 { 709 import std.typecons : t = tuple; 710 import std.algorithm : equal, map; 711 auto x = [1, 2, 3, 4, 5, 6, 7].s[].map!(a => a); // test with ForwardRange 712 auto y = x.adjacentTuples!4; 713 assert(y.equal([t(1, 2, 3, 4), 714 t(2, 3, 4, 5), 715 t(3, 4, 5, 6), 716 t(4, 5, 6, 7)].s[])); 717 } 718 719 /// 720 @safe pure nothrow @nogc unittest 721 { 722 import std.typecons : t = tuple; 723 import std.algorithm : equal; 724 immutable x = [1, 2, 3, 4].s; 725 auto y = x[].adjacentPairs; 726 assert(y.equal([t(1, 2), t(2, 3), t(3, 4)].s[])); 727 } 728 729 /// 730 @safe pure nothrow @nogc unittest 731 { 732 import std.typecons : t = tuple; 733 import std.algorithm : equal; 734 auto x = ["1", "2", "3", "4"].s; 735 auto y = x[].adjacentPairs; 736 assert(y.equal([t("1", "2"), t("2", "3"), t("3", "4")].s[])); 737 } 738 739 /// 740 @safe pure nothrow @nogc unittest 741 { 742 import std.typecons : t = tuple; 743 import std.algorithm : equal; 744 immutable x = ["1", "2", "3", "4"].s; 745 auto y = x[].adjacentPairs; 746 assert(y.equal([t("1", "2"), t("2", "3"), t("3", "4")].s[])); 747 } 748 749 auto rangify(T)(T range) 750 if (__traits(hasMember, T, "length") && 751 __traits(hasMember, T, "opIndex")) 752 { 753 struct Range 754 { 755 bool empty() { return _counter == range.length; } 756 auto front() { return range[_counter]; } 757 auto popFront() { _counter++; } 758 T range; 759 ulong _counter; 760 } 761 return Range(range); 762 } 763 764 struct S 765 { 766 int[] arr; 767 auto length() { return arr.length; } 768 int opIndex(size_t i) { return arr[i]; } 769 } 770 771 unittest 772 { 773 import std.algorithm : equal; 774 auto s = S(); 775 s.arr = [1, 2, 3]; 776 assert(s.rangify.equal([1, 2, 3].s[])); 777 } 778 779 /** Overload has questionable memory safety. Would be quite cool if DIP-1000 780 * could support this use case 781 * 782 * See_Also: http://forum.dlang.org/post/qgrbmkqxffgeiqaigdic@forum.dlang.org 783 */ 784 auto staticLengthRange(T, size_t n)(ref T[n] arr) 785 { 786 return .staticLengthRange!(n, T[])(arr[]); // TODO DIP-1000 scope 787 } 788 789 import std.range.primitives : hasLength, isInputRange; 790 791 auto staticLengthRange(size_t n, R)(R range) 792 if (isInputRange!R && hasLength!R) 793 { 794 static struct Result 795 { 796 enum size_t length = n; 797 R _range; 798 alias _range this; 799 } 800 assert (range.length == n); 801 return Result(range); 802 } 803 804 805 @safe pure nothrow @nogc unittest 806 { 807 import std.algorithm.iteration : map; 808 809 int[3] sarr = [1, 2, 3]; 810 auto r1 = sarr.staticLengthRange; 811 static assert (isInputRange!(typeof(r1))); 812 static assert (r1.length == 3); 813 814 auto arr = [1, 2, 3, 4].s; 815 auto r2 = arr[].map!(a => a * 2).staticLengthRange!4; 816 static assert (r2.length == 4); 817 } 818 819 /** Given a `SortedRange` R, `sortingPredicate!R(a, b)` will call in to the 820 * predicate that was used to create the `SortedRange`. 821 * 822 * Params: 823 * Range = the range to extract the predicate from 824 * fallbackPred = the sorting predicate to fallback to if `Range` is not a `SortedRange` 825 */ 826 template sortingPredicate(Range, alias fallbackPred = "a < b") 827 if (isInputRange!Range) 828 { 829 import std.range : SortedRange; 830 import std.functional : binaryFun; 831 static if (is(Range : SortedRange!P, P...)) 832 { 833 alias sortingPredicate = binaryFun!(P[1]); 834 } 835 else 836 { 837 alias sortingPredicate = binaryFun!fallbackPred; 838 } 839 } 840 841 /// 842 unittest 843 { 844 import std.algorithm : sort; 845 assert(sortingPredicate!(typeof([1].sort!"a < b"))(1, 2) == true); 846 assert(sortingPredicate!(typeof([1].sort!"a > b"))(1, 2) == false); 847 assert(sortingPredicate!(typeof([1].sort!((a, b) => a < b)))(1, 2) == true); 848 assert(sortingPredicate!(typeof([1].sort!((a, b) => a > b)))(1, 2) == false); 849 assert(sortingPredicate!(int[])(1, 2) == true); 850 } 851 852 /** Faster than std.range.zip on DMD. 853 * 854 * See_Also: https://forum.dlang.org/post/khvwfwvjiblobfybsurd@forum.dlang.org 855 */ 856 auto zip(R1, R2)(R1 r1, R2 r2) 857 if (isRandomAccessRange!R1 && 858 isRandomAccessRange!R2) 859 { 860 static struct Result(R1, R2) 861 { 862 size_t _length; 863 864 this(R1 r1, R2 r2) 865 { 866 _r1 = r1; 867 _r2 = r2; 868 import std.algorithm.comparison : min; 869 _length = min(r1.length, r2.length); 870 } 871 872 @property auto front() 873 { 874 import std.typecons : tuple; 875 return tuple(_r1[_index], 876 _r2[_index]); 877 } 878 879 @property bool empty() const @safe pure nothrow @nogc 880 { 881 return _index == _length; 882 } 883 884 @property size_t length() const @safe pure nothrow @nogc 885 { 886 return _length; 887 } 888 889 void popFront() @safe pure nothrow @nogc 890 { 891 assert(!empty); 892 _index += 1; 893 } 894 895 private: 896 size_t _index = 0; 897 R1 _r1; 898 R2 _r2; 899 } 900 return Result!(R1,R2)(r1,r2); 901 } 902 903 @safe pure nothrow @nogc unittest 904 { 905 import nxt.array_help : s; 906 import std.algorithm.comparison : equal; 907 import std.typecons : tuple; 908 const r1 = [1, 2, 3].s; 909 const r2 = [4, 5, 6, 7].s; 910 auto r12 = zip(r1[], r2[]); 911 assert(r12.equal([tuple(1, 4), 912 tuple(2, 5), 913 tuple(3, 6)].s[])); 914 }