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 /** Array-specialization of `startsWith` with default predicate.
9  *
10  * See_Also: https://d.godbolt.org/z/ejEmrK
11  */
12 bool startsWith(T)(scope const T[] haystack, scope const T[] needle) @trusted {
13 	if (haystack.length < needle.length)
14 		return false;
15 	return haystack.ptr[0 .. needle.length] == needle;
16 }
17 /// ditto
18 bool startsWith(T)(scope const T[] haystack, scope const T needle) @trusted {
19 	static if (is(T : const(char)))
20 		assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
21 	if (haystack.length == 0)
22 		return false;
23 	return haystack.ptr[0] == needle;
24 }
25 ///
26 pure nothrow @safe @nogc unittest {
27 	const x = "beta version";
28 	assert(x.startsWith("beta"));
29 	assert(x.startsWith('b'));
30 	assert(!x.startsWith("_"));
31 	assert(!"".startsWith("_"));
32 	assert(!"".startsWith('_'));
33 }
34 
35 /** Array-specialization of `all` with element `needle`. */
36 bool all(T)(scope const T[] haystack, scope const T needle) @trusted {
37 	static if (is(T : const(char)))
38 		assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
39 	foreach (const offset; 0 .. haystack.length)
40 		if (haystack.ptr[offset] != needle)
41 			return false;
42 	return true;
43 }
44 ///
45 pure nothrow @safe @nogc unittest {
46 	assert("".all('a'));	// matches behaviour of `std.algorithm.searching.any`
47 	assert("aaa".all('a'));
48 	assert(!"aa_".all('a'));
49 }
50 
51 /** Array-specialization of `any` with element `needle`. */
52 bool any(T)(scope const T[] haystack, scope const T needle) @trusted {
53 	static if (is(T : const(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 true;
58 	return false;
59 }
60 ///
61 pure nothrow @safe @nogc unittest {
62 	assert(!"".any('a'));  // matches behaviour of `std.algorithm.searching.any`
63 	assert("aaa".any('a'));
64 	assert("aa_".any('a'));
65 	assert(!"_".any('a'));
66 }
67 
68 /** Array-specialization of `endsWith` with default predicate. */
69 bool endsWith(T)(scope const T[] haystack, scope const T[] needle) @trusted {
70 	if (haystack.length < needle.length)
71 		return false;
72 	return haystack.ptr[haystack.length - needle.length .. haystack.length] == needle;
73 }
74 /// ditto
75 bool endsWith(T)(scope const T[] haystack, scope const T needle) @trusted {
76 	static if (is(T : const(char)))
77 		assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
78 	if (haystack.length == 0)
79 		return false;
80 	return haystack.ptr[haystack.length - 1] == needle;
81 }
82 ///
83 pure nothrow @safe @nogc unittest {
84 	const x = "beta version";
85 	assert(x.endsWith("version"));
86 	assert(x.endsWith('n'));
87 	assert(!x.endsWith("_"));
88 	assert(!"".endsWith("_"));
89 	assert(!"".endsWith('_'));
90 }
91 
92 bool startsWithAmong(T)(scope const T[] haystack, scope const T[][] needles) {
93 	foreach (const needle; needles)
94 		if (haystack.startsWith(needle)) /+ TODO: optimize +/
95 			return true;
96 	return false;
97 }
98 /// ditto
99 bool startsWithAmong(T)(scope const T[] haystack, scope const T[] needles) {
100 	foreach (const needle; needles)
101 		if (haystack.startsWith(needle)) /+ TODO: optimize +/
102 			return true;
103 	return false;
104 }
105 ///
106 pure nothrow @safe @nogc unittest {
107 	const x = "beta version";
108 	assert(x.startsWithAmong(["beta", "version", ""]));
109 	assert(x.startsWithAmong(['b', ' ']));
110 	assert(x.startsWithAmong("b "));
111 	assert(!x.startsWithAmong(["_"]));
112 	assert(!x.startsWithAmong(['_']));
113 }
114 
115 bool endsWithAmong(T)(scope const T[] haystack, scope const T[][] needles) {
116 	foreach (const needle; needles)
117 		if (haystack.endsWith(needle)) /+ TODO: optimize +/
118 			return true;
119 	return false;
120 }
121 /// ditto
122 bool endsWithAmong(T)(scope const T[] haystack, scope const T[] needles) {
123 	foreach (const needle; needles)
124 		if (haystack.endsWith(needle)) /+ TODO: optimize +/
125 			return true;
126 	return false;
127 }
128 ///
129 pure nothrow @safe @nogc unittest {
130 	const x = "beta version";
131 	assert(x.endsWithAmong(["version"]));
132 	assert(!x.endsWithAmong(["alpha", "beta"]));
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 `count` with default predicate.
443  *
444  * NOTE: Opposite to Phobos' `canFind`, returns `true` when `needle` is empty.
445  *
446  * TODO: Add optimized implementation for needles with length >=
447  * `largeNeedleLength` with no repeat of elements.
448  *
449  * TODO: reuse `return haystack.indexOf(needle) != -1` in both overloads
450  */
451 bool canFind(T)(scope const T[] haystack, scope const T[] needle) @trusted {
452 	// enum largeNeedleLength = 4;
453 	if (needle.length == 0)
454 		return true;
455 	if (haystack.length < needle.length)
456 		return false;
457 	foreach (const offset; 0 .. haystack.length - needle.length + 1)
458 		if (haystack.ptr[offset .. offset + needle.length] == needle)
459 			return true;
460 	return false;
461 }
462 /// ditto
463 bool canFind(T)(scope const T[] haystack, scope const T needle) @trusted {
464 	static if (is(T : const(char)))
465 		assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
466 	if (haystack.length == 0)
467 		return false;
468 	foreach (const ref element; haystack)
469 		if (element == needle)
470 			return true;
471 	return false;
472 }
473 ///
474 pure nothrow @safe @nogc unittest {
475 	assert("".canFind("")); // opposite to Phobos
476 	assert("_".canFind("")); // opposite to Phobos
477 	assert(!"".canFind("_"));
478 	assert(!"a".canFind("_"));
479 	assert("a".canFind("a"));
480 	assert(!"a".canFind("ab"));
481 	assert("ab".canFind("a"));
482 	assert("ab".canFind("b"));
483 	assert("ab".canFind("ab"));
484 	assert(!"a".canFind("ab"));
485 	assert(!"b".canFind("ab"));
486 }
487 ///
488 pure nothrow @safe @nogc unittest {
489 	assert(!"".canFind('_'));
490 	assert(!"a".canFind('_'));
491 	assert("a".canFind('a'));
492 	assert("a".canFind('a'));
493 	assert("ab".canFind('a'));
494 	assert("ab".canFind('b'));
495 }
496 
497 /** Array-specialization of `count` with default predicate.
498  */
499 ptrdiff_t count(T)(scope const T[] haystack, scope const T[] needle) @trusted {
500 	if (needle.length == 0)
501 		return -1;
502 	size_t result = 0;
503 	if (haystack.length < needle.length)
504 		return false;
505 	foreach (const offset; 0 .. haystack.length - needle.length + 1)
506 		result += haystack.ptr[offset .. offset + needle.length] == needle ? 1 : 0;
507 	return result;
508 }
509 /// ditto
510 size_t count(T)(scope const T[] haystack, scope const T needle) {
511 	static if (is(T : const(char)))
512 		assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
513 	size_t result;
514 	foreach (const ref element; haystack)
515 		result += element == needle ? 1 : 0;
516 	return result;
517 }
518 ///
519 pure nothrow @safe @nogc unittest {
520 	assert("".count("") == -1); // -1 instead of `assert` that `std.algorithm.count` does
521 	assert("".count("_") == 0);
522 	assert("".count(" ") == 0);
523 	assert(" ".count(" ") == 1);
524 	assert("abc_abc".count("a") == 2);
525 	assert("abc_abc".count("abc") == 2);
526 	assert("_a_a_".count("_") == 3);
527 	assert("_aaa_".count("a") == 3);
528 }
529 ///
530 pure nothrow @safe @nogc unittest {
531 	assert("".count('_') == 0);
532 	assert("abc_abc".count('a') == 2);
533 	assert("_abc_abc_".count('_') == 3);
534 }
535 
536 /** Array-specialization of `count` with default predicate and no needle.
537  */
538 size_t count(T)(scope const T[] haystack) => haystack.length;
539 ///
540 pure nothrow @safe @nogc unittest {
541 	assert("abc_abc".count == 7);
542 }
543 
544 /** Array-specialization of `countAmong` with default predicate.
545  */
546 ptrdiff_t countAmong(T)(scope const T[] haystack, scope const T[][] needles) @trusted {
547 	if (needles.length == 0)
548 		return -1;
549 	size_t result = 0;
550 	foreach (const ref needle; needles)
551 		foreach (const offset; 0 .. haystack.length - needle.length + 1)
552 			result += haystack.ptr[offset .. offset + needle.length] == needle ? 1 : 0;
553 	return result;
554 }
555 /// ditto
556 ptrdiff_t countAmong(T)(scope const T[] haystack, scope const T[] needles) {
557 	static if (is(T : const(char)))
558 		foreach (needle; needles)
559 			assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
560 	if (needles.length == 0)
561 		return -1;
562 	size_t result = 0;
563 	foreach (const offset; 0 .. haystack.length)
564 		foreach (const ref needle; needles)
565 			result += haystack[offset] == needle ? 1 : 0;
566 	return result;
567 }
568 ///
569 pure nothrow @safe @nogc unittest {
570 	assert("".countAmong(string[].init) == -1);
571 	assert("".countAmong([""]) == 1);
572 	assert("".countAmong(["_"]) == 0);
573 	assert("".countAmong([" "]) == 0);
574 	assert(" ".countAmong([" "]) == 1);
575 	assert("abc_abc".countAmong(["a"]) == 2);
576 	assert("abc_abc".countAmong(["abc"]) == 2);
577 	assert("_a_a_".countAmong(["_"]) == 3);
578 	assert("_aaa_".countAmong(["a"]) == 3);
579 }
580 ///
581 pure nothrow @safe @nogc unittest {
582 	assert("".countAmong(string.init) == -1); // -1 instead of `assert` that `std.algorithm.count` does
583 	assert("".countAmong("") == -1); // -1 instead of `assert` that `std.algorithm.count` does
584 	assert(" ".countAmong("") == -1); // -1 instead of `assert` that `std.algorithm.count` does
585 	assert("".countAmong("_") == 0);
586 	assert("".countAmong(" ") == 0);
587 	assert(" ".countAmong(" ") == 1);
588 	assert("abc_abc".countAmong("a") == 2);
589 	assert("abc_abc".countAmong(" ") == 0);
590 	assert("abc_abc".countAmong("ab") == 4);
591 	assert("abc_abc".countAmong("abc") == 6);
592 	assert("_a_a_".countAmong("_") == 3);
593 	assert("_aaa_".countAmong("a") == 3);
594 }
595 
596 /** Array-specialization of `indexOf` with default predicate.
597  *
598  * TODO: Add optimized implementation for needles with length >=
599  * `largeNeedleLength` with no repeat of elements.
600  */
601 ptrdiff_t indexOf(T)(scope inout(T)[] haystack, scope const(T)[] needle) @trusted {
602 	// enum largeNeedleLength = 4;
603 	if (haystack.length < needle.length)
604 		return -1;
605 	foreach (const offset; 0 .. haystack.length - needle.length + 1)
606 		if (haystack.ptr[offset .. offset + needle.length] == needle)
607 			return offset;
608 	return -1;
609 }
610 /// ditto
611 ptrdiff_t indexOf(T)(scope inout(T)[] haystack, scope const T needle) {
612 	static if (is(T : const(char)))
613 		assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
614 	foreach (const offset, const ref element; haystack)
615 		if (element == needle)
616 			return offset;
617 	return -1;
618 }
619 ///
620 pure nothrow @safe @nogc unittest {
621 	assert("".indexOf("") == 0);
622 	assert("_".indexOf("") == 0);
623 	assert("_abc_abc_".indexOf("abc") == 1);
624 	assert("__abc_".indexOf("abc") == 2);
625 	assert("a".indexOf("a") == 0);
626 	assert("abc".indexOf("abc") == 0);
627 	assert("_".indexOf("a") == -1);
628 	assert("_".indexOf("__") == -1);
629 	assert("__".indexOf("a") == -1);
630 }
631 ///
632 pure nothrow @safe @nogc unittest {
633 	assert("".indexOf('a') == -1);
634 	assert("_".indexOf('a') == -1);
635 	assert("a".indexOf('a') == 0);
636 	assert("_a".indexOf('a') == 1);
637 	assert("__a".indexOf('a') == 2);
638 }
639 
640 /// ditto
641 ptrdiff_t indexOfAmong(T)(scope inout(T)[] haystack, scope const T[] needles) {
642 	if (needles.length == 0)
643 		return -1;
644 	foreach (const offset, const ref element; haystack)
645 		foreach (const needle; needles)
646 			if (element == needle)
647 				return offset;
648 	return -1;
649 }
650 alias indexOfAny = indexOfAmong; // Compliance with `std.string.indexOfAny`.
651 ///
652 pure nothrow @safe @nogc unittest {
653 	assert("".indexOfAmong("a") == -1);
654 	assert("_".indexOfAmong("a") == -1);
655 	assert("_a".indexOfAmong("a") == 1);
656 	assert("_a".indexOfAmong("ab") == 1);
657 	assert("_b".indexOfAmong("ab") == 1);
658 	assert("_b".indexOfAmong("_") == 0);
659 	assert("_b".indexOfAmong("xy") == -1);
660 	assert("_b".indexOfAmong("") == -1);
661 }
662 
663 /** Array-specialization of `lastIndexOf` with default predicate.
664  */
665 ptrdiff_t lastIndexOf(T)(scope inout(T)[] haystack, scope const(T)[] needle) @trusted {
666 	if (haystack.length < needle.length)
667 		return -1;
668 	foreach_reverse (const offset; 0 .. haystack.length - needle.length + 1)
669 		if (haystack.ptr[offset .. offset + needle.length] == needle)
670 			return offset;
671 	return -1;
672 }
673 /// ditto
674 ptrdiff_t lastIndexOf(T)(scope inout(T)[] haystack, scope const T needle) {
675 	static if (is(T : const(char)))
676 		assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
677 	foreach_reverse (const offset, const ref element; haystack)
678 		if (element == needle)
679 			return offset;
680 	return -1;
681 }
682 ///
683 pure nothrow @safe @nogc unittest {
684 	assert("".lastIndexOf("") == 0);
685 	assert("_".lastIndexOf("") == 1);
686 	assert("_abc_abc_".lastIndexOf("abc") == 5);
687 	assert("__abc_".lastIndexOf("abc") == 2);
688 	assert("a".lastIndexOf("a") == 0);
689 	assert("aa".lastIndexOf("a") == 1);
690 	assert("abc".lastIndexOf("abc") == 0);
691 	assert("_".lastIndexOf("a") == -1);
692 	assert("_".lastIndexOf("__") == -1);
693 	assert("__".lastIndexOf("a") == -1);
694 }
695 ///
696 pure nothrow @safe @nogc unittest {
697 	assert("_".lastIndexOf('a') == -1);
698 	assert("a".lastIndexOf('a') == 0);
699 	assert("_a".lastIndexOf('a') == 1);
700 	assert("__a".lastIndexOf('a') == 2);
701 	assert("a__a".lastIndexOf('a') == 3);
702 }
703 
704 /** Array-specialization of `findSplit` with default predicate.
705  *
706  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
707  * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org
708  */
709 auto findSplit(T)(scope return inout(T)[] haystack, scope const(T)[] needle) {
710 	static struct Result {
711 		private T[] _haystack;
712 		private size_t _offset; // hit offset
713 		private size_t _length; // hit length
714 	pragma(inline, true) pure nothrow @nogc:
715 		inout(T)[] opIndex(in size_t i) inout {
716 			switch (i) {
717 			case 0: return pre;
718 			case 1: return separator;
719 			case 2: return post;
720 			default: return typeof(return).init;
721 			}
722 		}
723 		inout(T)[] pre() @trusted inout		  => _haystack.ptr[0 .. _offset];
724 		inout(T)[] separator() @trusted inout => _haystack.ptr[_offset .. _offset + _length];
725 		inout(T)[] post() @trusted inout	  => _haystack.ptr[_offset + _length .. _haystack.length];
726 		bool opCast(T : bool)() @safe const	  => _haystack.length != _offset;
727 	}
728 
729 	assert(needle.length, "Cannot find occurrence of an empty range");
730 	const index = haystack.indexOf(needle);
731 	if (index >= 0)
732 		return inout(Result)(haystack, index, needle.length);
733 	return inout(Result)(haystack, haystack.length, 0); // miss
734 }
735 /// ditto
736 auto findSplit(T)(scope return inout(T)[] haystack, scope const T needle) {
737 	static struct Result {
738 		private T[] _haystack;
739 		private size_t _offset; // hit offset
740 	pragma(inline, true) pure nothrow @nogc:
741 		inout(T)[] opIndex(in size_t i) inout {
742 			switch (i) {
743 			case 0: return pre;
744 			case 1: return separator;
745 			case 2: return post;
746 			default: return typeof(return).init;
747 			}
748 		}
749 		inout(T)[] pre() @trusted inout		  => _haystack.ptr[0 .. _offset];
750 		inout(T)[] separator() @trusted inout => !empty ? _haystack.ptr[_offset .. _offset + 1] : _haystack[$ .. $];
751 		inout(T)[] post() @trusted inout	  => !empty ? _haystack.ptr[_offset + 1 .. _haystack.length] : _haystack[$ .. $];
752 		bool opCast(T : bool)() const		  => !empty;
753 		private bool empty() const @property  => _haystack.length == _offset;
754 	}
755 	static if (is(T : const(char)))
756 		assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
757 	const index = haystack.indexOf(needle);
758 	if (index >= 0)
759 		return inout(Result)(haystack, index);
760 	return inout(Result)(haystack, haystack.length);
761 }
762 ///
763 pure nothrow @safe @nogc unittest {
764 	const h = "a**b";
765 	const r = h.findSplit("**");
766 	assert(r);
767 	assert(r.pre is h[0 .. 1]);
768 	assert(r.separator is h[1 .. 3]);
769 	assert(r.post is h[3 .. 4]);
770 	version (unittest) {
771 		static auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplit(" "); }
772 		static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
773 	}
774 }
775 ///
776 pure nothrow @safe @nogc unittest {
777 	const h = "a**b";
778 	const r = h.findSplit("_");
779 	static assert(r.sizeof == 2 * 2 * size_t.sizeof);
780 	assert(!r);
781 	assert(r.pre is h);
782 	assert(r.separator is h[$ .. $]);
783 	assert(r.post is h[$ .. $]);
784 }
785 ///
786 pure nothrow @safe @nogc unittest {
787 	const r = "a*b".findSplit('*');
788 	static assert(r.sizeof == 3 * size_t.sizeof);
789 	assert(r);
790 	assert(r.pre == "a");
791 	assert(r.separator == "*");
792 	assert(r.post == "b");
793 	version (unittest) {
794 		static auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplit(' '); }
795 		static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
796 	}
797 }
798 /// DIP-1000 scope analysis
799 version (none)					// TODO. enable
800 pure nothrow @safe @nogc unittest {
801 	char[] f() @safe pure nothrow
802 	{
803 		char[3] haystack = "a*b";
804 		auto r = haystack[].findSplit('*');
805 		static assert(is(typeof(r.pre()) == char[]));
806 		return r.pre();		 /+ TODO: this should fail +/
807 	}
808 	f();
809 }
810 
811 /** Array-specialization of `findLastSplit` with default predicate.
812  */
813 auto findLastSplit(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 	assert(needle.length, "Cannot find occurrence of an empty range");
833 	const index = haystack.lastIndexOf(needle);
834 	if (index >= 0)
835 		return inout(Result)(haystack, index, needle.length);
836 	return inout(Result)(haystack, haystack.length, 0); // miss
837 }
838 /// ditto
839 auto findLastSplit(T)(scope return inout(T)[] haystack, scope const T needle) {
840 	static struct Result {
841 		private T[] _haystack;
842 		private size_t _offset; // hit offset
843 	pragma(inline, true) pure nothrow @nogc:
844 		inout(T)[] opIndex(in size_t i) inout {
845 			switch (i) {
846 			case 0: return pre;
847 			case 1: return separator;
848 			case 2: return post;
849 			default: return typeof(return).init;
850 			}
851 		}
852 		inout(T)[] pre() @trusted inout		  => _haystack.ptr[0 .. _offset];
853 		inout(T)[] separator() @trusted inout => !empty ? _haystack.ptr[_offset .. _offset + 1] : _haystack[$ .. $];
854 		inout(T)[] post() @trusted inout	  => !empty ? _haystack.ptr[_offset + 1 .. _haystack.length] : _haystack[$ .. $];
855 		bool opCast(T : bool)() const		  => !empty;
856 		private bool empty() const @property  => _haystack.length == _offset;
857 	}
858 	static if (is(T : const(char)))
859 		assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
860 	const index = haystack.lastIndexOf(needle);
861 	if (index >= 0)
862 		return inout(Result)(haystack, index);
863 	return inout(Result)(haystack, haystack.length);
864 }
865 ///
866 pure nothrow @safe @nogc unittest {
867 	const h = "a**b**c";
868 	const r = h.findLastSplit("**");
869 	assert(r);
870 	assert(r.pre is h[0 .. 4]);
871 	assert(r.separator is h[4 .. 6]);
872 	assert(r.post is h[6 .. 7]);
873 	version (unittest) {
874 		static auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findLastSplit(" "); }
875 		static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
876 	}
877 }
878 ///
879 pure nothrow @safe @nogc unittest {
880 	const h = "a**b**c";
881 	const r = h.findLastSplit("_");
882 	static assert(r.sizeof == 2 * 2 * size_t.sizeof);
883 	assert(!r);
884 	assert(r.pre is h);
885 	assert(r.separator is h[$ .. $]);
886 	assert(r.post is h[$ .. $]);
887 }
888 ///
889 pure nothrow @safe @nogc unittest {
890 	const r = "a*b*c".findLastSplit('*');
891 	static assert(r.sizeof == 3 * size_t.sizeof);
892 	assert(r);
893 	assert(r.pre == "a*b");
894 	assert(r.separator == "*");
895 	assert(r.post == "c");
896 	version (unittest) {
897 		static auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findLastSplit(' '); }
898 		static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
899 	}
900 }
901 /// DIP-1000 scope analysis
902 version (none)					/+ TODO: enable +/
903 pure nothrow @safe @nogc unittest {
904 	char[] f() @safe pure nothrow
905 	{
906 		char[3] haystack = "a*b";
907 		auto r = haystack[].findLastSplit('*');
908 		static assert(is(typeof(r.pre()) == char[]));
909 		return r.pre();		 /+ TODO: this should fail +/
910 	}
911 	f();
912 }
913 
914 /** Array-specialization of `findSplitBefore` with default predicate.
915  *
916  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
917  * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org
918  */
919 auto findSplitBefore(T)(scope return inout(T)[] haystack, scope const T needle) {
920 	static struct Result {
921 		private T[] _haystack;
922 		private size_t _offset;
923 	pragma(inline, true) pure nothrow @nogc:
924 		inout(T)[] pre() @trusted inout		 => _haystack.ptr[0 .. _offset];
925 		inout(T)[] post() @trusted inout	 => !empty ? _haystack.ptr[_offset .. _haystack.length] : _haystack[$ .. $];
926 		bool opCast(T : bool)() const		 => !empty;
927 		private bool empty() const @property => _haystack.length == _offset;
928 	}
929 	static if (is(T : const(char)))
930 		assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
931 	foreach (const offset, const ref element; haystack)
932 		if (element == needle)
933 			return inout(Result)(haystack, offset);
934 	return inout(Result)(haystack, haystack.length);
935 }
936 ///
937 pure nothrow @safe @nogc unittest {
938 	char[] haystack;
939 	auto r = haystack.findSplitBefore('_');
940 	static assert(is(typeof(r.pre()) == typeof(haystack)));
941 	static assert(is(typeof(r.post()) == typeof(haystack)));
942 	assert(!r);
943 	assert(!r.pre);
944 	assert(!r.post);
945 	version (unittest) {
946 		static auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplitBefore(' '); }
947 		static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
948 	}
949 }
950 ///
951 pure nothrow @safe @nogc unittest {
952 	const(char)[] haystack;
953 	auto r = haystack.findSplitBefore('_');
954 	static assert(is(typeof(r.pre()) == typeof(haystack)));
955 	static assert(is(typeof(r.post()) == typeof(haystack)));
956 	assert(!r);
957 	assert(!r.pre);
958 	assert(!r.post);
959 }
960 ///
961 pure nothrow @safe @nogc unittest {
962 	const r = "a*b".findSplitBefore('*');
963 	assert(r);
964 	assert(r.pre == "a");
965 	assert(r.post == "*b");
966 }
967 ///
968 pure nothrow @safe @nogc unittest {
969 	const r = "a*b".findSplitBefore('_');
970 	assert(!r);
971 	assert(r.pre == "a*b");
972 	assert(r.post == "");
973 }
974 
975 /** Array-specialization of `findSplitBefore` with explicit needle-only predicate `needlePred`.
976  *
977  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
978  * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org
979  */
980 auto findSplitBefore(alias needlePred, T)(scope return inout(T)[] haystack) {
981 	static struct Result {
982 		private T[] _haystack;
983 		private size_t _offset;
984 	pragma(inline, true) pure nothrow @nogc:
985 		inout(T)[] pre() @trusted inout		 => _haystack.ptr[0 .. _offset];
986 		inout(T)[] post() @trusted inout	 => !empty ? _haystack.ptr[_offset .. _haystack.length] : _haystack[$ .. $];
987 		bool opCast(T : bool)() const		 => !empty;
988 		private bool empty() const @property => _haystack.length == _offset;
989 	}
990 	foreach (const offset, const ref element; haystack)
991 		if (needlePred(element))
992 			return inout(Result)(haystack, offset);
993 	return inout(Result)(haystack, haystack.length);
994 }
995 ///
996 pure nothrow @safe @nogc unittest {
997 	char[] haystack;
998 	auto r = haystack.findSplitBefore!(_ => _ == '_');
999 	static assert(is(typeof(r.pre()) == typeof(haystack)));
1000 	static assert(is(typeof(r.post()) == typeof(haystack)));
1001 	assert(!r);
1002 	assert(!r.pre);
1003 	assert(!r.post);
1004 	version (unittest) {
1005 		static auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplitBefore!(_ => _ == ' '); }
1006 		static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
1007 	}
1008 }
1009 ///
1010 pure nothrow @safe @nogc unittest {
1011 	const(char)[] haystack;
1012 	auto r = haystack.findSplitBefore!(_ => _ == '_');
1013 	static assert(is(typeof(r.pre()) == typeof(haystack)));
1014 	static assert(is(typeof(r.post()) == const(char)[]));
1015 	assert(!r);
1016 	assert(!r.pre);
1017 	assert(!r.post);
1018 }
1019 ///
1020 pure nothrow @safe @nogc unittest {
1021 	const r = "a*b".findSplitBefore!(_ => _ == '*');
1022 	assert(r);
1023 	assert(r.pre == "a");
1024 	assert(r.post == "*b");
1025 }
1026 ///
1027 pure nothrow @safe @nogc unittest {
1028 	const r = "a*b".findSplitBefore!(_ => _ == '*' || _ == '+');
1029 	assert(r);
1030 	assert(r.pre == "a");
1031 	assert(r.post == "*b");
1032 }
1033 ///
1034 pure nothrow @safe @nogc unittest {
1035 	const r = "a+b".findSplitBefore!(_ => _ == '*' || _ == '+');
1036 	assert(r);
1037 	assert(r.pre == "a");
1038 	assert(r.post == "+b");
1039 }
1040 ///
1041 pure nothrow @safe @nogc unittest {
1042 	const r = "a*b".findSplitBefore!(_ => _ == '_');
1043 	assert(!r);
1044 	assert(r.pre == "a*b");
1045 	assert(r.post == "");
1046 }
1047 
1048 /** Array-specialization of `findSplitAfter` with default predicate.
1049  *
1050  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
1051  * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org
1052  */
1053 auto findSplitAfter(T)(scope return inout(T)[] haystack, scope const T needle) @trusted {
1054 	static struct Result {
1055 		private T[] _haystack;
1056 		private size_t _offset;
1057 	pragma(inline, true) pure nothrow @nogc:
1058 		inout(T)[] pre() @trusted inout		 => !empty ? _haystack.ptr[0 .. _offset + 1] : _haystack[$ .. $];
1059 		inout(T)[] post() @trusted inout	 => !empty ? _haystack.ptr[_offset + 1 .. _haystack.length] : _haystack[0 .. $];
1060 		bool opCast(T : bool)() const		 => !empty;
1061 		private bool empty() const @property => _haystack.length == _offset;
1062 	}
1063 	static if (is(T : const(char)))
1064 		assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
1065 	foreach (const offset, const ref element; haystack)
1066 		if (element == needle)
1067 			return inout(Result)(haystack, offset);
1068 	return inout(Result)(haystack, haystack.length);
1069 }
1070 ///
1071 pure nothrow @safe @nogc unittest {
1072 	char[] haystack;
1073 	auto r = haystack.findSplitAfter('_');
1074 	static assert(is(typeof(r.pre()) == typeof(haystack)));
1075 	static assert(is(typeof(r.post()) == typeof(haystack)));
1076 	assert(!r);
1077 	assert(!r.pre);
1078 	assert(!r.post);
1079 	version (unittest) {
1080 		static auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findSplitAfter(' '); }
1081 		static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
1082 	}
1083 }
1084 ///
1085 pure nothrow @safe @nogc unittest {
1086 	const(char)[] haystack;
1087 	auto r = haystack.findSplitAfter('_');
1088 	static assert(is(typeof(r.pre()) == typeof(haystack)));
1089 	static assert(is(typeof(r.post()) == typeof(haystack)));
1090 	assert(!r);
1091 	assert(!r.pre);
1092 	assert(!r.post);
1093 }
1094 ///
1095 pure nothrow @safe @nogc unittest {
1096 	auto haystack = "a*b";
1097 	auto r = haystack.findSplitAfter('*');
1098 	static assert(is(typeof(r.pre()) == typeof(haystack)));
1099 	static assert(is(typeof(r.post()) == typeof(haystack)));
1100 	assert(r);
1101 	assert(r.pre == "a*");
1102 	assert(r.post == "b");
1103 }
1104 
1105 /** Array-specialization of `findLastSplitAfter` with default predicate.
1106  *
1107  * See_Also: https://forum.dlang.org/post/dhxwgtaubzbmjaqjmnmq@forum.dlang.org
1108  * See_Also: https://forum.dlang.org/post/zhgajqdhybtbufeiiofp@forum.dlang.org
1109  */
1110 auto findLastSplitAfter(T)(scope return inout(T)[] haystack, scope const T needle) @trusted {
1111 	static struct Result {
1112 		private T[] _haystack;
1113 		private size_t _offset;
1114 	pragma(inline, true) pure nothrow @nogc:
1115 		inout(T)[] pre() @trusted inout		 => !empty ? _haystack.ptr[0 .. _offset + 1] : _haystack[$ .. $];
1116 		inout(T)[] post() @trusted inout	 => !empty ? _haystack.ptr[_offset + 1 .. _haystack.length] : _haystack[0 .. $];
1117 		bool opCast(T : bool)() const		 => !empty;
1118 		private bool empty() const @property => _haystack.length == _offset;
1119 	}
1120 	static if (is(T : const(char)))
1121 		assert(needle < 128); // See_Also: https://forum.dlang.org/post/sjirukypxmmcgdmqbcpe@forum.dlang.org
1122 	const index = haystack.lastIndexOf(needle);
1123 	if (index >= 0)
1124 		return inout(Result)(haystack, index);
1125 	return inout(Result)(haystack, haystack.length); // miss
1126 }
1127 ///
1128 pure nothrow @safe @nogc unittest {
1129 	char[] haystack;
1130 	auto r = haystack.findLastSplitAfter('_');
1131 	static assert(is(typeof(r.pre()) == typeof(haystack)));
1132 	static assert(is(typeof(r.post()) == typeof(haystack)));
1133 	assert(!r);
1134 	assert(!r.pre);
1135 	assert(!r.post);
1136 	version (unittest) {
1137 		static auto f()() @safe pure nothrow { char[1] x = "_"; return x[].findLastSplitAfter(' '); }
1138 		static if (hasPreviewDIP1000) static assert(!__traits(compiles, { auto _ = f(); }));
1139 	}
1140 }
1141 ///
1142 pure nothrow @safe @nogc unittest {
1143 	const(char)[] haystack;
1144 	auto r = haystack.findLastSplitAfter('_');
1145 	static assert(is(typeof(r.pre()) == typeof(haystack)));
1146 	static assert(is(typeof(r.post()) == typeof(haystack)));
1147 	assert(!r);
1148 	assert(!r.pre);
1149 	assert(!r.post);
1150 }
1151 ///
1152 pure nothrow @safe @nogc unittest {
1153 	auto haystack = "a*b*c";
1154 	auto r = haystack.findLastSplitAfter('*');
1155 	static assert(is(typeof(r.pre()) == typeof(haystack)));
1156 	static assert(is(typeof(r.post()) == typeof(haystack)));
1157 	assert(r);
1158 	assert(r.pre == "a*b*");
1159 	assert(r.post == "c");
1160 }
1161 
1162 import std.traits : isExpressions;
1163 
1164 /** Like `findSplit` but with multiple separator `needles` known at compile-time
1165  * to prevent `NarrowString` decoding.
1166  *
1167  * TODO: Resort to `memchr` for some case `if (!__ctfe)`.
1168  * See_Also: https://forum.dlang.org/post/efpbmtyisamwwqgpxnbq@forum.dlang.org
1169  *
1170  * See_Also: https://forum.dlang.org/post/ycotlbfsqoupogaplkvf@forum.dlang.org
1171  */
1172 template findSplitAmong(needles...)
1173 if (needles.length != 0 &&
1174 	isExpressions!needles) {
1175 	import std.meta : allSatisfy;
1176 	import nxt.char_traits : isASCII;
1177 
1178 	auto findSplitAmong(Haystack)(const scope return Haystack haystack) @trusted /+ TODO: qualify with `inout` to reduce template bloat +/
1179 	if (is(typeof(Haystack.init[0 .. 0])) && // can be sliced
1180 		is(typeof(Haystack.init[0]) : char) &&
1181 		allSatisfy!(isASCII, needles)) {
1182 		// similar return result to `std.algorithm.searching.findSplit`
1183 		static struct Result {
1184 			/* Only requires 3 words opposite to Phobos' `findSplit`,
1185 			 * `findSplitBefore` and `findSplitAfter`:
1186 			 */
1187 
1188 			private Haystack _haystack; // original copy of haystack
1189 			private size_t _offset; // hit offset if any, or `_haystack.length` if miss
1190 
1191 			bool opCast(T : bool)() const => !empty;
1192 
1193 			inout(Haystack) opIndex(in size_t i) inout {
1194 				switch (i) {
1195 				case 0: return pre;
1196 				case 1: return separator;
1197 				case 2: return post;
1198 				default: return typeof(return).init;
1199 				}
1200 			}
1201 
1202 		@property:
1203 			private bool empty() const => _haystack.length == _offset;
1204 
1205 			inout(Haystack) pre() inout => _haystack[0 .. _offset];
1206 
1207 			inout(Haystack) separator() inout {
1208 				if (empty) { return _haystack[$ .. $]; }
1209 				return _haystack[_offset .. _offset + 1];
1210 			}
1211 
1212 			inout(Haystack) post() inout {
1213 				if (empty) { return _haystack[$ .. $]; }
1214 				return _haystack[_offset + 1 .. $];
1215 			}
1216 		}
1217 
1218 		enum use_memchr = false;
1219 		static if (use_memchr &&
1220 				   needles.length == 1) {
1221 			// See_Also: https://forum.dlang.org/post/piowvfbimztbqjvieddj@forum.dlang.org
1222 			import core.stdc.string : memchr;
1223 			// extern (C) @system nothrow @nogc pure void* rawmemchr(return const void* s, int c);
1224 
1225 			const void* hit = memchr(haystack.ptr, needles[0], haystack.length);
1226 			return Result(haystack, hit ? hit - cast(const(void)*)haystack.ptr : haystack.length);
1227 		} else {
1228 			foreach (immutable offset; 0 .. haystack.length) {
1229 				static if (needles.length == 1) {
1230 					immutable hit = haystack[offset] == needles[0];
1231 				} else {
1232 					import std.algorithm.comparison : among;
1233 					immutable hit = haystack[offset].among!(needles) != 0;
1234 				}
1235 				if (hit)
1236 					return Result(haystack, offset);
1237 			}
1238 			return Result(haystack, haystack.length);
1239 		}
1240 	}
1241 }
1242 
1243 template findSplit(needles...)
1244 if (needles.length == 1 &&
1245 	isExpressions!needles) {
1246 	import nxt.char_traits : isASCII;
1247 	auto findSplit(Haystack)(const scope return Haystack haystack) @trusted /+ TODO: qualify with `inout` to reduce template bloat +/
1248 	if (is(typeof(Haystack.init[0 .. 0])) && // can be sliced
1249 		is(typeof(Haystack.init[0]) : char) &&
1250 		isASCII!(needles[0])) {
1251 		return findSplitAmong!(needles)(haystack);
1252 	}
1253 }
1254 
1255 ///
1256 pure nothrow @safe @nogc unittest {
1257 	const r = "a*b".findSplit!('*');
1258 	assert(r);
1259 
1260 	assert(r[0] == "a");
1261 	assert(r.pre == "a");
1262 
1263 	assert(r[1] == "*");
1264 	assert(r.separator == "*");
1265 
1266 	assert(r[2] == "b");
1267 	assert(r.post == "b");
1268 }
1269 
1270 ///
1271 pure nothrow @safe @nogc unittest {
1272 	auto r = "a+b*c".findSplitAmong!('+', '-');
1273 
1274 	static assert(r.sizeof == 24);
1275 	static assert(is(typeof(r.pre) == string));
1276 	static assert(is(typeof(r.separator) == string));
1277 	static assert(is(typeof(r.post) == string));
1278 
1279 	assert(r);
1280 
1281 	assert(r[0] == "a");
1282 	assert(r.pre == "a");
1283 
1284 	assert(r[1] == "+");
1285 	assert(r.separator == "+");
1286 
1287 	assert(r[2] == "b*c");
1288 	assert(r.post == "b*c");
1289 }
1290 
1291 ///
1292 pure nothrow @safe @nogc unittest {
1293 	const r = "a+b*c".findSplitAmong!('-', '*');
1294 	assert(r);
1295 	assert(r.pre == "a+b");
1296 	assert(r.separator == "*");
1297 	assert(r.post == "c");
1298 }
1299 
1300 ///
1301 pure nothrow @safe @nogc unittest {
1302 	const r = "a*".findSplitAmong!('*');
1303 
1304 	assert(r);
1305 
1306 	assert(r[0] == "a");
1307 	assert(r.pre == "a");
1308 
1309 	assert(r[1] == "*");
1310 	assert(r.separator == "*");
1311 
1312 	assert(r[2] == "");
1313 	assert(r.post == "");
1314 }
1315 
1316 ///
1317 pure nothrow @safe @nogc unittest {
1318 	const r = "*b".findSplitAmong!('*');
1319 
1320 	assert(r);
1321 
1322 	assert(r[0] == "");
1323 	assert(r.pre == "");
1324 
1325 	assert(r[1] == "*");
1326 	assert(r.separator == "*");
1327 
1328 	assert(r[2] == "b");
1329 	assert(r.post == "b");
1330 }
1331 
1332 ///
1333 pure nothrow @safe @nogc unittest {
1334 	const r = "*".findSplitAmong!('*');
1335 
1336 	assert(r);
1337 
1338 	assert(r[0] == "");
1339 	assert(r.pre == "");
1340 
1341 	assert(r[1] == "*");
1342 	assert(r.separator == "*");
1343 
1344 	assert(r[2] == "");
1345 	assert(r.post == "");
1346 }
1347 
1348 ///
1349 pure nothrow @safe @nogc unittest {
1350 	static immutable separator_char = '/';
1351 
1352 	immutable r = "a+b*c".findSplitAmong!(separator_char);
1353 
1354 	static assert(r.sizeof == 24);
1355 	static assert(is(typeof(r.pre) == immutable string));
1356 	static assert(is(typeof(r.separator) == immutable string));
1357 	static assert(is(typeof(r.post) == immutable string));
1358 
1359 	assert(!r);
1360 
1361 	assert(r.pre == "a+b*c");
1362 	assert(r[0] == "a+b*c");
1363 	assert(r.separator == []);
1364 	assert(r[1] == []);
1365 	assert(r.post == []);
1366 	assert(r[2] == []);
1367 }
1368 
1369 version (unittest) {
1370 	import nxt.dip_traits : hasPreviewDIP1000;
1371 	import nxt.array_help : s;
1372 }