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