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