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