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