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 
718 ///
719 @safe pure nothrow @nogc unittest
720 {
721     assert("_".indexOfEither("a") == -1);
722     assert("_a".indexOfEither("a") == 1);
723     assert("_a".indexOfEither("ab") == 1);
724     assert("_b".indexOfEither("ab") == 1);
725     assert("_b".indexOfEither("_") == 0);
726     assert("_b".indexOfEither("xy") == -1);
727 }
728 
729 /** Array-specialization of `lastIndexOf` with default predicate.
730  */
731 ptrdiff_t lastIndexOf(T)(scope inout(T)[] haystack,
732                          scope const(T)[] needle) @trusted
733 {
734     if (haystack.length < needle.length)
735         return -1;
736     foreach_reverse (const offset; 0 .. haystack.length - needle.length + 1)
737         if (haystack.ptr[offset .. offset + needle.length] == needle)
738             return offset;
739     return -1;
740 }
741 /// ditto
742 ptrdiff_t lastIndexOf(T)(scope inout(T)[] haystack,
743                          scope const T needle)
744 {
745     static if (is(T == char))
746         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
747     foreach_reverse (const offset, const ref element; haystack)
748         if (element == needle)
749             return offset;
750     return -1;
751 }
752 
753 ///
754 @safe pure nothrow @nogc unittest
755 {
756     assert("_abc_abc_".lastIndexOf("abc") == 5);
757     assert("__abc_".lastIndexOf("abc") == 2);
758     assert("a".lastIndexOf("a") == 0);
759     assert("aa".lastIndexOf("a") == 1);
760     assert("abc".lastIndexOf("abc") == 0);
761     assert("_".lastIndexOf("a") == -1);
762     assert("_".lastIndexOf("__") == -1);
763     assert("__".lastIndexOf("a") == -1);
764 }
765 
766 ///
767 @safe pure nothrow @nogc unittest
768 {
769     assert("_".lastIndexOf('a') == -1);
770     assert("a".lastIndexOf('a') == 0);
771     assert("_a".lastIndexOf('a') == 1);
772     assert("__a".lastIndexOf('a') == 2);
773     assert("a__a".lastIndexOf('a') == 3);
774 }
775 
776 /** Array-specialization of `findSplit` with default predicate.
777  *
778  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
779  * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org
780  */
781 auto findSplit(T)(scope return inout(T)[] haystack,
782                   scope const(T)[] needle)
783 {
784     static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly
785     {
786         private T[] _haystack;
787         private size_t _offset; // hit offset
788         private size_t _length; // hit length
789     pragma(inline, true):
790         inout(T)[] pre() @trusted inout		  => _haystack.ptr[0 .. _offset];
791         inout(T)[] separator() @trusted inout => _haystack.ptr[_offset .. _offset + _length];
792         inout(T)[] post() @trusted inout	  => _haystack.ptr[_offset + _length .. _haystack.length];
793         bool opCast(T : bool)() @safe const	  => _haystack.length != _offset;
794     }
795 
796     assert(needle.length, "Cannot find occurrence of an empty range");
797     const index = haystack.indexOf(needle);
798     if (index >= 0)
799         return inout(Result)(haystack, index, needle.length);
800     return inout(Result)(haystack, haystack.length, 0); // miss
801 }
802 /// ditto
803 auto findSplit(T)(scope return inout(T)[] haystack,
804                   scope const T needle)
805 {
806     static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly
807     {
808         private T[] _haystack;
809         private size_t _offset; // hit offset
810     pragma(inline, true):
811         inout(T)[] pre() @trusted inout		  => _haystack.ptr[0 .. _offset];
812         inout(T)[] separator() @trusted inout => !empty ? _haystack.ptr[_offset .. _offset + 1] : _haystack[$ .. $];
813         inout(T)[] post() @trusted inout	  => !empty ? _haystack.ptr[_offset + 1 .. _haystack.length] : _haystack[$ .. $];
814         bool opCast(T : bool)() const		  => !empty;
815         private bool empty() const @property  => _haystack.length == _offset;
816     }
817 
818     static if (is(T == char))
819         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
820     const index = haystack.indexOf(needle);
821     if (index >= 0)
822         return inout(Result)(haystack, index);
823     return inout(Result)(haystack, haystack.length);
824 }
825 
826 ///
827 @safe pure nothrow @nogc unittest
828 {
829     const h = "a**b";
830     const r = h.findSplit("**");
831     assert(r);
832     assert(r.pre is h[0 .. 1]);
833     assert(r.separator is h[1 .. 3]);
834     assert(r.post is h[3 .. 4]);
835 
836     auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplit(" "); }
837     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
838 }
839 
840 ///
841 @safe pure nothrow @nogc unittest
842 {
843     const h = "a**b";
844     const r = h.findSplit("_");
845     static assert(r.sizeof == 2 * 2 * size_t.sizeof);
846     assert(!r);
847     assert(r.pre is h);
848     assert(r.separator is h[$ .. $]);
849     assert(r.post is h[$ .. $]);
850 }
851 
852 ///
853 version(none)
854 @safe pure nothrow @nogc unittest
855 {
856     import std.algorithm.searching : findSplit;
857     const h = "a**b";
858     const r = h.findSplit("_");
859     static assert(r.sizeof == 3 * 2 * size_t.sizeof);
860     assert(!r);
861     assert(r[0] is h);
862     assert(r[1] is h[$ .. $]);
863     assert(r[2] is h[$ .. $]);
864 }
865 
866 ///
867 @safe pure nothrow @nogc unittest
868 {
869     const r = "a*b".findSplit('*');
870     static assert(r.sizeof == 3 * size_t.sizeof);
871     assert(r);
872     assert(r.pre == "a");
873     assert(r.separator == "*");
874     assert(r.post == "b");
875 
876     auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplit(' '); }
877     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
878 }
879 
880 /// DIP-1000 scope analysis
881 version(none)					// TODO. enable
882 @safe pure nothrow @nogc unittest
883 {
884     char[] f() @safe pure nothrow
885     {
886         char[3] haystack = "a*b";
887         auto r = haystack[].findSplit('*');
888         static assert(is(typeof(r.pre()) == char[]));
889         return r.pre();         // TODO: this should fail
890     }
891     f();
892 }
893 
894 /** Array-specialization of `findLastSplit` with default predicate.
895  */
896 auto findLastSplit(T)(scope return inout(T)[] haystack,
897                       scope const(T)[] needle)
898 {
899     static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly
900     {
901         private T[] _haystack;
902         private size_t _offset; // hit offset
903         private size_t _length; // hit length
904     pragma(inline, true):
905         inout(T)[] pre() @trusted inout		  => _haystack.ptr[0 .. _offset];
906         inout(T)[] separator() @trusted inout => _haystack.ptr[_offset .. _offset + _length];
907         inout(T)[] post() @trusted inout	  => _haystack.ptr[_offset + _length .. _haystack.length];
908         bool opCast(T : bool)() @safe const	  => _haystack.length != _offset;
909     }
910 
911     assert(needle.length, "Cannot find occurrence of an empty range");
912     const index = haystack.lastIndexOf(needle);
913     if (index >= 0)
914         return inout(Result)(haystack, index, needle.length);
915     return inout(Result)(haystack, haystack.length, 0); // miss
916 }
917 /// ditto
918 auto findLastSplit(T)(scope return inout(T)[] haystack,
919                   scope const T needle)
920 {
921     static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly
922     {
923         private T[] _haystack;
924         private size_t _offset; // hit offset
925     pragma(inline, true):
926         inout(T)[] pre() @trusted inout		  => _haystack.ptr[0 .. _offset];
927         inout(T)[] separator() @trusted inout => !empty ? _haystack.ptr[_offset .. _offset + 1] : _haystack[$ .. $];
928         inout(T)[] post() @trusted inout	  => !empty ? _haystack.ptr[_offset + 1 .. _haystack.length] : _haystack[$ .. $];
929         bool opCast(T : bool)() const		  => !empty;
930         private bool empty() const @property  => _haystack.length == _offset;
931     }
932 
933     static if (is(T == char))
934         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
935     const index = haystack.lastIndexOf(needle);
936     if (index >= 0)
937         return inout(Result)(haystack, index);
938     return inout(Result)(haystack, haystack.length);
939 }
940 
941 ///
942 @safe pure nothrow @nogc unittest
943 {
944     const h = "a**b**c";
945     const r = h.findLastSplit("**");
946     assert(r);
947     assert(r.pre is h[0 .. 4]);
948     assert(r.separator is h[4 .. 6]);
949     assert(r.post is h[6 .. 7]);
950 
951     auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findLastSplit(" "); }
952     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
953 }
954 
955 ///
956 @safe pure nothrow @nogc unittest
957 {
958     const h = "a**b**c";
959     const r = h.findLastSplit("_");
960     static assert(r.sizeof == 2 * 2 * size_t.sizeof);
961     assert(!r);
962     assert(r.pre is h);
963     assert(r.separator is h[$ .. $]);
964     assert(r.post is h[$ .. $]);
965 }
966 
967 ///
968 @safe pure nothrow @nogc unittest
969 {
970     const r = "a*b*c".findLastSplit('*');
971     static assert(r.sizeof == 3 * size_t.sizeof);
972     assert(r);
973     assert(r.pre == "a*b");
974     assert(r.separator == "*");
975     assert(r.post == "c");
976 
977     auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findLastSplit(' '); }
978     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
979 }
980 
981 /// DIP-1000 scope analysis
982 version(none)					// TODO: enable
983 @safe pure nothrow @nogc unittest
984 {
985     char[] f() @safe pure nothrow
986     {
987         char[3] haystack = "a*b";
988         auto r = haystack[].findLastSplit('*');
989         static assert(is(typeof(r.pre()) == char[]));
990         return r.pre();         // TODO: this should fail
991     }
992     f();
993 }
994 
995 /** Array-specialization of `findSplitBefore` with default predicate.
996  *
997  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
998  * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org
999  */
1000 auto findSplitBefore(T)(scope return inout(T)[] haystack,
1001                         scope const T needle)
1002 {
1003     static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly
1004     {
1005         private T[] _haystack;
1006         private size_t _offset;
1007     pragma(inline, true):
1008         inout(T)[] pre() @trusted inout		 => _haystack.ptr[0 .. _offset];
1009         inout(T)[] post() @trusted inout	 => !empty ? _haystack.ptr[_offset .. _haystack.length] : _haystack[$ .. $];
1010         bool opCast(T : bool)() const		 => !empty;
1011         private bool empty() const @property => _haystack.length == _offset;
1012     }
1013 
1014     static if (is(T == char))
1015         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
1016     foreach (const offset, const ref element; haystack)
1017         if (element == needle)
1018             return inout(Result)(haystack, offset);
1019     return inout(Result)(haystack, haystack.length);
1020 }
1021 
1022 ///
1023 @safe pure nothrow @nogc unittest
1024 {
1025     char[] haystack;
1026     auto r = haystack.findSplitBefore('_');
1027     static assert(is(typeof(r.pre()) == char[]));
1028     static assert(is(typeof(r.post()) == char[]));
1029     assert(!r);
1030     assert(!r.pre);
1031     assert(!r.post);
1032 
1033     auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplitBefore(' '); }
1034     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
1035 }
1036 
1037 ///
1038 @safe pure nothrow @nogc unittest
1039 {
1040     const(char)[] haystack;
1041     auto r = haystack.findSplitBefore('_');
1042     static assert(is(typeof(r.pre()) == const(char)[]));
1043     static assert(is(typeof(r.post()) == const(char)[]));
1044     assert(!r);
1045     assert(!r.pre);
1046     assert(!r.post);
1047 }
1048 
1049 ///
1050 @safe pure nothrow @nogc unittest
1051 {
1052     const r = "a*b".findSplitBefore('*');
1053     assert(r);
1054     assert(r.pre == "a");
1055     assert(r.post == "*b");
1056 }
1057 
1058 ///
1059 @safe pure nothrow @nogc unittest
1060 {
1061     const r = "a*b".findSplitBefore('_');
1062     assert(!r);
1063     assert(r.pre == "a*b");
1064     assert(r.post == "");
1065 }
1066 
1067 /** Array-specialization of `findSplitBefore` with explicit needle-only predicate `needlePred`.
1068  *
1069  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
1070  * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org
1071  */
1072 auto findSplitBefore(alias needlePred, T)(scope return inout(T)[] haystack)
1073 {
1074     static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly
1075     {
1076         private T[] _haystack;
1077         private size_t _offset;
1078     pragma(inline, true):
1079         inout(T)[] pre() @trusted inout		 => _haystack.ptr[0 .. _offset];
1080         inout(T)[] post() @trusted inout	 => !empty ? _haystack.ptr[_offset .. _haystack.length] : _haystack[$ .. $];
1081         bool opCast(T : bool)() const		 => !empty;
1082         private bool empty() const @property => _haystack.length == _offset;
1083     }
1084 
1085     foreach (const offset, const ref element; haystack)
1086         if (needlePred(element))
1087             return inout(Result)(haystack, offset);
1088     return inout(Result)(haystack, haystack.length);
1089 }
1090 
1091 ///
1092 @safe pure nothrow @nogc unittest
1093 {
1094     char[] haystack;
1095     auto r = haystack.findSplitBefore!(_ => _ == '_');
1096     static assert(is(typeof(r.pre()) == char[]));
1097     static assert(is(typeof(r.post()) == char[]));
1098     assert(!r);
1099     assert(!r.pre);
1100     assert(!r.post);
1101 
1102     auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplitBefore!(_ => _ == ' '); }
1103     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
1104 }
1105 
1106 ///
1107 @safe pure nothrow @nogc unittest
1108 {
1109     const(char)[] haystack;
1110     auto r = haystack.findSplitBefore!(_ => _ == '_');
1111     static assert(is(typeof(r.pre()) == const(char)[]));
1112     static assert(is(typeof(r.post()) == const(char)[]));
1113     assert(!r);
1114     assert(!r.pre);
1115     assert(!r.post);
1116 }
1117 
1118 ///
1119 @safe pure nothrow @nogc unittest
1120 {
1121     const r = "a*b".findSplitBefore!(_ => _ == '*');
1122     assert(r);
1123     assert(r.pre == "a");
1124     assert(r.post == "*b");
1125 }
1126 
1127 ///
1128 @safe pure nothrow @nogc unittest
1129 {
1130     const r = "a*b".findSplitBefore!(_ => _ == '*' || _ == '+');
1131     assert(r);
1132     assert(r.pre == "a");
1133     assert(r.post == "*b");
1134 }
1135 
1136 ///
1137 @safe pure nothrow @nogc unittest
1138 {
1139     const r = "a+b".findSplitBefore!(_ => _ == '*' || _ == '+');
1140     assert(r);
1141     assert(r.pre == "a");
1142     assert(r.post == "+b");
1143 }
1144 
1145 ///
1146 @safe pure nothrow @nogc unittest
1147 {
1148     const r = "a*b".findSplitBefore!(_ => _ == '_');
1149     assert(!r);
1150     assert(r.pre == "a*b");
1151     assert(r.post == "");
1152 }
1153 
1154 /** Array-specialization of `findSplitAfter` with default predicate.
1155  *
1156  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
1157  * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org
1158  */
1159 auto findSplitAfter(T)(scope return inout(T)[] haystack,
1160                        scope const T needle) @trusted
1161 {
1162     static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly
1163     {
1164         private T[] _haystack;
1165         private size_t _offset;
1166     pragma(inline, true):
1167         inout(T)[] pre() @trusted inout		 => !empty ? _haystack.ptr[0 .. _offset + 1] : _haystack[$ .. $];
1168         inout(T)[] post() @trusted inout	 => !empty ? _haystack.ptr[_offset + 1 .. _haystack.length] : _haystack[0 .. $];
1169         bool opCast(T : bool)() const		 => !empty;
1170         private bool empty() const @property => _haystack.length == _offset;
1171     }
1172 
1173     static if (is(T == char))
1174         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
1175     foreach (const offset, const ref element; haystack)
1176         if (element == needle)
1177             return inout(Result)(haystack, offset);
1178     return inout(Result)(haystack, haystack.length);
1179 }
1180 
1181 ///
1182 @safe pure nothrow @nogc unittest
1183 {
1184     char[] haystack;
1185     auto r = haystack.findSplitAfter('_');
1186     static assert(is(typeof(r.pre()) == char[]));
1187     static assert(is(typeof(r.post()) == char[]));
1188     assert(!r);
1189     assert(!r.pre);
1190     assert(!r.post);
1191 
1192     auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplitAfter(' '); }
1193     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
1194 }
1195 
1196 ///
1197 @safe pure nothrow @nogc unittest
1198 {
1199     const(char)[] haystack;
1200     auto r = haystack.findSplitAfter('_');
1201     static assert(is(typeof(r.pre()) == const(char)[]));
1202     static assert(is(typeof(r.post()) == const(char)[]));
1203     assert(!r);
1204     assert(!r.pre);
1205     assert(!r.post);
1206 }
1207 
1208 ///
1209 @safe pure nothrow @nogc unittest
1210 {
1211     auto r = "a*b".findSplitAfter('*');
1212     static assert(is(typeof(r.pre()) == string));
1213     static assert(is(typeof(r.post()) == string));
1214     assert(r);
1215     assert(r.pre == "a*");
1216     assert(r.post == "b");
1217 }
1218 
1219 /** Array-specialization of `findLastSplitAfter` with default predicate.
1220  *
1221  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
1222  * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org
1223  */
1224 auto findLastSplitAfter(T)(scope return inout(T)[] haystack,
1225                            scope const T needle) @trusted
1226 {
1227     static struct Result // NOTE `static` qualifier is needed for `inout` to propagate correctly
1228     {
1229         private T[] _haystack;
1230         private size_t _offset;
1231     pragma(inline, true):
1232         inout(T)[] pre() @trusted inout		 => !empty ? _haystack.ptr[0 .. _offset + 1] : _haystack[$ .. $];
1233         inout(T)[] post() @trusted inout	 => !empty ? _haystack.ptr[_offset + 1 .. _haystack.length] : _haystack[0 .. $];
1234         bool opCast(T : bool)() const		 => !empty;
1235         private bool empty() const @property => _haystack.length == _offset;
1236     }
1237 
1238     static if (is(T == char))
1239         assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
1240     const index = haystack.lastIndexOf(needle);
1241     if (index >= 0)
1242         return inout(Result)(haystack, index);
1243     return inout(Result)(haystack, haystack.length); // miss
1244 }
1245 
1246 ///
1247 @safe pure nothrow @nogc unittest
1248 {
1249     char[] haystack;
1250     auto r = haystack.findLastSplitAfter('_');
1251     static assert(is(typeof(r.pre()) == char[]));
1252     static assert(is(typeof(r.post()) == char[]));
1253     assert(!r);
1254     assert(!r.pre);
1255     assert(!r.post);
1256 
1257     auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findLastSplitAfter(' '); }
1258     static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
1259 }
1260 
1261 ///
1262 @safe pure nothrow @nogc unittest
1263 {
1264     const(char)[] haystack;
1265     auto r = haystack.findLastSplitAfter('_');
1266     static assert(is(typeof(r.pre()) == const(char)[]));
1267     static assert(is(typeof(r.post()) == const(char)[]));
1268     assert(!r);
1269     assert(!r.pre);
1270     assert(!r.post);
1271 }
1272 
1273 ///
1274 @safe pure nothrow @nogc unittest
1275 {
1276     auto r = "a*b*c".findLastSplitAfter('*');
1277     static assert(is(typeof(r.pre()) == string));
1278     static assert(is(typeof(r.post()) == string));
1279     assert(r);
1280     assert(r.pre == "a*b*");
1281     assert(r.post == "c");
1282 }
1283 
1284 import std.range.primitives : isInputRange, isInfinite, ElementType;
1285 
1286 // Use until `std.algorithm.comparison.equal` supports uncopyable parameters.
1287 bool equal(T, U)(scope T a, scope U b)
1288 if (!(isInfinite!T &&
1289 	  isInfinite!U))
1290 {
1291 	static if ((is(T == TE[m], TE, size_t m)) &&
1292 			   (is(U == UE[n], UE, size_t n)) &&
1293 			   is(typeof(TE.init == UE.init) : bool)) // static array
1294 	{
1295 		static if (m != n)
1296 			return false;
1297 		else
1298 		{
1299 			foreach (const i; 0 .. m)
1300 				if (a[i] != b[i])
1301 					return false;
1302 			return true;
1303 		}
1304 	}
1305 	else static if ((is(T == TA[], TA)) &&
1306 					(is(U == UA[], UA)) &&
1307 					is(typeof(TA.init == UA.init) : bool)) // dynamic array
1308 	{
1309 		if (a.length != b.length)
1310 			return false;
1311 		const n = a.length;
1312 		foreach (const i; 0 .. n)
1313 			if (a[i] != b[i])
1314 				return false;
1315 		return true;
1316 	}
1317 	else static if (is(typeof(ElementType!T.init == ElementType!U.init) : bool))
1318 	{
1319 		static if (is(typeof(a[size_t.init])))
1320 		{
1321 			import std.algorithm.mutation : move;
1322 			size_t i = 0;
1323 			foreach (const ref be; move(b))
1324 				if (a[i++] != be)
1325 					return false;
1326 			return true;
1327 		}
1328 		else static if (is(typeof(b[size_t.init])))
1329 		{
1330 			import std.algorithm.mutation : move;
1331 			size_t i = 0;
1332 			foreach (const ref ae; move(a))
1333 				if (ae != b[i++])
1334 					return false;
1335 			return true;
1336 		}
1337 		else
1338 		{
1339 			while (true)
1340 			{
1341 				if (a.empty())
1342 					return b.empty();
1343 				if (b.empty())
1344 					return a.empty();
1345 				if (a.front != b.front)
1346 					return false;
1347 				a.popFront();
1348 				b.popFront();
1349 			}
1350 		}
1351 	}
1352 	else
1353 		static assert(0, "Cannot compare " ~ T.stringof ~ "with" ~ U.stringof);
1354 }
1355 /// ditto
1356 bool equal(T, U)(scope const(T)[] a, scope const(U)[] b)
1357 if (is(typeof(T.init == U.init) : bool))
1358 {
1359 }
1360 
1361 /// dynamic arrays
1362 @safe pure nothrow @nogc unittest
1363 {
1364 	assert(!equal([1, 2   ].s[], [1, 2, 3].s[]));
1365 	assert(!equal([1, 2, 3].s[], [1, 2,  ].s[]));
1366 	assert( equal([1, 2, 3].s[], [1, 2, 3].s[]));
1367 }
1368 
1369 /// static arrays
1370 @safe pure nothrow @nogc unittest
1371 {
1372 	assert(!equal([1, 2   ].s, [1, 2, 3].s));
1373 	assert(!equal([1, 2, 3].s, [1, 2,  ].s));
1374 	assert( equal([1, 2, 3].s, [1, 2, 3].s));
1375 }
1376 
1377 version(unittest)
1378 {
1379     import nxt.dip_traits : hasPreviewDIP1000;
1380 	import nxt.array_help : s;
1381 }
1382 
1383 // See_Also: https://dlang.org/spec/betterc.html#unittests
1384 version(unittestAsBetterC)
1385 extern(C) void main()
1386 {
1387     static foreach (u; __traits(getUnitTests, __traits(parent, main)))
1388     {
1389         u();
1390     }
1391 }