1 /** Array-only overloads of Phobos algorithms.
2  *
3  * Functions are when possible `@safe pure nothrow @nogc`.
4  * Haystack parameter is when possible and relevant `scope return inout(T)[]` and DIP-1000-compliant.
5  * Needle parameter is either `scope const(T)[]` or `scope const T[]`.
6  *
7  * Provides more than twice as fast compilation for `char`-arrays (`string`s).
8  *
9  * See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
10  * See_Also: https://forum.dlang.org/thread/ybamybeakxwxwleebnwb@forum.dlang.org?page=1
11  *
12  * TODO: Merge into separate array-specializations of Phobos algorithms for less template bloat in Phobos.
13  *
14  * TODO: Move to nxt/algorithm/searching_ex.d
15  */
16 module nxt.array_algorithm;
17 
18 // version = unittestAsBetterC; // Run_As: dmd -betterC -unittest -run $(__FILE__).d
19 
20 /** Array-specialization of `startsWith` with default predicate.
21  *
22  * See_Also: https://d.godbolt.org/z/ejEmrK
23  */
24 bool startsWith(T)(scope const T[] haystack,
25                    scope const T[] needle) @trusted
26 {
27     if (haystack.length < needle.length)
28         return false;
29     return haystack.ptr[0 .. needle.length] == needle;
30 }
31 /// ditto
32 bool startsWith(T)(scope const T[] haystack,
33                    scope const T needle) @trusted
34 {
35     static if (is(T == char))
36         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
37     if (haystack.length == 0)
38         return false;
39     return haystack.ptr[0] == needle;
40 }
41 
42 ///
43 @safe pure nothrow @nogc unittest
44 {
45     const x = "beta version";
46     assert(x.startsWith("beta"));
47     assert(x.startsWith('b'));
48     assert(!x.startsWith("_"));
49 }
50 
51 /** Array-specialization of `all` with element needle. */
52 bool all(T)(scope const T[] haystack,
53             scope const T needle) @trusted
54 {
55     static if (is(T == char))
56         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
57     foreach (const offset; 0 .. haystack.length)
58         if (haystack.ptr[offset] != needle)
59             return false;
60     return true;
61 }
62 
63 ///
64 @safe pure nothrow @nogc unittest
65 {
66     assert("".all('a'));    // matches behaviour of `std.algorithm.searching.any`
67     assert("aaa".all('a'));
68     assert(!"aa_".all('a'));
69 }
70 
71 /** Array-specialization of `any` with element needle. */
72 bool any(T)(scope const T[] haystack,
73             scope const T needle) @trusted
74 {
75     static if (is(T == char))
76         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
77     foreach (const offset; 0 .. haystack.length)
78         if (haystack.ptr[offset] == needle)
79             return true;
80     return false;
81 }
82 
83 ///
84 @safe pure nothrow @nogc unittest
85 {
86     assert(!"".any('a'));  // matches behaviour of `std.algorithm.searching.any`
87     assert("aaa".any('a'));
88     assert("aa_".any('a'));
89     assert(!"_".any('a'));
90 }
91 
92 /** Array-specialization of `endsWith` with default predicate. */
93 bool endsWith(T)(scope const T[] haystack,
94                  scope const T[] needle) @trusted
95 {
96     if (haystack.length < needle.length)
97         return false;
98     return haystack.ptr[haystack.length - needle.length .. haystack.length] == needle;
99 }
100 /// ditto
101 bool endsWith(T)(scope const T[] haystack,
102                  scope const T needle) @trusted
103 {
104     static if (is(T == char))
105         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
106     if (haystack.length == 0)
107         return false;
108     return haystack.ptr[haystack.length - 1] == needle;
109 }
110 
111 ///
112 @safe pure nothrow @nogc unittest
113 {
114     const x = "beta version";
115     assert(x.endsWith("version"));
116     assert(x.endsWith('n'));
117     assert(!x.startsWith("_"));
118 }
119 
120 bool endsWithEither(T)(scope const T[] haystack,
121                        scope const T[][] needles) @trusted
122 {
123     foreach (const needle; needles)
124         if (haystack.endsWith(needle)) // TODO: optimize
125             return true;
126     return false;
127 }
128 /// ditto
129 bool endsWithEither(T)(scope const T[] haystack,
130                        scope const T[] needles) @trusted
131 {
132     foreach (const needle; needles)
133         if (haystack.endsWith(needle)) // TODO: optimize
134             return true;
135     return false;
136 }
137 
138 ///
139 @safe pure nothrow @nogc unittest
140 {
141     const x = "beta version";
142     assert(x.endsWithEither(["version", ""]));
143     assert(x.endsWithEither(['n', ' ']));
144     assert(x.endsWithEither("n "));
145 }
146 
147 /** Array-specialization of `findSkip` with default predicate.
148  */
149 auto findSkip(T)(scope ref inout(T)[] haystack,
150                  scope const T[] needle) @trusted
151 {
152     const index = haystack.indexOf(needle);
153     if (index != -1)
154     {
155         haystack = haystack.ptr[index + needle.length .. haystack.length];
156         return true;
157     }
158     return false;
159 }
160 /// ditto
161 auto findSkip(T)(scope ref inout(T)[] haystack,
162                  scope const T needle) @trusted
163 {
164     const index = haystack.indexOf(needle);
165     if (index != -1)
166     {
167         haystack = haystack.ptr[index + 1 .. haystack.length];
168         return true;
169     }
170     return false;
171 }
172 
173 ///
174 @safe pure nothrow @nogc unittest
175 {
176     const auto x = "abc";
177     {
178         string y = x;
179         const bool ok = y.findSkip("_");
180         assert(!ok);
181         assert(y is x);
182     }
183     {
184         string y = x;
185         const bool ok = y.findSkip("a");
186         assert(ok);
187         assert(y == x[1 .. $]);
188     }
189     {
190         string y = x;
191         const bool ok = y.findSkip("c");
192         assert(ok);
193         assert(y is x[$ .. $]);
194     }
195     char[] f()() @safe pure nothrow { char[1] x = "_"; return x[].findSkip(" "); }
196     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
197 }
198 
199 ///
200 @safe pure nothrow @nogc unittest
201 {
202     const auto x = "abc";
203     {
204         string y = x;
205         const bool ok = y.findSkip('_');
206         assert(!ok);
207         assert(y is x);
208     }
209     {
210         string y = x;
211         const bool ok = y.findSkip('a');
212         assert(ok);
213         assert(y == x[1 .. $]);
214     }
215     {
216         string y = x;
217         const bool ok = y.findSkip('c');
218         assert(ok);
219         assert(y is x[$ .. $]);
220     }
221     char[] f()() @safe pure nothrow { char[1] x = "_"; return x[].findSkip(' '); }
222     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
223 }
224 
225 /** Array-specialization of `findSkip` with default predicate that finds the last skip.
226  */
227 auto findLastSkip(T)(scope ref inout(T)[] haystack,
228                      scope const T[] needle) @trusted
229 {
230     const index = haystack.lastIndexOf(needle);
231     if (index != -1)
232     {
233         haystack = haystack.ptr[index + needle.length .. haystack.length];
234         return true;
235     }
236     return false;
237 }
238 ///
239 auto findLastSkip(T)(scope ref inout(T)[] haystack,
240                      scope const T needle) @trusted
241 {
242     const index = haystack.lastIndexOf(needle);
243     if (index != -1)
244     {
245         haystack = haystack.ptr[index + 1 .. haystack.length];
246         return true;
247     }
248     return false;
249 }
250 
251 ///
252 @safe pure nothrow @nogc unittest
253 {
254     const auto x = "abacc";
255     {
256         string y = x;
257         const bool ok = y.findLastSkip("_");
258         assert(!ok);
259         assert(y is x);
260     }
261     {
262         string y = x;
263         const bool ok = y.findLastSkip("a");
264         assert(ok);
265         assert(y == x[3 .. $]);
266     }
267     {
268         string y = x;
269         const bool ok = y.findLastSkip("c");
270         assert(ok);
271         assert(y is x[$ .. $]);
272     }
273     char[] f()() @safe pure nothrow { char[1] x = "_"; return x[].findLastSkip(" "); }
274     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
275 }
276 
277 ///
278 @safe pure nothrow @nogc unittest
279 {
280     const auto x = "abacc";
281     {
282         string y = x;
283         const bool ok = y.findLastSkip('_');
284         assert(!ok);
285         assert(y is x);
286     }
287     {
288         string y = x;
289         const bool ok = y.findLastSkip('a');
290         assert(ok);
291         assert(y == x[3 .. $]);
292     }
293     {
294         string y = x;
295         const bool ok = y.findLastSkip('c');
296         assert(ok);
297         assert(y is x[$ .. $]);
298     }
299     char[] f()() @safe pure nothrow { char[1] x = "_"; return x[].findLastSkip(' '); }
300     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
301 }
302 
303 /** Array-specialization of `skipOver` with default predicate.
304  *
305  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
306  */
307 bool skipOver(T)(scope ref inout(T)[] haystack,
308                  scope const T[] needle) @trusted
309 {
310     if (!startsWith(haystack, needle))
311         return false;
312     haystack = haystack.ptr[needle.length .. haystack.length];
313     return true;
314 }
315 /// ditto
316 bool skipOver(T)(scope ref inout(T)[] haystack,
317                  scope const T needle) @trusted
318 {
319     if (!startsWith(haystack, needle))
320         return false;
321     haystack = haystack.ptr[1 .. haystack.length];
322     return true;
323 }
324 
325 ///
326 @safe pure nothrow @nogc unittest
327 {
328     string x = "beta version";
329     assert(x.skipOver("beta"));
330     assert(x == " version");
331     assert(x.skipOver(' '));
332     assert(x == "version");
333 }
334 
335 /// constness of haystack and needle
336 @safe pure nothrow @nogc unittest
337 {
338     {
339         const(char)[] haystack;
340         string needle;
341         assert(haystack.skipOver(needle));
342     }
343     {
344         const(char)[] haystack;
345         const(char)[] needle;
346         assert(haystack.skipOver(needle));
347     }
348     {
349         const(char)[] haystack;
350         char[] needle;
351         assert(haystack.skipOver(needle));
352     }
353 }
354 
355 /** Array-specialization of `skipOverBack` with default predicate.
356  *
357  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
358  */
359 bool skipOverBack(T)(scope ref inout(T)[] haystack,
360                      scope const T[] needle) @trusted
361 {
362     if (!endsWith(haystack, needle))
363         return false;
364     haystack = haystack.ptr[0 .. haystack.length - needle.length];
365     return true;
366 }
367 /// ditto
368 bool skipOverBack(T)(scope ref inout(T)[] haystack,
369                      scope const T needle) @trusted
370 {
371     if (!endsWith(haystack, needle))
372         return false;
373     haystack = haystack.ptr[0 .. haystack.length - 1];
374     return true;
375 }
376 
377 ///
378 @safe pure nothrow @nogc unittest
379 {
380     string x = "beta version";
381     assert(x.skipOverBack(" version"));
382     assert(x == "beta");
383     assert(x.skipOverBack('a'));
384     assert(x == "bet");
385 }
386 
387 bool skipOverAround(T)(scope ref inout(T)[] haystack,
388                        scope const T[] needleFront,
389                        scope const T[] needleBack) @trusted
390 {
391     if (!startsWith(haystack, needleFront) ||
392         !endsWith(haystack, needleBack))
393         return false;
394     haystack = haystack.ptr[needleFront.length .. haystack.length - needleBack.length];
395     return true;
396 }
397 /// ditto
398 bool skipOverAround(T)(scope ref inout(T)[] haystack,
399                        scope const T needleFront,
400                        scope const T needleBack) @trusted
401 {
402     if (!startsWith(haystack, needleFront) ||
403         !endsWith(haystack, needleBack))
404         return false;
405     haystack = haystack.ptr[1 .. haystack.length - 1];
406     return true;
407 }
408 
409 ///
410 @safe pure nothrow @nogc unittest
411 {
412     string x = "alpha beta_gamma";
413     assert(x.skipOverAround("alpha", "gamma"));
414     assert(x == " beta_");
415     assert(x.skipOverAround(' ', '_'));
416     assert(x == "beta");
417 }
418 
419 /** Array-specialization of `stripLeft` with default predicate.
420  *
421  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
422  */
423 inout(T)[] stripLeft(T)(scope return inout(T)[] haystack,
424                         scope const T needle) @trusted
425 {
426     static if (is(T == char))
427         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
428     size_t offset = 0;
429     while (offset != haystack.length &&
430            haystack.ptr[offset] == needle) // TODO: elide range-check
431         offset += 1;
432     return haystack.ptr[offset .. haystack.length];
433 }
434 /// ditto
435 inout(char)[] stripLeft()(scope return inout(char)[] haystack) @safe pure nothrow @nogc /* template-lazy */
436 {
437     return haystack.stripLeft(' ');
438 }
439 
440 ///
441 @safe pure nothrow @nogc unittest
442 {
443     assert("beta".stripLeft(' ') == "beta");
444     assert(" beta".stripLeft(' ') == "beta");
445     assert("  beta".stripLeft(' ') == "beta");
446     assert("   beta".stripLeft(' ') == "beta");
447     assert("   beta".stripLeft() == "beta");
448     assert(" _ beta _ ".stripLeft(' ') == "_ beta _ ");
449     assert(" _  beta _ ".stripLeft(' ') == "_  beta _ ");
450 
451     char[] f()() @safe pure nothrow { char[1] x = "_"; return x[].stripLeft(' '); }
452     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
453 }
454 
455 /** Array-specialization of `stripRight` with default predicate.
456  *
457  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
458  */
459 inout(T)[] stripRight(T)(scope return inout(T)[] haystack,
460                          scope const T needle) @trusted
461 {
462     static if (is(T == char))
463         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
464     size_t offset = haystack.length;
465     while (offset != 0 &&
466            haystack.ptr[offset - 1] == needle) // TODO: elide range-check
467         offset -= 1;
468     return haystack.ptr[0 .. offset];
469 }
470 /// ditto
471 inout(char)[] stripRight()(scope return inout(char)[] haystack) @safe pure nothrow @nogc /* template-lazy */
472 {
473     return haystack.stripRight(' ');
474 }
475 
476 ///
477 @safe pure nothrow @nogc unittest
478 {
479     assert("beta".stripRight(' ') == "beta");
480     assert("beta ".stripRight(' ') == "beta");
481     assert("beta  ".stripRight(' ') == "beta");
482     assert("beta    ".stripRight(' ') == "beta");
483     assert("beta    ".stripRight() == "beta");
484     assert(" _ beta _ ".stripRight(' ') == " _ beta _");
485     assert(" _  beta _ ".stripRight(' ') == " _  beta _");
486 
487     char[] f()() @safe pure nothrow { char[1] x = "_"; return x[].stripRight(' '); }
488     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
489 }
490 
491 /** Array-specialization of `strip` with default predicate.
492  *
493  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
494  */
495 inout(T)[] strip(T)(scope return inout(T)[] haystack,
496                     scope const T needle) @trusted
497 {
498     static if (is(T == char))
499         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
500 
501     size_t leftOffset = 0;
502     while (leftOffset != haystack.length &&
503            haystack.ptr[leftOffset] == needle) // TODO: elide range-check
504         leftOffset += 1;
505 
506     size_t rightOffset = haystack.length;
507     while (rightOffset != leftOffset &&
508            haystack.ptr[rightOffset - 1] == needle) // TODO: elide range-check
509         rightOffset -= 1;
510 
511     return haystack.ptr[leftOffset .. rightOffset];
512 }
513 /// ditto
514 inout(char)[] strip()(scope return inout(char)[] haystack) @safe pure nothrow @nogc /* template-lazy */
515 {
516     return haystack.strip(' ');
517 }
518 
519 ///
520 @safe pure nothrow @nogc unittest
521 {
522     assert("beta".strip(' ') == "beta");
523     assert(" beta ".strip(' ') == "beta");
524     assert("  beta  ".strip(' ') == "beta");
525     assert("   beta   ".strip(' ') == "beta");
526     assert(" _ beta _ ".strip(' ') == "_ beta _");
527     assert(" _  beta _ ".strip(' ') == "_  beta _");
528 
529     char[] f()() @safe pure nothrow { char[1] x = "_"; return x[].strip(' '); }
530     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
531 }
532 
533 ///
534 @safe pure nothrow @nogc unittest
535 {
536     const ubyte[3] x = [0, 42, 0];
537     assert(x.strip(0) == x[1 .. 2]);
538 }
539 
540 /** Array-specialization of `count` with default predicate.
541  *
542  * TODO: Add optimized implementation for needles with length >=
543  * `largeNeedleLength` with no repeat of elements.
544  *
545  * TODO: reuse `return haystack.indexOf(needle) != -1` in both overloads
546  */
547 bool canFind(T)(scope const T[] haystack,
548                 scope const T[] needle) @trusted
549 in(needle.length, "Cannot count occurrences of an empty range")
550 {
551     // enum largeNeedleLength = 4;
552     if (haystack.length < needle.length)
553         return false;
554     foreach (const offset; 0 .. haystack.length - needle.length + 1)
555         if (haystack.ptr[offset .. offset + needle.length] == needle)
556             return true;
557     return false;
558 }
559 /// ditto
560 bool canFind(T)(scope const T[] haystack,
561                 scope const T needle) @trusted
562 {
563     static if (is(T == char))
564         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
565     if (haystack.length == 0)
566         return false;
567     foreach (const ref element; haystack)
568         if (element == needle)
569             return true;
570     return false;
571 }
572 
573 ///
574 @safe pure nothrow @nogc unittest
575 {
576     assert(!"".canFind("_"));
577     assert(!"a".canFind("_"));
578     assert("a".canFind("a"));
579     assert(!"a".canFind("ab"));
580     assert("ab".canFind("a"));
581     assert("ab".canFind("b"));
582     assert("ab".canFind("ab"));
583     assert(!"a".canFind("ab"));
584     assert(!"b".canFind("ab"));
585 }
586 
587 ///
588 @safe pure nothrow @nogc unittest
589 {
590     assert(!"".canFind('_'));
591     assert(!"a".canFind('_'));
592     assert("a".canFind('a'));
593     assert("a".canFind('a'));
594     assert("ab".canFind('a'));
595     assert("ab".canFind('b'));
596 }
597 
598 /** Array-specialization of `count` with default predicate.
599  */
600 size_t count(T)(scope const T[] haystack,
601                 scope const T[] needle) @trusted
602 {
603     assert(needle.length, "Cannot count occurrences of an empty range");
604     size_t result = 0;
605     if (haystack.length < needle.length)
606         return false;
607     foreach (const offset; 0 .. haystack.length - needle.length + 1)
608         result += haystack.ptr[offset .. offset + needle.length] == needle ? 1 : 0;
609     return result;
610 }
611 /// ditto
612 size_t count(T)(scope const T[] haystack,
613                 scope const T needle)
614 {
615     static if (is(T == char))
616         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
617     size_t result;
618     foreach (const ref element; haystack)
619         result += element == needle ? 1 : 0;
620     return result;
621 }
622 
623 ///
624 @safe pure nothrow @nogc unittest
625 {
626     // import std.algorithm.searching : count;
627     assert("".count("_") == 0);
628     assert("".count(" ") == 0);
629     assert(" ".count(" ") == 1);
630     assert("abc_abc".count("a") == 2);
631     assert("abc_abc".count("abc") == 2);
632     assert("_a_a_".count("_") == 3);
633     assert("_aaa_".count("a") == 3);
634     // assert("".count("") == 0);
635     // assert("_a_a_".count("") == 5);
636 }
637 
638 ///
639 @safe pure nothrow @nogc unittest
640 {
641     assert("".count('_') == 0);
642     assert("abc_abc".count('a') == 2);
643     assert("_abc_abc_".count('_') == 3);
644 }
645 
646 /** Array-specialization of `count` with default predicate and no needle.
647  */
648 size_t count(T)(scope const T[] haystack) => haystack.length;
649 
650 ///
651 @safe pure nothrow @nogc unittest
652 {
653     assert("abc_abc".count == 7);
654 }
655 
656 /** Array-specialization of `indexOf` with default predicate.
657  *
658  * TODO: Add optimized implementation for needles with length >=
659  * `largeNeedleLength` with no repeat of elements.
660  */
661 ptrdiff_t indexOf(T)(scope inout(T)[] haystack,
662                      scope const(T)[] needle) @trusted
663 {
664     // enum largeNeedleLength = 4;
665     if (haystack.length < needle.length)
666         return -1;
667     foreach (const offset; 0 .. haystack.length - needle.length + 1)
668         if (haystack.ptr[offset .. offset + needle.length] == needle)
669             return offset;
670     return -1;
671 }
672 /// ditto
673 ptrdiff_t indexOf(T)(scope inout(T)[] haystack,
674                      scope const T needle)
675 {
676     static if (is(T == char))
677         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
678     foreach (const offset, const ref element; haystack)
679         if (element == needle)
680             return offset;
681     return -1;
682 }
683 
684 ///
685 @safe pure nothrow @nogc unittest
686 {
687     assert("_abc_abc_".indexOf("abc") == 1);
688     assert("__abc_".indexOf("abc") == 2);
689     assert("a".indexOf("a") == 0);
690     assert("abc".indexOf("abc") == 0);
691     assert("_".indexOf("a") == -1);
692     assert("_".indexOf("__") == -1);
693     assert("__".indexOf("a") == -1);
694 }
695 
696 ///
697 @safe pure nothrow @nogc unittest
698 {
699     assert("_".indexOf('a') == -1);
700     assert("a".indexOf('a') == 0);
701     assert("_a".indexOf('a') == 1);
702     assert("__a".indexOf('a') == 2);
703 }
704 
705 /// ditto
706 ptrdiff_t indexOfEither(T)(scope inout(T)[] haystack,
707                            scope const T[] needles)
708 {
709     if (needles.length == 0)
710         return -1;
711     foreach (const offset, const ref element; haystack)
712         foreach (const needle; needles)
713             if (element == needle)
714                 return offset;
715     return -1;
716 }
717 alias indexOfAny = indexOfEither; // Compliance with `std.string.indexOfAny`.
718 
719 ///
720 @safe pure nothrow @nogc unittest
721 {
722     assert("_".indexOfEither("a") == -1);
723     assert("_a".indexOfEither("a") == 1);
724     assert("_a".indexOfEither("ab") == 1);
725     assert("_b".indexOfEither("ab") == 1);
726     assert("_b".indexOfEither("_") == 0);
727     assert("_b".indexOfEither("xy") == -1);
728 }
729 
730 /** Array-specialization of `lastIndexOf` with default predicate.
731  */
732 ptrdiff_t lastIndexOf(T)(scope inout(T)[] haystack,
733                          scope const(T)[] needle) @trusted
734 {
735     if (haystack.length < needle.length)
736         return -1;
737     foreach_reverse (const offset; 0 .. haystack.length - needle.length + 1)
738         if (haystack.ptr[offset .. offset + needle.length] == needle)
739             return offset;
740     return -1;
741 }
742 /// ditto
743 ptrdiff_t lastIndexOf(T)(scope inout(T)[] haystack,
744                          scope const T needle)
745 {
746     static if (is(T == char))
747         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
748     foreach_reverse (const offset, const ref element; haystack)
749         if (element == needle)
750             return offset;
751     return -1;
752 }
753 
754 ///
755 @safe pure nothrow @nogc unittest
756 {
757     assert("_abc_abc_".lastIndexOf("abc") == 5);
758     assert("__abc_".lastIndexOf("abc") == 2);
759     assert("a".lastIndexOf("a") == 0);
760     assert("aa".lastIndexOf("a") == 1);
761     assert("abc".lastIndexOf("abc") == 0);
762     assert("_".lastIndexOf("a") == -1);
763     assert("_".lastIndexOf("__") == -1);
764     assert("__".lastIndexOf("a") == -1);
765 }
766 
767 ///
768 @safe pure nothrow @nogc unittest
769 {
770     assert("_".lastIndexOf('a') == -1);
771     assert("a".lastIndexOf('a') == 0);
772     assert("_a".lastIndexOf('a') == 1);
773     assert("__a".lastIndexOf('a') == 2);
774     assert("a__a".lastIndexOf('a') == 3);
775 }
776 
777 /** Array-specialization of `findSplit` with default predicate.
778  *
779  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
780  * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org
781  */
782 auto findSplit(T)(scope return inout(T)[] haystack,
783                   scope const(T)[] needle)
784 {
785     static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly
786     {
787         private T[] _haystack;
788         private size_t _offset; // hit offset
789         private size_t _length; // hit length
790     pragma(inline, true):
791         inout(T)[] pre() @trusted inout		  => _haystack.ptr[0 .. _offset];
792         inout(T)[] separator() @trusted inout => _haystack.ptr[_offset .. _offset + _length];
793         inout(T)[] post() @trusted inout	  => _haystack.ptr[_offset + _length .. _haystack.length];
794         bool opCast(T : bool)() @safe const	  => _haystack.length != _offset;
795     }
796 
797     assert(needle.length, "Cannot find occurrence of an empty range");
798     const index = haystack.indexOf(needle);
799     if (index >= 0)
800         return inout(Result)(haystack, index, needle.length);
801     return inout(Result)(haystack, haystack.length, 0); // miss
802 }
803 /// ditto
804 auto findSplit(T)(scope return inout(T)[] haystack,
805                   scope const T needle)
806 {
807     static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly
808     {
809         private T[] _haystack;
810         private size_t _offset; // hit offset
811     pragma(inline, true):
812         inout(T)[] pre() @trusted inout		  => _haystack.ptr[0 .. _offset];
813         inout(T)[] separator() @trusted inout => !empty ? _haystack.ptr[_offset .. _offset + 1] : _haystack[$ .. $];
814         inout(T)[] post() @trusted inout	  => !empty ? _haystack.ptr[_offset + 1 .. _haystack.length] : _haystack[$ .. $];
815         bool opCast(T : bool)() const		  => !empty;
816         private bool empty() const @property  => _haystack.length == _offset;
817     }
818 
819     static if (is(T == char))
820         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
821     const index = haystack.indexOf(needle);
822     if (index >= 0)
823         return inout(Result)(haystack, index);
824     return inout(Result)(haystack, haystack.length);
825 }
826 
827 ///
828 @safe pure nothrow @nogc unittest
829 {
830     const h = "a**b";
831     const r = h.findSplit("**");
832     assert(r);
833     assert(r.pre is h[0 .. 1]);
834     assert(r.separator is h[1 .. 3]);
835     assert(r.post is h[3 .. 4]);
836 
837     auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplit(" "); }
838     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
839 }
840 
841 ///
842 @safe pure nothrow @nogc unittest
843 {
844     const h = "a**b";
845     const r = h.findSplit("_");
846     static assert(r.sizeof == 2 * 2 * size_t.sizeof);
847     assert(!r);
848     assert(r.pre is h);
849     assert(r.separator is h[$ .. $]);
850     assert(r.post is h[$ .. $]);
851 }
852 
853 ///
854 version(none)
855 @safe pure nothrow @nogc unittest
856 {
857     import std.algorithm.searching : findSplit;
858     const h = "a**b";
859     const r = h.findSplit("_");
860     static assert(r.sizeof == 3 * 2 * size_t.sizeof);
861     assert(!r);
862     assert(r[0] is h);
863     assert(r[1] is h[$ .. $]);
864     assert(r[2] is h[$ .. $]);
865 }
866 
867 ///
868 @safe pure nothrow @nogc unittest
869 {
870     const r = "a*b".findSplit('*');
871     static assert(r.sizeof == 3 * size_t.sizeof);
872     assert(r);
873     assert(r.pre == "a");
874     assert(r.separator == "*");
875     assert(r.post == "b");
876 
877     auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplit(' '); }
878     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
879 }
880 
881 /// DIP-1000 scope analysis
882 version(none)					// TODO. enable
883 @safe pure nothrow @nogc unittest
884 {
885     char[] f() @safe pure nothrow
886     {
887         char[3] haystack = "a*b";
888         auto r = haystack[].findSplit('*');
889         static assert(is(typeof(r.pre()) == char[]));
890         return r.pre();         // TODO: this should fail
891     }
892     f();
893 }
894 
895 /** Array-specialization of `findLastSplit` with default predicate.
896  */
897 auto findLastSplit(T)(scope return inout(T)[] haystack,
898                       scope const(T)[] needle)
899 {
900     static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly
901     {
902         private T[] _haystack;
903         private size_t _offset; // hit offset
904         private size_t _length; // hit length
905     pragma(inline, true):
906         inout(T)[] pre() @trusted inout		  => _haystack.ptr[0 .. _offset];
907         inout(T)[] separator() @trusted inout => _haystack.ptr[_offset .. _offset + _length];
908         inout(T)[] post() @trusted inout	  => _haystack.ptr[_offset + _length .. _haystack.length];
909         bool opCast(T : bool)() @safe const	  => _haystack.length != _offset;
910     }
911 
912     assert(needle.length, "Cannot find occurrence of an empty range");
913     const index = haystack.lastIndexOf(needle);
914     if (index >= 0)
915         return inout(Result)(haystack, index, needle.length);
916     return inout(Result)(haystack, haystack.length, 0); // miss
917 }
918 /// ditto
919 auto findLastSplit(T)(scope return inout(T)[] haystack,
920                   scope const T needle)
921 {
922     static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly
923     {
924         private T[] _haystack;
925         private size_t _offset; // hit offset
926     pragma(inline, true):
927         inout(T)[] pre() @trusted inout		  => _haystack.ptr[0 .. _offset];
928         inout(T)[] separator() @trusted inout => !empty ? _haystack.ptr[_offset .. _offset + 1] : _haystack[$ .. $];
929         inout(T)[] post() @trusted inout	  => !empty ? _haystack.ptr[_offset + 1 .. _haystack.length] : _haystack[$ .. $];
930         bool opCast(T : bool)() const		  => !empty;
931         private bool empty() const @property  => _haystack.length == _offset;
932     }
933 
934     static if (is(T == char))
935         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
936     const index = haystack.lastIndexOf(needle);
937     if (index >= 0)
938         return inout(Result)(haystack, index);
939     return inout(Result)(haystack, haystack.length);
940 }
941 
942 ///
943 @safe pure nothrow @nogc unittest
944 {
945     const h = "a**b**c";
946     const r = h.findLastSplit("**");
947     assert(r);
948     assert(r.pre is h[0 .. 4]);
949     assert(r.separator is h[4 .. 6]);
950     assert(r.post is h[6 .. 7]);
951 
952     auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findLastSplit(" "); }
953     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
954 }
955 
956 ///
957 @safe pure nothrow @nogc unittest
958 {
959     const h = "a**b**c";
960     const r = h.findLastSplit("_");
961     static assert(r.sizeof == 2 * 2 * size_t.sizeof);
962     assert(!r);
963     assert(r.pre is h);
964     assert(r.separator is h[$ .. $]);
965     assert(r.post is h[$ .. $]);
966 }
967 
968 ///
969 @safe pure nothrow @nogc unittest
970 {
971     const r = "a*b*c".findLastSplit('*');
972     static assert(r.sizeof == 3 * size_t.sizeof);
973     assert(r);
974     assert(r.pre == "a*b");
975     assert(r.separator == "*");
976     assert(r.post == "c");
977 
978     auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findLastSplit(' '); }
979     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
980 }
981 
982 /// DIP-1000 scope analysis
983 version(none)					// TODO: enable
984 @safe pure nothrow @nogc unittest
985 {
986     char[] f() @safe pure nothrow
987     {
988         char[3] haystack = "a*b";
989         auto r = haystack[].findLastSplit('*');
990         static assert(is(typeof(r.pre()) == char[]));
991         return r.pre();         // TODO: this should fail
992     }
993     f();
994 }
995 
996 /** Array-specialization of `findSplitBefore` with default predicate.
997  *
998  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
999  * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org
1000  */
1001 auto findSplitBefore(T)(scope return inout(T)[] haystack,
1002                         scope const T needle)
1003 {
1004     static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly
1005     {
1006         private T[] _haystack;
1007         private size_t _offset;
1008     pragma(inline, true):
1009         inout(T)[] pre() @trusted inout		 => _haystack.ptr[0 .. _offset];
1010         inout(T)[] post() @trusted inout	 => !empty ? _haystack.ptr[_offset .. _haystack.length] : _haystack[$ .. $];
1011         bool opCast(T : bool)() const		 => !empty;
1012         private bool empty() const @property => _haystack.length == _offset;
1013     }
1014 
1015     static if (is(T == char))
1016         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
1017     foreach (const offset, const ref element; haystack)
1018         if (element == needle)
1019             return inout(Result)(haystack, offset);
1020     return inout(Result)(haystack, haystack.length);
1021 }
1022 
1023 ///
1024 @safe pure nothrow @nogc unittest
1025 {
1026     char[] haystack;
1027     auto r = haystack.findSplitBefore('_');
1028     static assert(is(typeof(r.pre()) == char[]));
1029     static assert(is(typeof(r.post()) == char[]));
1030     assert(!r);
1031     assert(!r.pre);
1032     assert(!r.post);
1033 
1034     auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplitBefore(' '); }
1035     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
1036 }
1037 
1038 ///
1039 @safe pure nothrow @nogc unittest
1040 {
1041     const(char)[] haystack;
1042     auto r = haystack.findSplitBefore('_');
1043     static assert(is(typeof(r.pre()) == const(char)[]));
1044     static assert(is(typeof(r.post()) == const(char)[]));
1045     assert(!r);
1046     assert(!r.pre);
1047     assert(!r.post);
1048 }
1049 
1050 ///
1051 @safe pure nothrow @nogc unittest
1052 {
1053     const r = "a*b".findSplitBefore('*');
1054     assert(r);
1055     assert(r.pre == "a");
1056     assert(r.post == "*b");
1057 }
1058 
1059 ///
1060 @safe pure nothrow @nogc unittest
1061 {
1062     const r = "a*b".findSplitBefore('_');
1063     assert(!r);
1064     assert(r.pre == "a*b");
1065     assert(r.post == "");
1066 }
1067 
1068 /** Array-specialization of `findSplitBefore` with explicit needle-only predicate `needlePred`.
1069  *
1070  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
1071  * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org
1072  */
1073 auto findSplitBefore(alias needlePred, T)(scope return inout(T)[] haystack)
1074 {
1075     static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly
1076     {
1077         private T[] _haystack;
1078         private size_t _offset;
1079     pragma(inline, true):
1080         inout(T)[] pre() @trusted inout		 => _haystack.ptr[0 .. _offset];
1081         inout(T)[] post() @trusted inout	 => !empty ? _haystack.ptr[_offset .. _haystack.length] : _haystack[$ .. $];
1082         bool opCast(T : bool)() const		 => !empty;
1083         private bool empty() const @property => _haystack.length == _offset;
1084     }
1085 
1086     foreach (const offset, const ref element; haystack)
1087         if (needlePred(element))
1088             return inout(Result)(haystack, offset);
1089     return inout(Result)(haystack, haystack.length);
1090 }
1091 
1092 ///
1093 @safe pure nothrow @nogc unittest
1094 {
1095     char[] haystack;
1096     auto r = haystack.findSplitBefore!(_ => _ == '_');
1097     static assert(is(typeof(r.pre()) == char[]));
1098     static assert(is(typeof(r.post()) == char[]));
1099     assert(!r);
1100     assert(!r.pre);
1101     assert(!r.post);
1102 
1103     auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplitBefore!(_ => _ == ' '); }
1104     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
1105 }
1106 
1107 ///
1108 @safe pure nothrow @nogc unittest
1109 {
1110     const(char)[] haystack;
1111     auto r = haystack.findSplitBefore!(_ => _ == '_');
1112     static assert(is(typeof(r.pre()) == const(char)[]));
1113     static assert(is(typeof(r.post()) == const(char)[]));
1114     assert(!r);
1115     assert(!r.pre);
1116     assert(!r.post);
1117 }
1118 
1119 ///
1120 @safe pure nothrow @nogc unittest
1121 {
1122     const r = "a*b".findSplitBefore!(_ => _ == '*');
1123     assert(r);
1124     assert(r.pre == "a");
1125     assert(r.post == "*b");
1126 }
1127 
1128 ///
1129 @safe pure nothrow @nogc unittest
1130 {
1131     const r = "a*b".findSplitBefore!(_ => _ == '*' || _ == '+');
1132     assert(r);
1133     assert(r.pre == "a");
1134     assert(r.post == "*b");
1135 }
1136 
1137 ///
1138 @safe pure nothrow @nogc unittest
1139 {
1140     const r = "a+b".findSplitBefore!(_ => _ == '*' || _ == '+');
1141     assert(r);
1142     assert(r.pre == "a");
1143     assert(r.post == "+b");
1144 }
1145 
1146 ///
1147 @safe pure nothrow @nogc unittest
1148 {
1149     const r = "a*b".findSplitBefore!(_ => _ == '_');
1150     assert(!r);
1151     assert(r.pre == "a*b");
1152     assert(r.post == "");
1153 }
1154 
1155 /** Array-specialization of `findSplitAfter` with default predicate.
1156  *
1157  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
1158  * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org
1159  */
1160 auto findSplitAfter(T)(scope return inout(T)[] haystack,
1161                        scope const T needle) @trusted
1162 {
1163     static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly
1164     {
1165         private T[] _haystack;
1166         private size_t _offset;
1167     pragma(inline, true):
1168         inout(T)[] pre() @trusted inout		 => !empty ? _haystack.ptr[0 .. _offset + 1] : _haystack[$ .. $];
1169         inout(T)[] post() @trusted inout	 => !empty ? _haystack.ptr[_offset + 1 .. _haystack.length] : _haystack[0 .. $];
1170         bool opCast(T : bool)() const		 => !empty;
1171         private bool empty() const @property => _haystack.length == _offset;
1172     }
1173 
1174     static if (is(T == char))
1175         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
1176     foreach (const offset, const ref element; haystack)
1177         if (element == needle)
1178             return inout(Result)(haystack, offset);
1179     return inout(Result)(haystack, haystack.length);
1180 }
1181 
1182 ///
1183 @safe pure nothrow @nogc unittest
1184 {
1185     char[] haystack;
1186     auto r = haystack.findSplitAfter('_');
1187     static assert(is(typeof(r.pre()) == char[]));
1188     static assert(is(typeof(r.post()) == char[]));
1189     assert(!r);
1190     assert(!r.pre);
1191     assert(!r.post);
1192 
1193     auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplitAfter(' '); }
1194     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
1195 }
1196 
1197 ///
1198 @safe pure nothrow @nogc unittest
1199 {
1200     const(char)[] haystack;
1201     auto r = haystack.findSplitAfter('_');
1202     static assert(is(typeof(r.pre()) == const(char)[]));
1203     static assert(is(typeof(r.post()) == const(char)[]));
1204     assert(!r);
1205     assert(!r.pre);
1206     assert(!r.post);
1207 }
1208 
1209 ///
1210 @safe pure nothrow @nogc unittest
1211 {
1212     auto r = "a*b".findSplitAfter('*');
1213     static assert(is(typeof(r.pre()) == string));
1214     static assert(is(typeof(r.post()) == string));
1215     assert(r);
1216     assert(r.pre == "a*");
1217     assert(r.post == "b");
1218 }
1219 
1220 /** Array-specialization of `findLastSplitAfter` with default predicate.
1221  *
1222  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
1223  * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org
1224  */
1225 auto findLastSplitAfter(T)(scope return inout(T)[] haystack,
1226                            scope const T needle) @trusted
1227 {
1228     static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly
1229     {
1230         private T[] _haystack;
1231         private size_t _offset;
1232     pragma(inline, true):
1233         inout(T)[] pre() @trusted inout		 => !empty ? _haystack.ptr[0 .. _offset + 1] : _haystack[$ .. $];
1234         inout(T)[] post() @trusted inout	 => !empty ? _haystack.ptr[_offset + 1 .. _haystack.length] : _haystack[0 .. $];
1235         bool opCast(T : bool)() const		 => !empty;
1236         private bool empty() const @property => _haystack.length == _offset;
1237     }
1238 
1239     static if (is(T == char))
1240         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
1241     const index = haystack.lastIndexOf(needle);
1242     if (index >= 0)
1243         return inout(Result)(haystack, index);
1244     return inout(Result)(haystack, haystack.length); // miss
1245 }
1246 
1247 ///
1248 @safe pure nothrow @nogc unittest
1249 {
1250     char[] haystack;
1251     auto r = haystack.findLastSplitAfter('_');
1252     static assert(is(typeof(r.pre()) == char[]));
1253     static assert(is(typeof(r.post()) == char[]));
1254     assert(!r);
1255     assert(!r.pre);
1256     assert(!r.post);
1257 
1258     auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findLastSplitAfter(' '); }
1259     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
1260 }
1261 
1262 ///
1263 @safe pure nothrow @nogc unittest
1264 {
1265     const(char)[] haystack;
1266     auto r = haystack.findLastSplitAfter('_');
1267     static assert(is(typeof(r.pre()) == const(char)[]));
1268     static assert(is(typeof(r.post()) == const(char)[]));
1269     assert(!r);
1270     assert(!r.pre);
1271     assert(!r.post);
1272 }
1273 
1274 ///
1275 @safe pure nothrow @nogc unittest
1276 {
1277     auto r = "a*b*c".findLastSplitAfter('*');
1278     static assert(is(typeof(r.pre()) == string));
1279     static assert(is(typeof(r.post()) == string));
1280     assert(r);
1281     assert(r.pre == "a*b*");
1282     assert(r.post == "c");
1283 }
1284 
1285 import std.range.primitives : isInputRange, isInfinite, ElementType;
1286 
1287 // Use until `std.algorithm.comparison.equal` supports uncopyable parameters.
1288 bool equal(T, U)(scope T a, scope U b)
1289 if (!(isInfinite!T &&
1290 	  isInfinite!U))
1291 {
1292 	static if ((is(T == TE[m], TE, size_t m)) &&
1293 			   (is(U == UE[n], UE, size_t n)) &&
1294 			   is(typeof(TE.init == UE.init) : bool)) // static array
1295 	{
1296 		static if (m != n)
1297 			return false;
1298 		else
1299 		{
1300 			foreach (const i; 0 .. m)
1301 				if (a[i] != b[i])
1302 					return false;
1303 			return true;
1304 		}
1305 	}
1306 	else static if ((is(T == TA[], TA)) &&
1307 					(is(U == UA[], UA)) &&
1308 					is(typeof(TA.init == UA.init) : bool)) // dynamic array
1309 	{
1310 		if (a.length != b.length)
1311 			return false;
1312 		const n = a.length;
1313 		foreach (const i; 0 .. n)
1314 			if (a[i] != b[i])
1315 				return false;
1316 		return true;
1317 	}
1318 	else static if (is(typeof(ElementType!T.init == ElementType!U.init) : bool))
1319 	{
1320 		static if (is(typeof(a[size_t.init])))
1321 		{
1322 			import std.algorithm.mutation : move;
1323 			size_t i = 0;
1324 			foreach (const ref be; move(b))
1325 				if (a[i++] != be)
1326 					return false;
1327 			return true;
1328 		}
1329 		else static if (is(typeof(b[size_t.init])))
1330 		{
1331 			import std.algorithm.mutation : move;
1332 			size_t i = 0;
1333 			foreach (const ref ae; move(a))
1334 				if (ae != b[i++])
1335 					return false;
1336 			return true;
1337 		}
1338 		else
1339 		{
1340 			while (true)
1341 			{
1342 				if (a.empty())
1343 					return b.empty();
1344 				if (b.empty())
1345 					return a.empty();
1346 				if (a.front != b.front)
1347 					return false;
1348 				a.popFront();
1349 				b.popFront();
1350 			}
1351 		}
1352 	}
1353 	else
1354 		static assert(0, "Cannot compare " ~ T.stringof ~ "with" ~ U.stringof);
1355 }
1356 /// ditto
1357 bool equal(T, U)(scope const(T)[] a, scope const(U)[] b)
1358 if (is(typeof(T.init == U.init) : bool))
1359 {
1360 }
1361 
1362 /// dynamic arrays
1363 @safe pure nothrow @nogc unittest
1364 {
1365 	assert(!equal([1, 2   ].s[], [1, 2, 3].s[]));
1366 	assert(!equal([1, 2, 3].s[], [1, 2,  ].s[]));
1367 	assert( equal([1, 2, 3].s[], [1, 2, 3].s[]));
1368 }
1369 
1370 /// static arrays
1371 @safe pure nothrow @nogc unittest
1372 {
1373 	assert(!equal([1, 2   ].s, [1, 2, 3].s));
1374 	assert(!equal([1, 2, 3].s, [1, 2,  ].s));
1375 	assert( equal([1, 2, 3].s, [1, 2, 3].s));
1376 }
1377 
1378 version(unittest)
1379 {
1380     import nxt.dip_traits : hasPreviewDIP1000;
1381 	import nxt.array_help : s;
1382 }
1383 
1384 // See_Also: https://dlang.org/spec/betterc.html#unittests
1385 version(unittestAsBetterC)
1386 extern(C) void main()
1387 {
1388     static foreach (u; __traits(getUnitTests, __traits(parent, main)))
1389     {
1390         u();
1391     }
1392 }