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