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 }