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