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