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