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.traits: isInstanceOf, Unqual;
500 import std.range: SortedRange;
501 import std.meta: allSatisfy, staticMap;
502 
503 /// Is the `CommonType` of the `ElementType`s of the ranges `Rs`.
504 template CommonElementType(Rs...)
505 {
506     import std.traits: CommonType;
507     import std.range: ElementType;
508     alias CommonElementType = CommonType!(staticMap!(ElementType, Rs));
509 }
510 
511 ///
512 @safe pure nothrow @nogc unittest
513 {
514     static assert(is(CommonElementType!(int[], double[]) == double));
515 }
516 
517 enum bool haveCommonElementType(Types...) = !is(CommonElementType!Types == void);
518 
519 /// Is `true` iff the ranges `Rs` have a common element type.
520 @safe pure nothrow @nogc unittest
521 {
522     static assert(haveCommonElementType!(bool[], int[]));
523     static assert(!haveCommonElementType!(bool[], int[], string[]));
524 }
525 
526 alias isSortedRange(R) = isInstanceOf!(SortedRange, R); // TODO: Or use: __traits(isSame, TemplateOf!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 }