1 /** Extensions to std.algorithm.
2     Copyright: Per Nordlöw 2022-.
3     License: $(WEB boost.org/LICENSE_1_0.txt, Boost License 1.0).
4     Authors: $(WEB Per Nordlöw)
5 
6     TODO: merge stuff from algorithm_ex_2.d
7 */
8 module nxt.algorithm_ex;
9 
10 /* version = show; */
11 
12 import std.traits : isArray, Unqual, isIntegral, CommonType, arity, isSomeString, isExpressionTuple;
13 import std.range.primitives : ElementType, isInputRange, isForwardRange, isBidirectionalRange, isRandomAccessRange, front;
14 import nxt.traits_ex : allSame;
15 import std.functional : binaryFun;
16 import std.algorithm.searching : find;
17 
18 version(show)
19 {
20     import nxt.dbgio : dbg;
21 }
22 
23 import std.range : dropOne;
24 alias tail = dropOne;
25 
26 /** This overload enables, when possible, lvalue return.
27  *
28  * BUG: this overload is not chosen over `std.algorithm.either` so function
29  * must currently be called `eitherRef` instead of `either`.
30  */
31 ref Ts[0] eitherRef(Ts...)(ref Ts a)
32 if (a.length != 0 &&
33     allSame!Ts)         // TODO: better trait for this?
34 {
35     static if (Ts.length == 1)
36         return a[0];
37     else
38         return a[0] ? a[0] : eitherRef(a[1 .. $]); // recurse
39 }
40 
41 ///
42 @safe pure /*TODO: nothrow*/ unittest
43 {
44     int x = 1, y = 2;
45     eitherRef(x, y) = 3;
46     assert(x == 3);
47     assert(y == 2);
48 }
49 
50 /** Returns: last argument if all arguments implicitly bool-convert to `true`
51  * otherwise `CommonType!T.init`.
52  *
53  * Similar to behaviour of `and` operator in dynamic languages such as of Lisp's
54  * `(and a...)` and Python's `a and ....`.
55  *
56  * TODO: Is inout Conversion!T the correct return value?
57 */
58 CommonType!T every(T...)(lazy T a)
59 if (T.length != 0)
60 {
61     auto a0 = a[0]();           // evaluate only once
62     static if (T.length == 1)
63         return a0;
64     else
65         return a0 ? every(a[1 .. $]) : CommonType!T.init; // recurse
66 }
67 
68 ///
69 @safe pure /*TODO: nothrow*/ unittest
70 {
71     assert(every(3) == 3);
72     assert(every(3, 4) == 4);
73     assert(every(0, 4) == 0);
74     assert(every(0, 0) == 0);
75     assert(every([0, 1], [1, 2]) == [1, 2]);
76     assert(every([0, 1], [1]) == [1]);
77     assert(every(`a`, `b`) == `b`);
78     assert(every(``, `b`) == `b`);
79     assert(every(cast(string)null, `b`) == cast(string)null);
80 }
81 
82 version(none) // WARNING disabled because I don't see any use of this for.
83 {
84     /** This overload enables, when possible, lvalue return.
85     */
86     auto ref every(T...)(ref T a)
87     if (T.length != 0 && allSame!T)
88     {
89         static if (T.length == 1)
90             return a[0];
91         else
92             return a[0] ? every(a[1 .. $]) : a[0]; // recurse
93     }
94 
95     ///
96     unittest
97     {
98         immutable p = 1, q = 2;
99         assert(every(p, q) == 2);
100 
101         int x = 1, y = 2;
102         every(x, y) = 3;
103         assert(x == 1);
104         assert(y == 3);
105     }
106 }
107 
108 /** Evaluate all `parts` possibly digesting `whole`.
109  *
110  * If all values of `parts` implicitly convert to `bool true`, return the values
111  * as an array, otherwise restore whole and return null.
112  */
113 CommonType!T[] tryEvery(S, T...)(ref S whole, lazy T parts)
114 if (T.length != 0)
115 {
116     auto wholeBackup = whole;
117     bool all = true;
118     alias R = typeof(return);
119     R results;
120     foreach (result; parts) // evaluate each part
121     {
122         if (result)
123             results ~= result;
124         else
125         {
126             all = false;
127             break;
128         }
129     }
130     if (all)
131         return results;        // ok that whole has been changed in caller scope
132     else
133     {
134         whole = wholeBackup; // restore whole in caller scope if any failed
135         return R.init;
136     }
137 }
138 
139 ///
140 unittest
141 {
142     auto whole = `xyz`;
143     import std.algorithm.searching : skipOver;
144 
145     assert(whole.tryEvery(whole.skipOver('x'),
146                           whole.skipOver('z')) == []); // failing match
147     assert(whole == `xyz`); // should restore whole
148 
149     assert(whole.tryEvery(whole.skipOver('x'),
150                           whole.skipOver('y'),
151                           whole.skipOver('w')) == []); // failing match
152     assert(whole == `xyz`); // should restore whole
153 
154     assert(whole.tryEvery(whole.skipOver('x'),
155                           whole.skipOver('y')) == [true, true]); // successful match
156     assert(whole == `z`); // should digest matching part
157 }
158 
159 /** Returns: `true` iff `a` has a value containing meaningful information.
160  */
161 bool hasContents(T)(in T a)
162 {
163 	import std.typecons : Nullable;
164     static if (is(T == Nullable!(_), _))
165         return !a.isNull;
166     else static if (isArray!T ||
167                     isSomeString!T)
168         return cast(bool)a.length; // see: http://stackoverflow.com/questions/18563414/empty-string-should-implicit-convert-to-bool-true/18566334?noredirect=1#18566334
169     else
170         return cast(bool)a;
171 }
172 
173 /** Reset `a` to its default value.
174  *
175  * Params:
176  *      T = the argument type, likely to be infered.
177  *      a = a reference to a T.
178  *
179  * See_Also: std.typecons.Nullable.nullify
180  */
181 auto ref reset(T)(ref T a) @trusted // pure nothrow
182 {
183 	import std.typecons : Nullable;
184     static if (is(T == Nullable!(_), _))
185         a.nullify();
186     else
187         return a = T.init;
188 }
189 
190 ///
191 @safe @nogc pure nothrow unittest
192 {
193     uint a = 159;
194     a.reset();
195     assert(a == a.init);
196 
197     string b = "bla";
198     b.reset();
199     assert(b == b.init);
200 }
201 
202 /** Returns: `true` iff $(D a) is set to the default/initial value of its type $(D T).
203  */
204 bool isDefaulted(T)(in T a)
205 {
206     static if (__traits(hasMember, T, "isNull"))
207         return a.isNull;		// Nullable
208     else
209         return a == T.init;
210 }
211 
212 ///
213 @safe pure nothrow @nogc unittest
214 {
215     import std.typecons : Nullable;
216     auto n = Nullable!(size_t, size_t.max)();
217     assert(n.isDefaulted);
218     n = 0;
219     assert(!n.isDefaulted);
220     assert(n == 0);
221     n.reset;
222     assert(n.isDefaulted);
223 }
224 
225 import std.typecons : Tuple, tuple;
226 
227 /** Find `needles` in order in `haystack`. */
228 auto findInOrder(alias pred = `a == b`,
229                  alias finder = find,
230                  R,
231                  E...)(R haystack,
232                        E needles) /* @trusted pure nothrow */
233 {
234     import std.range.primitives : empty;
235     auto hit = haystack; // reference
236     foreach (needle; needles) // for every needle in order
237     {
238         hit = finder!pred(hit, needle);
239         if (hit.empty)
240             break;
241     }
242     return hit;
243 }
244 
245 ///
246 @safe pure nothrow @nogc unittest
247 {
248     import std.range.primitives : empty;
249     assert(`a b c`.findInOrder(`a`, `b`, `c`));
250     assert(`b a`.findInOrder(`a`, `b`).empty);
251 }
252 
253 /** Returns: If `range` is symmetric.
254  *
255  * See_Also: http://forum.dlang.org/thread/dlfeiszyweafpjiocplf@forum.dlang.org#post-vpzuaqxvtdpzpeuorxdl:40forum.dlang.org
256  * See_Also: https://stackoverflow.com/questions/21849580/equality-operator-in-favour-of-std-range-equal
257  *
258  * TODO: Test graphemes in `string` and `wstring`.
259  * TODO: Move to Phobos
260 */
261 bool isSymmetric(R)(R range)
262 if (isBidirectionalRange!(R))
263 {
264     size_t i = 0;
265     import std.range.primitives : empty;
266     while (!range.empty)
267     {
268         import std.range.primitives : front, back, popFront, popBack;
269         if (range.front != range.back)
270             return false;
271         range.popFront(); ++i;
272         if (range.empty)
273             break;
274         range.popBack(); ++i;
275     }
276     return true;
277 }
278 
279 ///
280 @safe pure unittest
281 {
282     assert(`dallassallad`.isSymmetric);
283     assert(!`ab`.isSymmetric);
284     assert(`a`.isSymmetric);
285     assert(`åäå`.isSymmetric);
286     assert(`áá`.isSymmetric);
287     assert(`åäå`.isSymmetric);
288     assert(``.isSymmetric);
289     assert([1, 2, 2, 1].isSymmetric);
290 }
291 
292 /** Returns: If `range` is a palindrome larger than `minLength`.
293  */
294 bool isPalindrome(R)(R range,
295                      size_t minLength = 0) // TODO: good value for minLength?
296 if (isBidirectionalRange!(R))
297 {
298     static if (isRandomAccessRange!R) // arrays excluding `char[]` and `wchar[]`
299         if (range.length < minLength)
300             return false;
301     size_t i = 0;
302     // TODO: reuse `isSymmetric`
303     import std.range.primitives : empty;
304     while (!range.empty)
305     {
306         import std.range.primitives : front, back, popFront, popBack;
307         if (range.front != range.back)
308             return false;
309         range.popFront(); ++i;
310         if (range.empty)
311             break;
312         range.popBack(); ++i;
313     }
314     return i >= minLength;
315 }
316 
317 ///
318 @safe pure unittest
319 {
320     assert(`dallassallad`.isPalindrome);
321     assert(!`ab`.isPalindrome);
322     assert(`a`.isPalindrome);
323     assert(`åäå`.isPalindrome);
324     assert(`áá`.isPalindrome);
325     assert(`åäå`.isPalindrome);
326     assert(``.isPalindrome);
327     assert([1, 2, 2, 1].s[].isPalindrome);
328 }
329 
330 import nxt.traits_ex : areEquable;
331 
332 /** Return true if $(D s1) is an Anagram of $(D s2).
333  *
334  * Equal arguments are not considered to be an anagrams of each other.
335  *
336  * TODO: Is there a faster way of calculating anagrams?
337  * TODO: Allow const input
338  * TODO: Move to Phobos std.algorithm.sorting.
339  * TODO: Should requirement isInputRange be relaxed?
340  *
341  * Note that implementations in http://rosettacode.org/wiki/Anagrams#D doesn't
342  * correctly handle multi-byte encoded characters in string and wstring.
343  */
344 auto isAnagramOf(R1, R2)(scope R1 r1, scope R2 r2) // TODO: nothrow
345 if (isInputRange!R1 &&
346     isInputRange!R2 &&
347     areEquable!(ElementType!R1,
348                 ElementType!R2))
349 {
350     immutable sortLimit = 0;
351     import std.range.primitives : empty;
352     if (r1.empty || r2.empty)
353         return false;
354     if (r1.length + r2.length < sortLimit)
355     {
356         import std.algorithm.comparison : equal;
357         import nxt.sort_ex : sorted; // NOTE allocates
358         return equal(r1.sorted,
359                      r2.sorted);
360     }
361     else
362     {
363         alias E1 = ElementType!R1;
364         alias E2 = ElementType!R2;
365         alias C = CommonType!(E1, E2);
366 
367         alias T = Tuple!(size_t, // R1 histogram bin count
368                          size_t); // R2 histogram bin count
369 
370         import std.traits : isNarrowString;
371         import std.utf : byUTF;
372 
373         // TODO: functionize
374         static if (isNarrowString!R1)
375             auto s1 = r1.byUTF!dchar;
376         else
377             auto s1 = r1; // TODO: avoid copying
378 
379         static if (isNarrowString!R2)
380             auto s2 = r2.byUTF!dchar;
381         else
382             auto s2 = r2; // TODO: avoid copying
383 
384         // histogram
385         T[C] hist;              // TODO: use non-GC-allocating AA
386 
387         // create histograms
388         foreach (const ref e1; s1)
389         {
390             // TODO: functionize to hist.initOrUpdate(e1, T(0,1), (ref AA aa){ aa[0] += 1; })
391             if (auto hit = e1 in hist)
392                 (*hit)[0] += 1;
393             else
394                 hist[e1] = T(1, 0);
395         }
396         foreach (const ref e2; s2)
397         {
398             // TODO: functionize to hist.initOrUpdate(e2, T(0,1), (ref AA aa){ aa[1] += 1; })
399             if (auto hit = e2 in hist)
400                 (*hit)[1] += 1;
401             else
402                 hist[e2] = T(0, 1);
403         }
404 
405         // check histograms
406         foreach (const ref e; hist) // TODO: nothrow
407             if (e[0] != e[1])
408                 return false;
409         return true;
410     }
411 }
412 alias isPermutationOf = isAnagramOf; // TODO: Only define isAnagramOf for strings?
413 
414 ///
415 @safe pure unittest
416 {
417     assert([1, 2, 3, 4, 5].s[].isPermutationOf([1, 2, 4, 5, 3].s[]));
418     assert(![1, 2, 3, 4, 5].s[].isPermutationOf([1, 4, 5, 3].s[]));
419 }
420 
421 ///
422 @safe pure unittest
423 {
424     assert(!``w.isAnagramOf(``));
425     assert(`äöå`w.isAnagramOf(`åäö`));
426     assert(`äöå`.isAnagramOf(`åäö`w));
427     assert(`äöå`w.isAnagramOf(`åäö`w));
428 
429     assert(`äöå`d.isAnagramOf(`åäö`));
430     assert(`äöå`.isAnagramOf(`åäö`d));
431     assert(`äöå`d.isAnagramOf(`åäö`d));
432 }
433 
434 ///
435 @safe pure unittest
436 {
437     assert(`äöå`.isAnagramOf(`åäö`));
438     assert(!`äöå`.isAnagramOf(`xyz`));
439     assert(!`äöå`.isAnagramOf(``));
440     assert(!``.isAnagramOf(`åäö`));
441 }
442 @safe pure unittest
443 {
444     assert(`xyzxyzxyzxyzxyzxyzxyzxyz`.isAnagramOf(`xyzxyzxyzxyzxyzxyzxyzxyz`));
445     assert(`xyzxyzxyzxyzxyzxyzxyzxyz`.isAnagramOf(`xxyyzzxxyyzzxxyyzzxxyyzz`));
446 }
447 
448 ///
449 @safe pure unittest
450 {
451     import std.conv: to;
452 
453     auto x = `äöå`.to!(dchar[]);
454 
455     auto y = sort(x);
456     alias Y = typeof(y);
457 
458     immutable z = `åäö`;
459 
460     assert(y.isAnagramOf(z));
461     assert(z.isAnagramOf(y));
462 }
463 
464 /* ref Unqual!T unqual(T)(in T x) pure nothrow if isStuct!T { return cast(Unqual!T)x; } */
465 /* /// */
466 /* unittest { */
467 /*     const int x; */
468 /*     unqual(x) = 1; */
469 /* } */
470 
471 enum Reduction
472 {
473     forwardDifference,
474     backwardDifference,
475     sum,
476 }
477 
478 /** Generalized Windowed Reduce.
479  *
480  * See_Also: https://stackoverflow.com/questions/21004944/forward-difference-algorithm
481  * See_Also: http://forum.dlang.org/thread/ujouqtqeehkegmtaxebg@forum.dlang.org#post-lczzsypupcfigttghkwx:40forum.dlang.org
482  * See_Also: http://rosettacode.org/wiki/Forward_difference#D
483 */
484 auto ref windowedReduce(Reduction reduction = Reduction.forwardDifference, R)(R range)
485 if (isInputRange!R)
486 {
487     import std.algorithm.iteration: map;
488     import std.range: zip, dropOne;
489     auto ref op(T)(T a, T b) @safe pure nothrow
490     {
491         static      if (reduction == Reduction.forwardDifference)
492             return b - a; // TODO: final static switch
493         else static if (reduction == Reduction.backwardDifference)
494             return a - b;
495         else static if (reduction == Reduction.sum)
496             return a + b;
497     }
498     return range.zip(range.dropOne).map!(a => op(a[0], a[1])); // a is a tuple here
499 }
500 // NOTE: Disabled for now because this solution cannot be made nothrow
501 /* auto ref windowedReduce(Reduction reduction = Reduction.forwardDifference, uint N = 0, R)(R range) */
502 /*     @safe pure /\* nothrow *\/ if (isInputRange!R) */
503 /* { */
504 /*     auto ref helper(R range) @safe pure /\* nothrow *\/ { */
505 /*         import std.algorithm.iteration: map; */
506 /*         import std.range: zip, dropOne; */
507 /*         //  Note: that a[0] and a[1] indexes Zip tuple */
508 /*         static if (reduction == Reduction.forwardDifference) */
509 /*             return range.zip(range.dropOne).map!(a => a[1] - a[0]); */
510 /*         static if (reduction == Reduction.backwardDifference) */
511 /*             return range.zip(range.dropOne).map!(a => a[0] - a[1]); */
512 /*         static if (reduction == Reduction.sum) */
513 /*             return range.zip(range.dropOne).map!(a => a[0] + a[1]); */
514 /*     } */
515 /*     static if (N != 0) { */
516 /*         return windowedReduce!(reduction, N - 1)(helper(range)); */
517 /*     } else { */
518 /*         return helper(range); */
519 /*     } */
520 /* } */
521 
522 /* /// */
523 /* unittest { */
524 /*     import std.range: front; */
525 /*     dbg([1].windowedReduce!(Reduction.forwardDifference)); */
526 /*     dbg([1, 22].windowedReduce!(Reduction.forwardDifference)); */
527 /*     dbg([1, 22, 333].windowedReduce!(Reduction.forwardDifference)); */
528 /* } */
529 
530 ///
531 unittest
532 {
533     import std.datetime : Clock, SysTime;
534     SysTime[] times;
535     immutable n = 4;
536     foreach (_; 0 .. n)
537         times ~= Clock.currTime;
538     version(show) dbg(times);
539     auto spans = times.windowedReduce!(Reduction.forwardDifference);
540     version(show) dbg(spans);
541     // dbg(*(cast(ulong*)&(spans.front)));
542     version(show) import std.datetime : Duration;
543     version(show) dbg(Duration.sizeof);
544 }
545 
546 ///
547 @safe pure unittest
548 {
549     immutable i = [1, 4, 9, 17];
550     assert(i.windowedReduce!(Reduction.forwardDifference).equal ([+3, +5, +8]));
551     assert(i.windowedReduce!(Reduction.backwardDifference).equal([-3, -5, -8]));
552     assert(i.windowedReduce!(Reduction.sum).equal ([+5, +13, +26]));
553     assert([1].windowedReduce.empty);
554     version(show) dbg(i.windowedReduce!(Reduction.sum));
555 }
556 
557 /* TODO: Assert that ElementType!R only value semantics.  */
558 auto ref packBitParallelRunLengths(R)(in R x)
559 if (isInputRange!R)
560 {
561     import std.bitmanip: BitArray;
562     import core.bitop: bt;
563     alias E = ElementType!R; // element type
564     enum nBits = 8*E.sizeof;
565 
566     BitArray[nBits] runs;
567 
568     // allocate runs
569     foreach (ref run; runs)
570         run.length = x.length;
571 
572     /* string toString() @property @trusted const { */
573     /*     typeof(return) y; */
574     /*     import std.conv: to; */
575     /*     foreach (run; runs) { */
576     /*         y ~= run.to!string ~ "\n"; */
577     /*     } */
578     /*     return y; */
579     /* } */
580 
581     /* size_t[nBits] counts; */
582 
583     import nxt.container.static_bitarray : StaticBitArray;
584     import nxt.bitop_ex: testBit;
585     foreach (eltIx, elt; x)
586         /* StaticBitArray!nBits bits; */
587         foreach (bitIndex; 0..nBits)
588             runs[bitIndex][eltIx] = elt.testBit(bitIndex);
589     return runs;
590 }
591 alias packBPRL = packBitParallelRunLengths;
592 
593 ///
594 pure unittest
595 {
596     /* import backtrace.backtrace; */
597     /* import std.stdio: stderr; */
598     /* backtrace.backtrace.install(stderr); */
599     immutable x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
600     immutable xPacked = x.packBitParallelRunLengths;
601     version(show) dbg(xPacked);
602 }
603 
604 /** Compute Forward Difference of `range`.
605  *
606  * TODO: Is there a difference between whether `R r` is immutable, const or
607  * mutable?
608  *
609  * TODO: If `r` contains only one element return empty range.
610  *
611  * See_Also: https://stackoverflow.com/questions/21004944/forward-difference-algorithm
612  * See_Also: http://forum.dlang.org/thread/ujouqtqeehkegmtaxebg@forum.dlang.org#post-lczzsypupcfigttghkwx:40forum.dlang.org
613  * See_Also: http://rosettacode.org/wiki/Forward_difference#D
614  */
615 auto forwardDifference(R)(R r)
616 if (isInputRange!R)
617 {
618     import std.range: front, empty, popFront, dropOne;
619 
620     struct ForwardDifference
621     {
622         R _range;
623         alias E = ElementType!R;                       // Input ElementType
624         alias D = typeof(_range.front - _range.front); // Element Difference Type. TODO: Use this as ElementType of range
625         D _front;
626         bool _initialized = false;
627 
628         this(R range)
629         in(!range.empty)
630         {
631             auto tmp = range;
632             if (tmp.dropOne.empty) // TODO: This may be an unneccesary cost but is practical to remove extra logic
633                 _range = R.init; // return empty range
634             else
635                 _range = range; // store range internally (by reference)
636         }
637 
638         @property:
639         auto ref front()
640         {
641             if (!_initialized)
642                 popFront();
643             return _front;
644         }
645         auto ref moveFront()
646         {
647             popFront();
648             return _front;
649         }
650         void popFront()
651         {
652             if (empty is false)
653             {
654                 _initialized = true;
655                 E rf = _range.front;
656                 _range.popFront();
657                 if (_range.empty is false)
658                     _front = _range.front - rf;
659             }
660         }
661         bool empty() => _range.empty;
662     }
663 
664     return ForwardDifference(r);
665 }
666 
667 ///
668 unittest
669 {
670     auto x = [long.max, 0, 1];
671     auto y = x.forwardDifference;
672     version(show) dbg(y);
673 }
674 
675 import std.traits: isCallable, ReturnType, arity;
676 import nxt.traits_ex: arityMin0;
677 
678 /** Create Range of Elements Generated by `fun`.
679  *
680  * Use for example to generate random instances of return value of fun.
681  *
682  * TODO: I believe we need arityMin, arityMax trait here
683  */
684 auto apply(alias fun, N)(N n)
685 if (// TODO: isCallable!fun &&
686         arityMin0!fun &&
687         !is(ReturnType!fun == void) &&
688         isIntegral!N)
689 {
690     import std.range: iota;
691     import std.algorithm.iteration: map;
692     return n.iota.map!(n => fun);
693 }
694 
695 ///
696 unittest
697 {
698     import std.datetime: Clock;
699     import std.array: array;
700     immutable n = 3;
701     auto times = n.apply!(Clock.currTime).array;
702     version(show) dbg(times);
703     auto spans = times.forwardDifference;
704     version(show) dbg(spans);
705 }
706 
707 /** In Place Ordering (in Sorted Order) of all Elements `t`.
708  *
709  * See_Also: https://stackoverflow.com/questions/21102646/in-place-ordering-of-elements/
710  * See_Also: http://forum.dlang.org/thread/eweortsmcmibppmvtriw@forum.dlang.org#post-eweortsmcmibppmvtriw:40forum.dlang.org
711 */
712 void orderInPlace(T...)(ref T t) @trusted
713 {
714     import std.algorithm: sort, swap;
715     static if (t.length == 2)
716     {
717         if (t[0] > t[1])
718             swap(t[0], t[1]);
719     }
720     else
721     {                           // generic version
722         T[0][T.length] buffer;      // static buffer to capture elements
723         foreach (idx, a; t)
724             buffer[idx] = a;
725         auto sorted = sort(buffer[]);
726         foreach (idx, a; t)
727             t[idx] = sorted[idx];
728     }
729 }
730 
731 ///
732 unittest
733 {
734     auto x = 2, y = 1;
735     orderInPlace(x, y);
736     assert(x == 1);
737     assert(y == 2);
738 }
739 
740 ///
741 unittest
742 {
743     auto x = 3, y = 1, z = 2;
744     orderInPlace(x, y, z);
745     assert(x == 1);
746     assert(y == 2);
747     assert(z == 3);
748 }
749 
750 import std.algorithm: SwapStrategy;
751 
752 /** Allow static arrays to be sorted without [].
753  *
754  * See_Also: http://forum.dlang.org/thread/jhzurojjnlkatjdgcfhg@forum.dlang.org
755  */
756 template sort(alias less = `a < b`, SwapStrategy ss = SwapStrategy.unstable)
757 {
758     import std.algorithm: stdSort = sort;
759     auto sort(Arr)(ref Arr arr)
760     if (__traits(isStaticArray, Arr))
761     {
762         return stdSort!(less, ss)(arr[]);
763     }
764     auto sort(Range)(Range r)
765     if (!__traits(isStaticArray, Range))
766     {
767         return stdSort!(less, ss)(r);
768     }
769 }
770 
771 ///
772 unittest
773 {
774     int[5] a = [ 9, 5, 1, 7, 3 ];
775     int[]  b = [ 4, 2, 1, 6, 3 ];
776     sort(a);
777     sort(b);
778 }
779 
780 /** Stable Variant of Quick Sort.
781  *
782  * See_Also: http://forum.dlang.org/thread/gjuvmrypvxeebvztszpr@forum.dlang.org
783  */
784 auto ref stableSort(T)(auto ref T a) pure
785 if (isRandomAccessRange!T)
786 {
787     if (a.length >= 2)
788     {
789         import std.algorithm: partition3, sort;
790         auto parts = partition3(a, a[$ / 2]); // mid element as pivot
791         parts[0].sort();
792         parts[2].sort();
793     }
794     return a;
795 }
796 
797 ///
798 unittest
799 {
800     import nxt.random_ex : randInPlace;
801     immutable n = 2^^16;
802     auto a = new int[n];
803     a.randInPlace();
804     auto b = a.dup;
805     a[].stableSort;
806     import std.algorithm: sort;
807     sort(b);
808     assert(a == b);
809 }
810 
811 /** Execute Expression `expr` the same way `n` times. */
812 void doTimes(uint n, lazy void expr)
813 {
814     while (n--)
815         expr();
816 }
817 
818 /** Execute Expression `expr` $(I inline) the same way `n` times.
819     `n` must be a constant known at compile time.
820 */
821 void doTimes(uint n)(lazy void expr)
822 {
823     static foreach (i; 0 .. n)
824         expr();
825 }
826 
827 ///
828 unittest
829 {
830     int i = 0;
831     doTimes!4(i++);
832     assert(i == 4);
833 }
834 
835 alias loop = doTimes;
836 alias doN = doTimes;
837 alias repeat = doTimes;
838 
839 /** Execute Expression `action` the same way `n` times. */
840 void times(alias action, N)(N n)
841 if (isCallable!action &&
842     isIntegral!N &&
843     arity!action <= 1)
844 {
845     import std.traits: ParameterTypeTuple;
846     static if (arity!action == 1 && // if one argument and
847                isIntegral!(ParameterTypeTuple!action[0])) // its an integer
848         foreach (i; 0 .. n)
849             action(i); // use it as action input
850     else
851         foreach (i; 0 .. n)
852             action();
853 }
854 
855 ///
856 unittest
857 {
858     enum n = 10;
859     int sum = 0;
860     10.times!({ sum++; });
861     assert(sum == n);
862 }
863 
864 private string genNaryFun(string fun, V...)()
865 {
866     string code;
867     import std.string : format;
868     foreach (n, v; V)
869         code ~= "alias values[%d] %s;".format(n, cast(char)('a'+n));
870     code ~= `return ` ~ fun ~ `;`;
871     return code;
872 }
873 template naryFun(string fun)
874 {
875     auto naryFun(V...)(V values)
876     {
877         mixin(genNaryFun!(fun, V));
878     }
879 }
880 
881 ///
882 unittest
883 {
884     alias test = naryFun!`a + b + c`;
885     assert(test(1, 2, 3) == 6);
886 }
887 
888 import std.meta : allSatisfy;
889 
890 /** Zip `ranges` together with operation `fun`.
891  *
892  * TODO: Remove when Issue 8715 is fixed providing zipWith
893  */
894 auto zipWith(alias fun, Ranges...)(Ranges ranges)
895 if (Ranges.length >= 2 &&
896     allSatisfy!(isInputRange, Ranges))
897 {
898     import std.range: zip;
899     import std.algorithm.iteration: map;
900     static if (ranges.length < 2)
901         static assert(0, `Need at least 2 range arguments.`);
902     else static if (ranges.length == 2)
903         return zip(ranges).map!(a => binaryFun!fun(a.expand));
904     else
905         return zip(ranges).map!(a => naryFun!fun(a.expand));
906     // return zip(ranges).map!(a => fun(a.expand));
907 }
908 
909 ///
910 unittest
911 {
912     auto x = [1, 2, 3];
913     import std.array: array;
914     assert(zipWith!`a+b`(x, x).array == [2, 4, 6]);
915     assert(zipWith!((a, b) => a + b)(x, x).array == [2, 4, 6]);
916     assert(zipWith!`a+b+c`(x, x, x).array == [3, 6, 9]);
917 }
918 
919 auto zipWith(fun, StoppingPolicy, Ranges...)(StoppingPolicy sp,
920                                              Ranges ranges)
921 if (Ranges.length != 0 &&
922     allSatisfy!(isInputRange, Ranges))
923 {
924     import std.range: zip;
925     import std.algorithm.iteration: map;
926     return zip(sp, ranges).map!fun;
927 }
928 
929 /** Pair. TODO: std.typecons */
930 alias Pair(T, U) = Tuple!(T, U);
931 /** Instantiator for `Pair`. */
932 auto pair(T, U)(T t, U u) { return Pair!(T, U)(t, u); }
933 
934 /** Triple. TODO: std.typecons */
935 alias Triple(T, U, V) = Tuple!(T, U, V);
936 /** Instantiator for `Triple`. */
937 auto triple(T, U, V)(T t, U u, V v) { return Triple!(T, U, V)(t, u, v); }
938 
939 /** Quadruple. TODO: std.typecons */
940 alias Quadruple(T, U, V, W) = Tuple!(T, U, V, W);
941 /** Instantiator for `Quadruple`. */
942 auto quadruple(T, U, V, W)(T t, U u, V v, W w) { return Quadruple!(T, U, V, W)(t, u, v, w); }
943 
944 /** Limit/Span (Min,Max) Pair.
945  *
946  * TODO: Simultaneous min and max (minmax) can be optimized to 3/4 comparison.
947  * TODO: Decide on either Span, MinMax or Limits
948  * See_Also: https://stackoverflow.com/questions/21241878/generic-span-type-in-phobos
949  */
950 struct Limits(T)
951 {
952     import std.algorithm.comparison : min, max;
953 
954     @property:
955 
956     /** Expand Limits to include `a`. */
957     auto ref include(in T a) nothrow
958     {
959         _lims[0] = min(_lims[0], a);
960         _lims[1] = max(_lims[1], a);
961         return this;
962     }
963     alias expand = include;
964 
965     auto ref reset() nothrow
966     {
967         _lims[0] = T.max;
968         _lims[1] = T.min;
969     }
970 
971     string toString() const
972     {
973         import std.conv: to;
974         return (`[` ~ to!string(_lims[0]) ~
975                 `...` ~ to!string(_lims[1]) ~ `]`) ;
976     }
977 
978     auto _lims = tuple(T.max, T.min);
979 
980     alias _lims this;
981 }
982 
983 auto limits(T)() { return Limits!T(); }
984 
985 ///
986 unittest
987 {
988     /* import std.file: SysTime; */
989     /* SysTime st; */
990     Limits!int x;
991     x.expand(-10);
992     x.expand(10);
993     assert(x[0] == -10);
994     assert(x[1] == +10);
995     version(show) dbg(x);
996 }
997 
998 /* import rational: Rational; */
999 
1000 /* template getTypeString(T) { */
1001 /*     static if (is(T == Rational)) */
1002 /*         string getTypeString(T)() @safe pure nothrow { */
1003 /*             return x`211A`; */
1004 /*         } */
1005 /* } */
1006 /* /// */
1007 /* unittest { */
1008 /*     import rational: Rational; */
1009 /*     dbg(getTypeString!Rational); */
1010 /* } */
1011 
1012 /** Check if `a` and `b` are colinear. */
1013 bool areColinear(T)(T a, T b)
1014 {
1015     // a and b are colinear if a.x / a.y == b.x / b.y
1016     // We can avoid the division by multiplying out.
1017     return a.x * b.y == a.y * b.x;
1018 }
1019 
1020 /* /\** TODO: Remove when each is standard in Phobos. *\/ */
1021 /* void each(R)(R range, delegate x) @safe pure /\* nothrow *\/ if (isInputRange!R) { */
1022 /*     foreach (ref elt; range) { */
1023 /*         x(elt); */
1024 /*     } */
1025 /* } */
1026 /* /// */
1027 /* unittest { */
1028 /*     version(show) [1, 2, 3, 4].each(a => dbg(a)); */
1029 /* } */
1030 
1031 enum isIntLike(T) = is(typeof({T t = 0; t = t+t;})); // More if needed
1032 
1033 /** $(LUCKY Fibonacci) Numbers (Infinite Range).
1034     See_Also: http://forum.dlang.org/thread/dqlrfoxzsppylcgljyyf@forum.dlang.org#post-mailman.1072.1350619455.5162.digitalmars-d-learn:40puremagic.com
1035     See_Also: https://www.reddit.com/r/programming/comments/rif9x/uniform_function_call_syntax_for_the_d/
1036 */
1037 auto fibonacci(T = int)(T nth = 0)
1038 if (isIntLike!T)
1039 {
1040     static struct Result
1041     {
1042         T a, b;
1043         inout(T) front() inout => b;
1044         void popFront()
1045         {
1046             T c = a+b;
1047             a = b;
1048             b = c;
1049         }
1050         bool empty() const @property => false;
1051     }
1052     return nth == 0 ? Result(0, 1) : Result(1, 1);
1053 }
1054 
1055 ///
1056 unittest
1057 {
1058     import std.range: take;
1059     assert(fibonacci.take(10).equal([1, 1, 2, 3, 5, 8, 13, 21, 34, 55]));
1060     assert(1.fibonacci.take(9).equal([1, 2, 3, 5, 8, 13, 21, 34, 55]));
1061 }
1062 
1063 /** Expand Static `array` into a parameter arguments (AliasSeq!).
1064     See_Also: http://forum.dlang.org/thread/hwellpcaomwbpnpofzlx@forum.dlang.org?page=1
1065 */
1066 template expand(alias array, size_t idx = 0)
1067 if (__traits(isStaticArray, typeof(array)))
1068 {
1069     @property ref delay() { return array[idx]; }
1070     static if (idx + 1 < array.length)
1071     {
1072         import std.meta : AliasSeq;
1073         alias expand = AliasSeq!(delay, expand!(array, idx + 1));
1074     }
1075     else
1076         alias expand = delay;
1077 }
1078 
1079 ///
1080 unittest
1081 {
1082     static void foo(int a, int b, int c)
1083     {
1084         version(show)
1085         {
1086             import std.stdio: writefln;
1087             writefln("a: %s, b: %s, c: %s", a, b, c);
1088         }
1089     }
1090     int[3] arr = [1, 2, 3];
1091     foo(expand!arr);
1092 }
1093 
1094 /** Python Style To-String-Conversion Alias. */
1095 string str(T)(in T a)
1096 {
1097     import std.conv: to;
1098     return to!string(a);
1099 }
1100 
1101 /** Python Style Length Alias. */
1102 auto len(T)(in T a)
1103 {
1104     return a.length;
1105 }
1106 
1107 ///
1108 unittest
1109 {
1110     import std.algorithm.iteration: map;
1111     import std.array: array;
1112     assert(([42].map!str).array == [`42`]);
1113 }
1114 
1115 import std.range: InputRange, OutputRange;
1116 alias Source = InputRange; // nicer alias
1117 alias Sink = OutputRange; // nicer alias
1118 
1119 /* belongs to std.range */
1120 import std.range: cycle, retro;
1121 import std.functional: compose;
1122 alias retroCycle = compose!(cycle, retro);
1123 
1124 import std.traits: isAggregateType;
1125 
1126 /** Generic Member Setter.
1127     See_Also: http://forum.dlang.org/thread/fdjkijrtduraaajlxxne@forum.dlang.org
1128 */
1129 auto ref T set(string member, T, U)(auto ref T a, in U value)
1130 if (isAggregateType!T &&
1131     __traits(hasMember, T, member))
1132 {
1133     __traits(getMember, a, member) = value;
1134     return a;
1135 }
1136 
1137 ///
1138 unittest
1139 {
1140     class C { int x, y, z, w; }
1141     auto c = new C().set!`x`(11).set!`w`(44);
1142     assert(c.x == 11);
1143     assert(c.w == 44);
1144 }
1145 
1146 /* Check if `part` is part of `whole`.
1147    See_Also: http://forum.dlang.org/thread/ls9dbk$jkq$1@digitalmars.com
1148    TODO: Standardize name and remove alises.
1149  */
1150 bool isSliceOf(T)(in T[] part,
1151                   in T[] whole)
1152 {
1153     return (whole.ptr <= part.ptr &&
1154             part.ptr + part.length <=
1155             whole.ptr + whole.length);
1156 }
1157 
1158 /* See_Also: http://forum.dlang.org/thread/cjpplpzdzebfxhyqtskw@forum.dlang.org#post-cjpplpzdzebfxhyqtskw:40forum.dlang.org */
1159 auto dropWhile(alias pred = `a == b`, R, E)(R range, E element)
1160 if (isInputRange!R &&
1161     is (typeof(binaryFun!pred(range.front, element)) : bool))
1162 {
1163     alias predFun = binaryFun!pred;
1164     return range.find!(a => !predFun(a, element));
1165 }
1166 alias dropAllOf = dropWhile;
1167 alias stripFront = dropWhile;
1168 alias lstrip = dropWhile;       // Python style
1169 
1170 import std.algorithm.searching: until;
1171 alias takeUntil = until;
1172 
1173 alias dropUntil = find;
1174 
1175 ///
1176 unittest
1177 {
1178     assert([1, 2, 3].dropWhile(1) == [2, 3]);
1179     assert([1, 1, 1, 2, 3].dropWhile(1) == [2, 3]);
1180     assert([1, 2, 3].dropWhile(2) == [1, 2, 3]);
1181     assert(`aabc`.dropWhile('a') == `bc`); // TODO: Remove restriction on cast to dchar
1182 }
1183 
1184 /* See_Also: http://forum.dlang.org/thread/cjpplpzdzebfxhyqtskw@forum.dlang.org#post-cjpplpzdzebfxhyqtskw:40forum.dlang.org */
1185 auto takeWhile(alias pred = `a == b`, R, E)(R range, E element)
1186 if (isInputRange!R &&
1187     is (typeof(binaryFun!pred(range.front, element)) : bool))
1188 {
1189     import std.algorithm: until;
1190     alias predFun = binaryFun!pred;
1191     return range.until!(a => !predFun(a, element));
1192 }
1193 alias takeAllOf = takeWhile;
1194 
1195 ///
1196 unittest
1197 {
1198     assert(equal([1, 1, 2, 3].takeWhile(1),
1199                  [1, 1]));
1200 }
1201 
1202 /** Simpler variant of Phobos' `split`. */
1203 auto split(alias pred, R)(R haystack)
1204 if (isForwardRange!R)
1205 {
1206     import std.range.primitives : empty;
1207     static if (isSomeString!R ||
1208                isRandomAccessRange!R)
1209     {
1210         auto balance = find!pred(haystack);
1211         immutable pos1 = haystack.length - balance.length;
1212         immutable pos2 = balance.empty ? pos1 : pos1 + 1;
1213         return tuple(haystack[0 .. pos1],
1214                      haystack[pos1 .. pos2],
1215                      haystack[pos2 .. haystack.length]);
1216     }
1217     else
1218     {
1219         import std.functional : unaryFun;
1220         auto original = haystack.save;
1221         auto h = haystack.save;
1222         size_t pos1, pos2;
1223         while (!h.empty)
1224         {
1225             if (unaryFun!pred(h.front))
1226             {
1227                 h.popFront();
1228                 ++pos2;
1229             }
1230             else
1231             {
1232                 haystack.popFront();
1233                 h = haystack.save;
1234                 pos2 = ++pos1;
1235             }
1236         }
1237         // TODO: use Voldemort struct instead of tuple
1238         return tuple(takeExactly(original, pos1),
1239                      takeExactly(haystack, pos2 - pos1),
1240                      h);
1241     }
1242 }
1243 
1244 ///
1245 unittest
1246 {
1247     import std.ascii: isDigit;
1248     assert(`aa1bb`.split!(a => a.isDigit) == tuple(`aa`, `1`, `bb`));
1249     assert(`aa1`.split!(a => a.isDigit) == tuple(`aa`, `1`, ``));
1250     assert(`1bb`.split!(a => a.isDigit) == tuple(``, `1`, `bb`));
1251 }
1252 
1253 /** Simpler variant of Phobos' `splitBefore`. */
1254 auto splitBefore(alias pred, R)(R haystack)
1255 if (isForwardRange!R)
1256 {
1257     static if (isSomeString!R ||
1258                sRandomAccessRange!R)
1259     {
1260         auto balance = find!pred(haystack);
1261         immutable pos = haystack.length - balance.length;
1262         return tuple(haystack[0 .. pos],
1263                      haystack[pos .. haystack.length]);
1264     }
1265     else
1266     {
1267         import std.functional : unaryFun;
1268         auto original = haystack.save;
1269         auto h = haystack.save;
1270         size_t pos;
1271         import std.range.primitives : empty;
1272         while (!h.empty)
1273             if (unaryFun!pred(h.front))
1274                 h.popFront();
1275             else
1276             {
1277                 haystack.popFront();
1278                 h = haystack.save;
1279                 ++pos;
1280             }
1281         // TODO: use Voldemort struct instead of tuple
1282         return tuple(takeExactly(original, pos),
1283                      haystack);
1284     }
1285 }
1286 
1287 ///
1288 unittest
1289 {
1290     import std.ascii: isDigit;
1291     assert(`11ab`.splitBefore!(a => !a.isDigit) == tuple(`11`, `ab`));
1292     assert(`ab`.splitBefore!(a => !a.isDigit) == tuple(``, `ab`));
1293 }
1294 
1295 auto splitAfter(alias pred, R)(R haystack)
1296 if (isForwardRange!R)
1297 {
1298     static if (isSomeString!R || isRandomAccessRange!R)
1299     {
1300         import std.range.primitives : empty;
1301         auto balance = find!pred(haystack);
1302         immutable pos = balance.empty ? 0 : haystack.length - balance.length + 1;
1303         // TODO: use Voldemort struct instead of tuple
1304         return tuple(haystack[0 .. pos], haystack[pos .. haystack.length]);
1305     }
1306     else
1307     {
1308         static assert(0, `How to implement this?`);
1309         // import std.range.primitives : empty;
1310         /* auto original = haystack.save; */
1311         /* auto h = haystack.save; */
1312         /* size_t pos1, pos2; */
1313         /* while (!n.empty) */
1314         /* { */
1315         /*     if (h.empty) */
1316         /*     { */
1317         /*         // Failed search */
1318         /*         return tuple(takeExactly(original, 0), original); */
1319         /*     } */
1320         /*     if (binaryFun!pred(h.front, n.front)) */
1321         /*     { */
1322         /*         h.popFront(); */
1323         /*         n.popFront(); */
1324         /*         ++pos2; */
1325         /*     } */
1326         /*     else */
1327         /*     { */
1328         /*         haystack.popFront(); */
1329         /*         n = needle.save; */
1330         /*         h = haystack.save; */
1331         /*         pos2 = ++pos1; */
1332         /*     } */
1333         /* } */
1334         /* return tuple(takeExactly(original, pos2), h); */
1335     }
1336 }
1337 
1338 ///
1339 unittest
1340 {
1341     import std.ascii: isDigit;
1342     assert(`aa1bb`.splitAfter!(a => a.isDigit) == tuple(`aa1`, `bb`));
1343     assert(`aa1`.splitAfter!(a => a.isDigit) == tuple(`aa1`, ``));
1344 }
1345 
1346 auto moveUntil(alias pred, R)(ref R r)
1347 if (isInputRange!R)
1348 {
1349     auto split = r.splitBefore!pred;
1350     r = split[1];
1351     return split[0];
1352 }
1353 
1354 ///
1355 unittest
1356 {
1357     auto r = `xxx111`;
1358     auto id = r.moveUntil!(a => a == '1');
1359     assert(id == `xxx`);
1360     assert(r == `111`);
1361 }
1362 
1363 auto moveWhile(alias pred, R)(ref R r)
1364 if (isInputRange!R)
1365 {
1366     return r.moveUntil!(a => !pred(a));
1367 }
1368 
1369 ///
1370 unittest
1371 {
1372     auto r = `xxx111`;
1373     auto id = r.moveWhile!(a => a == 'x');
1374     assert(id == `xxx`);
1375     assert(r == `111`);
1376 }
1377 
1378 /** Variant of `findSplitBefore` that destructively pops everything up to,
1379     not including, `needle` from `haystack`.
1380 */
1381 auto findPopBefore(alias pred = `a == b`, R1, R2)(ref R1 haystack, R2 needle)
1382 if (isForwardRange!R1 &&
1383     isForwardRange!R2)
1384 {
1385     import std.range.primitives : empty;
1386 
1387     if (needle.empty)
1388         return haystack[0 .. 0]; // contextual empty hit
1389     if (haystack.empty)
1390         return R1.init;
1391 
1392     import std.algorithm.searching : findSplitBefore;
1393     if (auto split = findSplitBefore!pred(haystack, needle)) // TODO: If which case are empty and what return value should they lead to?
1394     {
1395         haystack = split[1];
1396         return split[0];
1397     }
1398     else
1399         return R1.init; // TODO: correct?
1400 }
1401 
1402 ///
1403 @safe pure nothrow @nogc unittest
1404 {
1405     auto haystack = `xy`;
1406     const needle = `z`;
1407     auto pop = haystack.findPopBefore(needle);
1408     assert(haystack == `xy`);
1409     assert(pop == ``);
1410 }
1411 
1412 ///
1413 @safe pure nothrow @nogc unittest
1414 {
1415     auto haystack = `xyz`;
1416     const needle = `y`;
1417     auto pop = haystack.findPopBefore(needle);
1418     assert(pop == `x`);
1419     assert(haystack == `yz`);
1420 }
1421 
1422 /** Variant of `findSplitAfter` that destructively pops everything up to,
1423     including, `needle` from `haystack`.
1424 */
1425 auto findPopAfter(alias pred = `a == b`, R1, R2)(ref R1 haystack, R2 needle)
1426 if (isForwardRange!R1 &&
1427     isForwardRange!R2)
1428 {
1429     import std.range.primitives : empty;
1430 
1431     if (needle.empty)
1432         return haystack[0 .. 0]; // contextual empty hit
1433     if (haystack.empty)
1434         return R1.init;
1435 
1436     import std.algorithm.searching : findSplitAfter;
1437     auto split = findSplitAfter!pred(haystack, needle);// TODO: use new interface to findSplitAfter
1438     if (split[0].empty)
1439         return R1.init; // TODO: correct?
1440     else
1441     {
1442         haystack = split[1];
1443         return split[0];
1444     }
1445 }
1446 
1447 ///
1448 @safe pure nothrow @nogc unittest
1449 {
1450     auto source = `xyz`;
1451     auto haystack = source;
1452     const needle = `y`;
1453     auto pop = haystack.findPopAfter(needle);
1454     assert(pop == `xy`);
1455     assert(haystack == `z`);
1456 }
1457 
1458 ///
1459 @safe pure nothrow @nogc unittest
1460 {
1461     auto source = `xy`;
1462     auto haystack = source;
1463     const needle = `z`;
1464     auto pop = haystack.findPopAfter(needle);
1465     assert(pop is null);
1466     assert(!pop);
1467     assert(haystack == source);
1468 }
1469 
1470 /** Find First Occurrence any of `needles` in `haystack`.
1471  *
1472  * Like to std.algorithm.find but takes an array of needles as argument instead
1473  * of a variadic list of key needle arguments.  Return found range plus index
1474  * into needles starting at 1 upon.
1475  */
1476 Tuple!(R, size_t) findFirstOfAnyInOrder(alias pred = `a == b`, R)(R haystack, const R[] needles)
1477 {
1478     import std.algorithm.searching : find;
1479     switch (needles.length)
1480     {
1481         case 1:
1482             import std.range.primitives : empty;
1483             auto hit = haystack.find(needles[0]);
1484             return tuple(hit, hit.empty ? 0UL : 1UL);
1485         case 2:
1486             return haystack.find(needles[0],
1487                                  needles[1]);
1488         case 3:
1489             return haystack.find(needles[0],
1490                                  needles[1],
1491                                  needles[2]);
1492         case 4:
1493             return haystack.find(needles[0],
1494                                  needles[1],
1495                                  needles[2],
1496                                  needles[3]);
1497         case 5:
1498             return haystack.find(needles[0],
1499                                  needles[1],
1500                                  needles[2],
1501                                  needles[3],
1502                                  needles[4]);
1503         case 6:
1504             return haystack.find(needles[0],
1505                                  needles[1],
1506                                  needles[2],
1507                                  needles[3],
1508                                  needles[4],
1509                                  needles[5]);
1510         case 7:
1511             return haystack.find(needles[0],
1512                                  needles[1],
1513                                  needles[2],
1514                                  needles[3],
1515                                  needles[4],
1516                                  needles[5],
1517                                  needles[6]);
1518         case 8:
1519             return haystack.find(needles[0],
1520                                  needles[1],
1521                                  needles[2],
1522                                  needles[3],
1523                                  needles[4],
1524                                  needles[5],
1525                                  needles[6],
1526                                  needles[7]);
1527         default:
1528             import std.conv: to;
1529             assert(0, `Too many keys ` ~ needles.length.to!string);
1530     }
1531 }
1532 
1533 ///
1534 @safe pure unittest
1535 {
1536     assert(`abc`.findFirstOfAnyInOrder([`x`]) == tuple(``, 0UL));
1537     assert(`abc`.findFirstOfAnyInOrder([`a`]) == tuple(`abc`, 1UL));
1538     assert(`abc`.findFirstOfAnyInOrder([`c`]) == tuple(`c`, 1UL));
1539     assert(`abc`.findFirstOfAnyInOrder([`a`, `b`]) == tuple(`abc`, 1UL));
1540     assert(`abc`.findFirstOfAnyInOrder([`a`, `b`]) == tuple(`abc`, 1UL));
1541     assert(`abc`.findFirstOfAnyInOrder([`x`, `b`]) == tuple(`bc`, 2UL));
1542 }
1543 
1544 Tuple!(R, size_t)[] findAllOfAnyInOrder(alias pred = `a == b`, R)(R haystack, R[] needles)
1545 {
1546     // TODO: Return some clever lazy range that calls each possible haystack.findFirstOfAnyInOrder(needles);
1547     return typeof(return).init;
1548 }
1549 
1550 /** Return true if all arguments `args` are strictly ordered, that is args[0] <
1551  * args[1] < args[2] < ... .
1552  *
1553  * TODO: CT-variant
1554  * See_Also: http://forum.dlang.org/thread/wzsdhzycwqyrvqmmttix@forum.dlang.org?page=2#post-vprvhifglfegnlvzqmjj:40forum.dlang.org
1555  */
1556 bool areStrictlyOrdered(Ts...)(Ts args)
1557 if (args.length >= 2 &&
1558     haveCommonType!Ts)
1559 {
1560     foreach (i, arg; args[1..$])
1561         if (args[i] >= arg)
1562             return false;
1563     return true;
1564 }
1565 
1566 ///
1567 @safe pure nothrow @nogc unittest
1568 {
1569     static assert(!__traits(compiles, areStrictlyOrdered()));
1570     static assert(!__traits(compiles, areStrictlyOrdered(1)));
1571     assert(areStrictlyOrdered(1, 2, 3));
1572     assert(!areStrictlyOrdered(1, 3, 2));
1573     assert(!areStrictlyOrdered(1, 2, 2));
1574     assert(areStrictlyOrdered('a', 'b', 'c'));
1575 }
1576 
1577 /** Return true if all arguments `args` are unstrictly ordered,
1578  * that is args[0] <= args[1] <= args[2] <= ... .
1579  *
1580  * TODO: CT-variant
1581  * See_Also: http://forum.dlang.org/thread/wzsdhzycwqyrvqmmttix@forum.dlang.org?page=2#post-vprvhifglfegnlvzqmjj:40forum.dlang.org
1582 */
1583 bool areUnstrictlyOrdered(Ts...)(Ts args)
1584 if (args.length >= 2 &&
1585     haveCommonType!Ts)
1586 {
1587     foreach (i, arg; args[1..$])
1588         if (args[i] > arg)
1589             return false;
1590     return true;
1591 }
1592 
1593 ///
1594 @safe pure nothrow @nogc unittest
1595 {
1596     static assert(!__traits(compiles, areUnstrictlyOrdered()));
1597     static assert(!__traits(compiles, areUnstrictlyOrdered(1)));
1598     assert(areUnstrictlyOrdered(1, 2, 2, 3));
1599     assert(!areUnstrictlyOrdered(1, 3, 2));
1600     assert(areUnstrictlyOrdered('a', 'b', 'c'));
1601 }
1602 
1603 import core.checkedint: addu, subu, mulu;
1604 
1605 alias sadd = addu;
1606 alias ssub = subu;
1607 alias smul = mulu;
1608 
1609 /** Distinct Elements of `r`.
1610  *
1611  * See_Also: http://forum.dlang.org/thread/jufggxqwzhlsmhshtnfj@forum.dlang.org?page=2
1612  * See_Also: http://dpaste.dzfl.pl/7b4b37b490a7
1613  */
1614 auto distinct(R)(R r)
1615 if (isInputRange!(Unqual!R))
1616 {
1617     import std.traits: ForeachType;
1618     bool[ForeachType!R] seen; // TODO: Use containers.hashset.HashSet
1619     import std.algorithm.iteration: filter;
1620     return r.filter!((k)
1621                      {
1622                          if (k !in seen)
1623                              return false;
1624                          else
1625                              return seen[k] = true;
1626                      });
1627 }
1628 
1629 // unittest
1630 // {
1631 //     immutable first = [1, 0, 2, 1, 3];
1632 //     immutable second = [1,5,6];
1633 //     import std.range: chain, take;
1634 //     assert(equal(first.chain(second).distinct.take(5),
1635 //                  [1, 0, 2, 3, 5]));
1636 // }
1637 
1638 /** Returns: `true` iff `value` is equal to any of `values`, `false` otherwise. */
1639 bool isAmong(alias pred = (a, b) => a == b,
1640              Value,
1641              Values...)(Value value,
1642                         Values values)
1643 if (Values.length != 0)
1644 {
1645     import std.algorithm.comparison : among;
1646     return cast(bool)value.among!pred(values);
1647 }
1648 
1649 ///
1650 @safe pure nothrow @nogc unittest
1651 {
1652     assert(`b`.isAmong(`a`, `b`));
1653     assert(!`c`.isAmong(`a`, `b`));
1654 }
1655 
1656 import std.traits : isExpressionTuple;
1657 import nxt.traits_ex : haveCommonType;
1658 
1659 /** Returns: `true` iff `value` is equal to any of `values`, `false` otherwise. */
1660 template isAmong(values...)
1661 if (isExpressionTuple!values &&
1662     values.length != 0)
1663 {
1664     bool isAmong(Value)(Value value)
1665         if (haveCommonType!(Value, values))
1666     {
1667         switch (value)
1668         {
1669             foreach (uint i, v; values)
1670             case v:
1671                 return true;
1672         default:
1673             return false;
1674         }
1675     }
1676 }
1677 
1678 ///
1679 @safe pure nothrow @nogc unittest
1680 {
1681     assert(`b`.isAmong!(`a`, `b`));
1682     assert(!`c`.isAmong!(`a`, `b`));
1683 }
1684 
1685 import std.algorithm.setops : cartesianProduct;
1686 
1687 /** More descriptive alias. */
1688 alias elementCombinations = cartesianProduct;
1689 
1690 /** Reset all members in aggregate instance `c`.
1691  *
1692  * See_Also: http://forum.dlang.org/post/ckitmpguywfitgadfpkv@forum.dlang.org
1693  * See_Also: http://forum.dlang.org/post/fbs8b5$5bu$1@digitalmars.com
1694  */
1695 void resetAllMembers(T)(T c)
1696 if (is(T == class))
1697 {
1698     foreach (ref m; c.tupleof)
1699     {
1700         import std.traits : isMutable;
1701         alias M = typeof(m);
1702         static if (isMutable!M)
1703             m = M.init;
1704     }
1705 }
1706 
1707 ///
1708 @safe pure nothrow unittest
1709 {
1710     class C
1711     {
1712         this (int a, int b, string c)
1713         {
1714             this.a = a;
1715             this.b = b;
1716             this.c = c;
1717         }
1718         int a; int b; string c;
1719     }
1720     void f(C c)
1721     {
1722         c.resetAllMembers();
1723     }
1724     auto c = new C(1, 2, "3");
1725     assert(c.a == 1);
1726     assert(c.b == 2);
1727     assert(c.c == "3");
1728     f(c);
1729     assert(c.a == 0);
1730     assert(c.b == 0);
1731     assert(c.c == null);
1732 }
1733 
1734 /** Returns: `true` iff `r` contains strictly values that are strictly increase
1735  * with the increment `step`.
1736  *
1737  * See_Also: http://forum.dlang.org/post/mqjyhvqxepgfljpkxvmd@forum.dlang.org
1738  */
1739 bool isLinearRamp(R)(R r, size_t step = 1)
1740 if (isInputRange!R &&
1741     isIntegral!(ElementType!R))
1742 {
1743     import std.algorithm.searching : findAdjacent;
1744     import std.range.primitives : empty;
1745     return r.findAdjacent!((a, b) => a + step != b).empty;
1746 }
1747 
1748 bool hasHoles(R)(R r)
1749 if (isInputRange!R &&
1750     isIntegral!(ElementType!R))
1751 {
1752     return !r.isLinearRamp;
1753 }
1754 
1755 ///
1756 @safe pure nothrow unittest
1757 {
1758     assert([1].isLinearRamp);
1759     assert([1, 2, 3].isLinearRamp);
1760     assert(![1, 1].isLinearRamp);
1761     assert(![1, 2, 1].isLinearRamp);
1762     assert(![1, 2, 4].isLinearRamp);
1763 }
1764 
1765 /** Check if `r` counts to exactly `exactCount` elements.
1766  */
1767 bool countsExactly(R)(R r, size_t exactCount) @("complexity", "O(exactCount)")
1768 if (isInputRange!R)
1769 {
1770     import std.range.primitives : hasLength;
1771     static if (hasLength!R)
1772         return r.length == exactCount;
1773     else
1774     {
1775         size_t n = 0;
1776         import std.range.primitives : empty;
1777         while (!r.empty)
1778         {
1779             r.popFront();
1780             if (++n > exactCount)
1781                 return false;
1782         }
1783         return n == exactCount;
1784     }
1785 }
1786 
1787 /** Check if `r` counts to at least `minCount` elements.
1788  */
1789 bool countsAtLeast(R)(R r, size_t minCount) @("complexity", "O(minCount)")
1790 if (isInputRange!R)
1791 {
1792     import std.range.primitives : hasLength;
1793     static if (hasLength!R)
1794         return r.length >= minCount;
1795     else
1796     {
1797         size_t n;
1798         import std.range.primitives : empty;
1799         while (!r.empty)
1800         {
1801             r.popFront();
1802             if (++n >= minCount)
1803                 return true;
1804         }
1805         return false;
1806     }
1807 }
1808 
1809 /** Check if `r` counts to at most `maxCount` elements.
1810  */
1811 bool countsAtMost(R)(R r, size_t maxCount) @("complexity", "O(maxCount)")
1812 if (isInputRange!R)
1813 {
1814     import std.range.primitives : hasLength;
1815     static if (hasLength!R)
1816         return r.length <= maxCount;
1817     else
1818     {
1819         size_t n;
1820         import std.range.primitives : empty;
1821         while (!r.empty)
1822         {
1823             r.popFront();
1824             if (++n > maxCount)
1825                 return false;
1826         }
1827         return true;
1828     }
1829 }
1830 
1831 ///
1832 @safe pure nothrow @nogc unittest
1833 {
1834     static void test(R)(R x)
1835         if (isInputRange!R)
1836     {
1837         import std.algorithm.searching : count;
1838         immutable n = x.count;
1839 
1840         // below
1841         foreach (immutable i; 0 .. n)
1842         {
1843             assert(x.countsAtLeast(i));
1844             assert(!x.countsExactly(i));
1845             assert(!x.countsAtMost(i));
1846         }
1847 
1848         // at
1849         assert(x.countsAtLeast(n));
1850         assert(x.countsExactly(n));
1851         assert(x.countsAtMost(n));
1852 
1853         // above
1854         foreach (immutable i; n + 1 .. n + 10)
1855         {
1856             assert(!x.countsAtLeast(i));
1857             assert(!x.countsExactly(i));
1858             assert(x.countsAtMost(i));
1859         }
1860     }
1861 
1862     import std.algorithm.iteration : filter;
1863     import std.range : iota;
1864 
1865     test(3.iota.filter!(x => true));
1866 
1867     int[3] x = [0, 1, 2];
1868     test(x[]);
1869 }
1870 
1871 import std.meta : allSatisfy;
1872 import std.range.primitives : hasLength;
1873 
1874 /** Returns: `true` iff `r` and all `ss` all have equal length.
1875  */
1876 bool equalLength(R, Ss...)(const R r, const Ss ss)
1877     @safe pure nothrow @nogc
1878 if (Ss.length != 0 &&
1879     allSatisfy!(hasLength, R, Ss))
1880 {
1881     foreach (const ref s; ss)
1882         if (r.length != s.length)
1883             return false;
1884     return true;
1885 }
1886 
1887 ///
1888 @safe pure nothrow unittest
1889 {
1890     assert(equalLength([1], [2], [3]));
1891     assert(!equalLength([1, 1], [2], [3]));
1892     assert(!equalLength([1], [2, 2], [3]));
1893     assert(!equalLength([1], [2], [3, 3]));
1894 }
1895 
1896 /** Collect/Gather the elements of `r` into a `Container` and return it.
1897  *
1898  * TODO: Use std.container.util.make instead?: https://dlang.org/phobos/std_container_util.html#.make
1899  * TODO: Rename `container` to `output`?
1900  * TODO: Support Set-containers via `insert` aswell, or add `alias put = insert` to them?
1901  * TODO: What about `Appender`?
1902  * TODO: Rename from `collect` to `gather`.
1903  */
1904 Container collect(Container, Range) (Range r)
1905     // if (isInputRange!Range &&
1906     //     isOutputRange!(Container, ElementType!Range))
1907 {
1908     static if (hasLength!Range)
1909     {
1910         static if (isArray!Container)
1911         {
1912             import std.array : uninitializedArray;
1913             auto output = uninitializedArray!(Container)(r.length);
1914         }
1915         else
1916         {
1917             Container output;
1918             output.length = r.length;
1919         }
1920         import std.algorithm.mutation : copy;
1921         r.copy(output[]);       // slicing is @trusted
1922         return output;
1923     }
1924     else
1925     {
1926         static if (isArray!Container)
1927         {
1928             import std.array : Appender;
1929             Appender!Container output;
1930             foreach (ref e; r)  // TODO: make const when this works with array_ex
1931                 output.put(e);
1932             return output.data;
1933         }
1934         else
1935         {
1936             Container output;
1937             foreach (ref e; r)  // TODO: make const when this works with array_ex
1938                 output ~= e; // TODO: use Appender or remove because too GC.intensive inefficient, or reuse `.array`?
1939             return output;
1940         }
1941     }
1942 }
1943 
1944 ///
1945 @safe pure nothrow unittest
1946 {
1947     import std.range : iota;
1948     import std.algorithm.iteration : map, filter;
1949     import nxt.algorithm_ex : collect;
1950 
1951     alias E = int;
1952     alias V = E[];
1953     immutable n = 1000;
1954 
1955     auto x = 0.iota(n).collect!V;
1956 
1957     assert([0, 1, 2].map!(_ => _^^2).collect!V.equal([0, 1, 4]));
1958     assert([0, 1, 2, 3].filter!(_ => _ & 1).collect!V.equal([1, 3]));
1959 }
1960 
1961 /** Returns: `x` eagerly split in two parts, all as equal in length as possible.
1962  *
1963  * Safely avoids range checking thanks to D's builtin slice expressions.
1964  * Use in divide-and-conquer algorithms such as quicksort and binary search.
1965  */
1966 auto spliced2(T)(T[] x) @trusted
1967 {
1968     static struct Result        // Voldemort type
1969     {
1970         T[] first;              // first half
1971         T[] second;             // second half
1972     }
1973     immutable m = x.length / 2;
1974     // range checking is not needed
1975     return Result(x.ptr[0 .. m], // can be @trusted
1976                   x.ptr[m .. x.length]); // can be @trusted
1977 }
1978 alias halved = spliced2;
1979 
1980 ///
1981 @safe pure nothrow @nogc unittest
1982 {
1983     immutable int[6] x = [0, 1, 2, 3, 4, 5];
1984     immutable y = x.spliced2;
1985     assert(y.first.equal(x[0 .. 3]));
1986     assert(y.second.equal(x[3 .. $]));
1987 }
1988 
1989 /** Returns: `x` eagerly split in three parts, all as equal in length as possible.
1990  *
1991  * Safely avoids range checking thanks to D's builtin slice expressions.
1992  * Use in divide-and-conquer algorithms such as quicksort and binary search.
1993  */
1994 auto spliced3(T)(T[] x) @trusted
1995 {
1996     enum count = 3;
1997     static struct Result        // Voldemort type
1998     {
1999         T[] first;              // first half
2000         T[] second;             // second half
2001         T[] third;              // third half
2002     }
2003     // range checking is not needed
2004     immutable m = 1*x.length/count;
2005     immutable n = 2*x.length/count;
2006     return Result(x.ptr[0 .. m], // can be @trusted
2007                   x.ptr[m .. n], // can be @trusted
2008                   x.ptr[n .. x.length]); // can be @trusted
2009 }
2010 
2011 @safe pure nothrow @nogc unittest
2012 {
2013     immutable int[6] x = [0, 1, 2, 3, 4, 5];
2014     immutable y = x.spliced3;
2015     assert(y.first.equal(x[0 .. 2]));
2016     assert(y.second.equal(x[2 .. 4]));
2017     assert(y.third.equal(x[4 .. 6]));
2018 }
2019 
2020 /** Splice `x` in `N` parts, all as equal in lengths as possible.
2021  *
2022  * Safely avoids range checking thanks to D's builtin slice expressions.
2023  * Use in divide-and-conquer algorithms such as quicksort and binary search.
2024  */
2025 auto splicerN(uint N, T)(T[] x) @trusted
2026 {
2027     static struct Result        // Voldemort type
2028     {
2029         enum count = N;
2030         pragma(inline) @trusted pure nothrow @nogc:
2031 
2032         /// Returns: `i`:th slice.
2033         inout(T)[] at(uint i)() inout
2034         {
2035             static assert(i < N, "Index " ~ i ~ " to large");
2036             static if (i == 0)
2037                 return _.ptr[0 .. (i + 1)*_.length/N]; // can be @trusted
2038             else static if (i + 1 == N)
2039                 return _.ptr[i * _.length/N .. _.length]; // can be @trusted
2040             else
2041                 return _.ptr[i * _.length/N .. (i + 1)*_.length/N]; // non-range-checked can be @trusted
2042         }
2043 
2044         private T[] _;
2045     }
2046     return Result(x);
2047 }
2048 
2049 ///
2050 @safe pure nothrow @nogc unittest
2051 {
2052     enum count = 6;
2053 
2054     immutable int[count] x = [0, 1, 3, 3, 4, 5];
2055     immutable y = x.splicerN!count;
2056 
2057     static foreach (i; 0 .. count)
2058     {
2059         assert(y.at!i.equal(x[i .. i + 1]));
2060     }
2061 }
2062 
2063 /** Specialization of `splicerN` to `N` being 2. */
2064 auto splicer2(T)(T[] x) @trusted
2065 {
2066     static struct Result        // Voldemort type
2067     {
2068         enum count = 2;
2069         pragma(inline) @trusted pure nothrow @nogc:
2070 
2071         /// Returns: first part of splice.
2072         @property inout(T)[] first() inout
2073         {
2074             return _.ptr[0 .. _.length/count]; // can be @trusted
2075         }
2076 
2077         /// Returns: second part of splice.
2078         @property inout(T)[] second() inout
2079         {
2080             return _.ptr[_.length/count .. _.length]; // can be @trusted
2081         }
2082 
2083         inout(T)[] at(uint i)() inout
2084         {
2085             static assert(i < count, "Index " ~ i ~ " to large");
2086             static      if (i == 0)
2087                 return first;
2088             else static if (i == 1)
2089                 return second;
2090         }
2091 
2092         private T[] _;
2093     }
2094     return Result(x);
2095 }
2096 
2097 ///
2098 @safe pure nothrow @nogc unittest
2099 {
2100     immutable int[6] x = [0, 1, 2, 3, 4, 5];
2101     immutable y = x.splicer2;
2102     assert(y.first.equal(x[0 .. 3]));
2103     assert(y.second.equal(x[3 .. $]));
2104     assert(y.at!0.equal(x[0 .. 3]));
2105     assert(y.at!1.equal(x[3 .. $]));
2106 }
2107 
2108 /** Specialization of `splicerN` to `N` being 3. */
2109 auto splicer3(T)(T[] x) @trusted
2110 {
2111     static struct Result        // Voldemort type
2112     {
2113         enum count = 3;
2114         pragma(inline) @trusted pure nothrow @nogc:
2115 
2116         /// Returns: first part of splice.
2117         @property inout(T)[] first() inout
2118         {
2119             return _.ptr[0 .. _.length/count];  // can be @trusted
2120         }
2121 
2122         /// Returns: second part of splice.
2123         @property inout(T)[] second() inout
2124         {
2125             return _.ptr[_.length/count .. 2*_.length/count];  // can be @trusted
2126         }
2127 
2128         /// Returns: third part of splice.
2129         @property inout(T)[] third() inout
2130         {
2131             return _.ptr[2*_.length/count .. _.length]; // can be @trusted
2132         }
2133 
2134         private T[] _;
2135     }
2136     return Result(x);
2137 }
2138 
2139 ///
2140 @safe pure nothrow @nogc unittest
2141 {
2142     immutable int[6] x = [0, 1, 3, 3, 4, 5];
2143     immutable y = x.splicer3;
2144     assert(y.first.equal(x[0 .. 2]));
2145     assert(y.second.equal(x[2 .. 4]));
2146     assert(y.third.equal(x[4 .. $]));
2147 }
2148 
2149 /**
2150    See_Also: http://forum.dlang.org/post/mqfaevkvhwwtzaafrtve@forum.dlang.org
2151  */
2152 auto use(alias F, T)(T t) if (is(typeof(F(T.init)))) => F(t);
2153 
2154 @safe pure nothrow @nogc unittest
2155 {
2156     foreach (const i; 1 .. 11)
2157         foreach (const j; 1 .. 11)
2158             immutable result = (i * j).use!(x => x*x);
2159 }
2160 
2161 import nxt.char_traits : isASCII;
2162 
2163 /** TOOD Merge into Phobos' startsWith. */
2164 template startsWith(needles...)
2165 if (isExpressionTuple!needles &&
2166     needles.length != 0)
2167 {
2168     uint startsWith(Haystack)(Haystack haystack) @trusted
2169         if (!is(CommonType!(typeof(Haystack.front), needles) == void))
2170     {
2171         if (haystack.length == 0)
2172             return 0;
2173         static if (isArray!Haystack &&
2174                    is(typeof(Haystack.init[0]) : char) &&
2175                    allSatisfy!(isASCII, needles))
2176         {
2177             // no front decoding needed
2178             static if (needles.length == 1)
2179                 return haystack.ptr[0] == needles[0] ? 1 : 0;
2180             else
2181             {
2182                 import std.algorithm.comparison : among;
2183                 return haystack.ptr[0].among!(needles);
2184             }
2185         }
2186         else
2187         {
2188             import std.range.primitives : front; // need decoding
2189             import std.algorithm.comparison : among;
2190             return haystack.front.among!(needles);
2191         }
2192     }
2193 }
2194 
2195 @safe pure nothrow @nogc unittest
2196 {
2197     const haystack = "abc";
2198     assert(haystack.startsWith!('a'));
2199     assert(!haystack.startsWith!('b'));
2200 }
2201 
2202 @safe pure unittest
2203 {
2204     const haystack = "äbc";
2205     assert(haystack.startsWith!('ä'));
2206 }
2207 
2208 @safe pure nothrow @nogc unittest
2209 {
2210     const haystack = "abc";
2211     assert(haystack.startsWith!('b') == 0);
2212     assert(haystack.startsWith!('a') == 1);
2213     assert(haystack.startsWith!('_',
2214                                 'a') == 2);
2215 }
2216 
2217 /** TOOD Merge into Phobos' endsWith. */
2218 template endsWith(needles...)
2219 if (isExpressionTuple!needles &&
2220     needles.length != 0)
2221 {
2222     uint endsWith(Haystack)(Haystack haystack)
2223         @trusted
2224     if (!is(CommonType!(typeof(Haystack.back), needles) == void))
2225     {
2226         if (haystack.length == 0)
2227             return 0;
2228         static if (isArray!Haystack &&
2229                    is(Unqual!(typeof(Haystack.init[0])) == char) && // TODO: reuse existing trait
2230                    allSatisfy!(isASCII, needles))
2231         {
2232             // no back decoding needed
2233             static if (needles.length == 1)
2234                 return haystack.ptr[haystack.length - 1] == needles[0] ? 1 : 0;
2235             else
2236             {
2237                 import std.algorithm.comparison : among;
2238                 return haystack.ptr[haystack.length - 1].among!(needles);
2239             }
2240         }
2241         else
2242         {
2243             import std.range.primitives : back; // need decoding
2244             import std.algorithm.comparison : among;
2245             return haystack.back.among!(needles);
2246         }
2247     }
2248 }
2249 
2250 @safe pure nothrow @nogc unittest
2251 {
2252     const haystack = "abc";
2253     assert(haystack.endsWith!('b') == 0);
2254     assert(haystack.endsWith!('c') == 1);
2255     assert(haystack.endsWith!('_',
2256                               'c') == 2);
2257 }
2258 
2259 @safe pure unittest
2260 {
2261     const haystack = "abć";
2262     assert(haystack.endsWith!('ć'));
2263 }
2264 
2265 /**
2266  * Allows forbidden casts.
2267  *
2268  * Params:
2269  *      OT = The output type.
2270  *      IT = The input type, optional, likely to be infered.
2271  *      it = A reference to an IT.
2272  *
2273  * Returns:
2274  *      the same as $(D cast(OT) it), except that it never fails to compile.
2275  */
2276 auto bruteCast(OT, IT)(auto ref IT it) => *cast(OT*) &it;
2277 
2278 ///
2279 nothrow pure @nogc unittest
2280 {
2281     static immutable array = [0u,1u,2u];
2282     size_t len;
2283     //len = cast(uint) array; // not allowed.
2284     len = bruteCast!uint(array);
2285     assert(len == array.length);
2286 }
2287 
2288 /** Split `range` using multiple separators stored as elements in `separators`.
2289  *
2290  * See_Also: https://forum.dlang.org/post/nv60ra$9vc$1@digitalmars.com
2291  */
2292 auto splitterN(R, S)(scope return R range,
2293                      const scope S separators)
2294 {
2295     import std.algorithm.iteration : splitter;
2296     import std.algorithm.searching : canFind;
2297     return range.splitter!(element => separators.canFind(element));
2298 }
2299 
2300 ///
2301 @safe pure unittest
2302 {
2303     auto result = "a-b+c".splitterN("+-");
2304     assert(result.equal(["a", "b", "c"].s[]));
2305 }
2306 
2307 // splitter is nothrow @nogc when `haystack` is of same NarrowString as `needle`
2308 @safe pure nothrow @nogc unittest
2309 {
2310     import std.algorithm.iteration : splitter;
2311     assert("a b".splitter(" ").equal(["a", "b"].s[]));
2312 }
2313 
2314 version(unittest)
2315 {
2316     import std.algorithm.comparison : equal;
2317     import nxt.array_help : s;
2318 }