1 /******************************************************************************* 2 3 Collection of utilities to search within arrays and buffers. 4 5 All functions in this module must never mutate their arguments and must 6 never allocate new memory each call. Some may keep internal static GC 7 buffers if required by algorithm. 8 9 Based on `tango.core.Array` module from Tango library. 10 11 Copyright: 12 Copyright (C) 2005-2006 Sean Kelly. 13 Some parts copyright (c) 2009-2016 dunnhumby Germany GmbH. 14 All rights reserved. 15 16 License: 17 Tango Dual License: 3-Clause BSD License / Academic Free License v3.0. 18 See LICENSE_TANGO.txt for details. 19 20 21 *******************************************************************************/ 22 23 module ocean.core.array.Search; 24 25 import ocean.core.Buffer; 26 import ocean.core.array.DefaultPredicates; 27 import ocean.core.Verify; 28 import ocean.meta.traits.Basic; 29 import ocean.meta.types.Qualifiers; 30 31 // ssize_t is only available on this header, but should really be in `object` 32 version (Posix) 33 import core.sys.posix.sys.types; 34 else static if (size_t.sizeof == 8) 35 alias ssize_t = long; 36 else 37 alias ssize_t = int; 38 39 version (unittest) 40 { 41 import ocean.meta.types.Typedef; 42 import ocean.core.Test; 43 } 44 45 /******************************************************************************* 46 47 Linear search in an array 48 49 Performs a linear scan of haystack from [0 .. haystack.length$(RP), returning 50 the index of the first element matching needle, or haystack.length if no match 51 was found. Comparisons will be performed using the supplied predicate 52 or '==' if none is supplied. 53 54 Params: 55 haystack = The array to search. 56 needle = The needletern to search for, either sub-array or element 57 pred = The evaluation predicate, which should return true if e1 is 58 equal to e2 and false if not. This predicate may be any callable 59 type. 60 61 Returns: 62 The index of the first match or haystack.length if no match was found. 63 64 *******************************************************************************/ 65 66 size_t find ( T, Pred = DefaultPredicates.IsEqual!(T) ) 67 ( in T[] haystack, in T needle, Pred pred = Pred.init ) 68 { 69 static assert (isCallableType!(Pred)); 70 71 foreach ( pos, cur; haystack ) 72 { 73 if( pred( cur, needle ) ) 74 return pos; 75 } 76 77 return haystack.length; 78 } 79 80 /// 81 unittest 82 { 83 test!("==")(find( "abc", 'b' ), 1); 84 } 85 86 unittest 87 { 88 test!("==")(find( "", 'a' ), 0); 89 test!("==")(find( "abc", 'a' ), 0); 90 test!("==")(find( "abc", 'b' ), 1); 91 test!("==")(find( "abc", 'c' ), 2); 92 test!("==")(find( "abc", 'd' ), 3); 93 } 94 95 /// ditto 96 size_t find ( T, Pred = DefaultPredicates.IsEqual!(T) ) 97 ( in T[] haystack, in T[] needle, Pred pred = Pred.init ) 98 { 99 static assert (isCallableType!(Pred)); 100 101 if( haystack.length == 0 || 102 needle.length == 0 || 103 haystack.length < needle.length ) 104 { 105 return haystack.length; 106 } 107 108 size_t end = haystack.length - needle.length + 1; 109 110 for( size_t pos = 0; pos < end; ++pos ) 111 { 112 if( pred( haystack[pos], needle[0] ) ) 113 { 114 size_t mat = 0; 115 116 do 117 { 118 if( ++mat >= needle.length ) 119 return pos - needle.length + 1; 120 ++pos; 121 assert (pos < haystack.length); 122 } while( pred( haystack[pos], needle[mat] ) ); 123 pos -= mat; 124 } 125 } 126 return haystack.length; 127 } 128 129 /// 130 unittest 131 { 132 test!("==")(find( "abc", "bc" ), 1); 133 test!("==")(find( "abcd", "cc" ), 4); 134 } 135 136 /// ditto 137 size_t find ( T, Pred = DefaultPredicates.IsEqual!(T) ) 138 ( ref Buffer!(T) haystack, in T[] needle, Pred pred = Pred.init ) 139 { 140 return find(haystack[], needle, pred); 141 } 142 143 /// 144 unittest 145 { 146 auto buffer = createBuffer([ 1, 2, 3 ]); 147 test!("==")(find(buffer, [ 2, 3 ]), 1); 148 } 149 150 /// ditto 151 size_t find ( T, Pred = DefaultPredicates.IsEqual!(T) ) 152 ( ref Buffer!(T) haystack, in T needle, Pred pred = Pred.init ) 153 { 154 return find(haystack[], needle, pred); 155 } 156 157 /// 158 unittest 159 { 160 auto buffer = createBuffer([ 1, 2, 3 ]); 161 test!("==")(find(buffer, 3), 2); 162 } 163 164 unittest 165 { 166 // null parameters 167 test!("==")(find( "", ""[] ), 0); 168 test!("==")(find( "a", ""[] ), 1); 169 test!("==")(find( "", "a"[] ), 0); 170 171 // exact match 172 test!("==")(find( "abc", "abc"[] ), 0); 173 174 // simple substring match 175 test!("==")(find( "abc", "a"[] ), 0); 176 test!("==")(find( "abca", "a"[] ), 0); 177 test!("==")(find( "abc", "b"[] ), 1); 178 test!("==")(find( "abc", "c"[] ), 2); 179 test!("==")(find( "abc", "d"[] ), 3); 180 181 // multi-char substring match 182 test!("==")(find( "abc", "ab"[] ), 0); 183 test!("==")(find( "abcab", "ab"[] ), 0); 184 test!("==")(find( "abc", "bc"[] ), 1); 185 test!("==")(find( "abc", "ac"[] ), 3); 186 test!("==")(find( "abrabracadabra", "abracadabra"[] ), 3); 187 188 // different qualifiers 189 mstring s = "abcd".dup; 190 test!("==")(find(s, "bc"[]), 1); 191 192 // custom predicate 193 bool foo(char a, char b) 194 { 195 return a == 'x'; 196 } 197 198 test!("==")(find( "abcdxa", 'b', &foo ), 4); 199 test!("==")(find( "abcdxa", "b"[], &foo ), 4); 200 } 201 202 /******************************************************************************* 203 204 Performs a linear scan of haystack from $(LP)haystack.length .. 0], returning 205 the index of the first element matching needle, or haystack.length if no match 206 was found. Comparisons will be performed using the supplied predicate 207 or '==' if none is supplied. 208 209 Params: 210 haystac = The array to search. 211 needle = The needletern to search for. 212 pred = The evaluation predicate, which should return true if e1 is 213 equal to e2 and false if not. This predicate may be any 214 callable type. 215 216 Returns: 217 The index of the first match or haystack.length if no match was found. 218 219 *******************************************************************************/ 220 221 size_t rfind (T, Pred = DefaultPredicates.IsEqual!(T)) 222 ( in T[] haystack, in T needle, Pred pred = Pred.init ) 223 { 224 static assert ( isCallableType!(Pred) ); 225 226 if( haystack.length == 0 ) 227 return haystack.length; 228 229 size_t pos = haystack.length; 230 231 do 232 { 233 if( pred( haystack[--pos], needle ) ) 234 return pos; 235 } while( pos > 0 ); 236 return haystack.length; 237 } 238 239 /// 240 unittest 241 { 242 test!("==")(rfind([ 1, 2, 3 ], 1), 0); 243 } 244 245 unittest 246 { 247 // rfind element 248 test!("==")( rfind( "", 'a' ), 0 ); 249 test!("==")( rfind( "abc", 'a' ), 0 ); 250 test!("==")( rfind( "abc", 'b' ), 1 ); 251 test!("==")( rfind( "abc", 'c' ), 2 ); 252 test!("==")( rfind( "abc", 'd' ), 3 ); 253 } 254 255 /// ditto 256 size_t rfind ( T, Pred = DefaultPredicates.IsEqual!(T) ) 257 ( in T[] haystack, in T[] needle, Pred pred = Pred.init ) 258 { 259 static assert (isCallableType!(Pred) ); 260 261 if( haystack.length == 0 || 262 needle.length == 0 || 263 haystack.length < needle.length ) 264 { 265 return haystack.length; 266 } 267 268 size_t pos = haystack.length - needle.length + 1; 269 270 do 271 { 272 if( pred( haystack[--pos], needle[0] ) ) 273 { 274 size_t mat = 0; 275 276 do 277 { 278 if( ++mat >= needle.length ) 279 return pos - needle.length + 1; 280 ++pos; 281 assert (pos < haystack.length); 282 } while( pred( haystack[pos], needle[mat] ) ); 283 pos -= mat; 284 } 285 } while( pos > 0 ); 286 return haystack.length; 287 } 288 289 /// 290 unittest 291 { 292 test!("==")(rfind([ 1, 2, 3 ], [ 1, 2 ]), 0); 293 } 294 295 /// ditto 296 size_t rfind ( T, Pred = DefaultPredicates.IsEqual!(T) ) 297 ( ref Buffer!(T) haystack, in T[] needle, Pred pred = Pred.init ) 298 { 299 return rfind(haystack[], needle, pred); 300 } 301 302 /// 303 unittest 304 { 305 auto buffer = createBuffer([ 1, 2, 3 ]); 306 test!("==")(rfind(buffer, 1), 0); 307 } 308 309 /// ditto 310 size_t rfind ( T, Pred = DefaultPredicates.IsEqual!(T) ) 311 ( ref Buffer!(T) haystack, in T needle, Pred pred = Pred.init ) 312 { 313 return rfind(haystack[], needle, pred); 314 } 315 316 /// 317 unittest 318 { 319 auto buffer = createBuffer([ 1, 2, 3 ]); 320 test!("==")(rfind(buffer, [ 2, 3 ]), 1); 321 } 322 323 unittest 324 { 325 // null parameters 326 test!("==")(rfind( "", ""[] ), 0 ); 327 test!("==")(rfind( "a", ""[] ), 1 ); 328 test!("==")(rfind( "", "a"[] ), 0 ); 329 330 // exact match 331 test!("==")(rfind( "abc", "abc"[] ), 0 ); 332 333 // simple substring match 334 test!("==")(rfind( "abc", "a"[] ), 0 ); 335 test!("==")(rfind( "abca", "a"[] ), 3 ); 336 test!("==")(rfind( "abc", "b"[] ), 1 ); 337 test!("==")(rfind( "abc", "c"[] ), 2 ); 338 test!("==")(rfind( "abc", "d"[] ), 3 ); 339 340 // multi-char substring match 341 test!("==")(rfind( "abc", "ab"[] ), 0 ); 342 test!("==")(rfind( "abcab", "ab"[] ), 3 ); 343 test!("==")(rfind( "abc", "bc"[] ), 1 ); 344 test!("==")(rfind( "abc", "ac"[] ), 3 ); 345 test!("==")(rfind( "abracadabrabra", "abracadabra"[] ), 0 ); 346 347 // custom predicate 348 bool foo(char a, char b) 349 { 350 return a == 'x'; 351 } 352 353 test!("==")(rfind( "axcdxa", 'b', &foo ), 4 ); 354 test!("==")(rfind( "axcdxa", "b"[], &foo ), 4 ); 355 356 } 357 358 /******************************************************************************* 359 360 Performs a linear scan of haystack from [0 .. haystack.length$(RP), returning 361 the index of the first element matching needle, or haystack.length if no match 362 was found. Comparisons will be performed using the supplied predicate 363 or '==' if none is supplied. 364 365 This function uses the KMP algorithm and offers O(M+N) performance but 366 must allocate a temporary buffer of size needle.sizeof to do so. If it is 367 available on the target system, alloca will be used for the allocation, 368 otherwise a standard dynamic memory allocation will occur. 369 370 Params: 371 haystack = The array to search. 372 needle = The pattern to search for. 373 pred = The evaluation predicate, which should return true if e1 is 374 equal to e2 and false if not. This predicate may be any 375 callable type. 376 377 Returns: 378 The index of the first match or haystack.length if no match was found. 379 380 *******************************************************************************/ 381 382 size_t kfind (T, Pred = DefaultPredicates.IsEqual!(T)) 383 ( in T[] haystack, in T needle, Pred pred = Pred.init ) 384 { 385 return find(haystack, needle, pred); 386 } 387 388 /// 389 unittest 390 { 391 test!("==")(kfind( "abc", 'b' ), 1); 392 } 393 394 /// ditto 395 size_t kfind (T, Pred = DefaultPredicates.IsEqual!(T)) 396 ( in T[] haystack, in T[] needle, Pred pred = Pred.init ) 397 { 398 static assert (isCallableType!(Pred)); 399 400 if( haystack.length == 0 || 401 needle.length == 0 || 402 haystack.length < needle.length ) 403 { 404 return haystack.length; 405 } 406 407 static Buffer!(size_t) func; 408 func.length = needle.length + 1; 409 410 func[0] = 0; 411 412 // building prefix-function 413 for( size_t m = 0, i = 1 ; i < needle.length ; ++i ) 414 { 415 while( ( m > 0 ) && !pred( needle[m], needle[i] ) ) 416 m = *func[m - 1]; 417 if( pred( needle[m], needle[i] ) ) 418 ++m; 419 func[i] = m; 420 } 421 422 // searching 423 for( size_t m = 0, i = 0; i < haystack.length; ++i ) 424 { 425 while( ( m > 0 ) && !pred( needle[m], haystack[i] ) ) 426 m = *func[m - 1]; 427 if( pred( needle[m], haystack[i] ) ) 428 { 429 ++m; 430 if( m == needle.length ) 431 { 432 return i - needle.length + 1; 433 } 434 } 435 } 436 437 return haystack.length; 438 } 439 440 /// 441 unittest 442 { 443 test!("==")( kfind( "abc", "a" ), 0 ); 444 } 445 446 /// ditto 447 size_t kfind ( T, Pred = DefaultPredicates.IsEqual!(T) ) 448 ( ref Buffer!(T) haystack, in T[] needle, Pred pred = Pred.init ) 449 { 450 return kfind(haystack[], needle[0..needle.length], pred); 451 } 452 453 /// 454 unittest 455 { 456 auto buffer = createBuffer([ 1, 2, 3 ]); 457 test!("==")(kfind(buffer, 1), 0); 458 } 459 460 /// ditto 461 size_t kfind ( T, Pred = DefaultPredicates.IsEqual!(T) ) 462 ( ref Buffer!(T) haystack, in T needle, Pred pred = Pred.init ) 463 { 464 return kfind(haystack[], needle, pred); 465 } 466 467 /// 468 unittest 469 { 470 auto buffer = createBuffer([ 1, 2, 3 ]); 471 test!("==")(kfind(buffer, [ 2, 3 ]), 1); 472 } 473 474 unittest 475 { 476 // find element 477 test!("==")( kfind( ""[], 'a' ), 0 ); 478 test!("==")( kfind( "abc"[], 'a' ), 0 ); 479 test!("==")( kfind( "abc"[], 'b' ), 1 ); 480 test!("==")( kfind( "abc"[], 'c' ), 2 ); 481 test!("==")( kfind( "abc"[], 'd' ), 3 ); 482 483 // null parameters 484 test!("==")( kfind( ""[], ""[] ), 0 ); 485 test!("==")( kfind( "a"[], ""[] ), 1 ); 486 test!("==")( kfind( ""[], "a"[] ), 0 ); 487 488 // exact match 489 test!("==")( kfind( "abc"[], "abc"[] ), 0 ); 490 491 // simple substring match 492 test!("==")( kfind( "abc"[], "a"[] ), 0 ); 493 test!("==")( kfind( "abca"[], "a"[] ), 0 ); 494 test!("==")( kfind( "abc"[], "b"[] ), 1 ); 495 test!("==")( kfind( "abc"[], "c"[] ), 2 ); 496 test!("==")( kfind( "abc"[], "d"[] ), 3 ); 497 498 // multi-char substring match 499 test!("==")( kfind( "abc"[], "ab"[] ), 0 ); 500 test!("==")( kfind( "abcab"[], "ab"[] ), 0 ); 501 test!("==")( kfind( "abc"[], "bc"[] ), 1 ); 502 test!("==")( kfind( "abc"[], "ac"[] ), 3 ); 503 test!("==")( kfind( "abrabracadabra"[], "abracadabra"[] ), 3 ); 504 } 505 506 /******************************************************************************* 507 508 Performs a linear scan of haystack from [0 .. haystack.length$(RP), returning 509 the index of the first element where pred returns true. 510 511 Params: 512 haystack = The array to search. 513 pred = The evaluation predicate, which should return true if the 514 element is a valid match and false if not. This predicate 515 may be any callable type. 516 517 Returns: 518 The index of the first match or haystack.length if no match was found. 519 520 *******************************************************************************/ 521 522 size_t findIf ( T, Pred ) ( in T[] haystack, Pred pred ) 523 { 524 static assert( isCallableType!(Pred) ); 525 526 foreach( pos, cur; haystack ) 527 { 528 if( pred( cur ) ) 529 return pos; 530 } 531 return haystack.length; 532 } 533 534 /// 535 unittest 536 { 537 test!("==")(findIf("bcecg", ( char c ) { return c == 'a'; }), 5); 538 } 539 540 unittest 541 { 542 struct S 543 { 544 mstring err; 545 int x; 546 } 547 548 S[] sarr; 549 findIf(sarr, ( in S s ) { return s.x == 5; }); 550 } 551 552 /// ditto 553 size_t findIf ( T, Pred ) ( ref Buffer!(T) haystack, Pred pred ) 554 { 555 return findIf(haystack[], pred); 556 } 557 558 /// 559 unittest 560 { 561 auto buffer = createBuffer("bcecg"); 562 test!("==")(findIf(buffer, ( char c ) { return c == 'a'; }), 5); 563 } 564 565 unittest 566 { 567 test!("==")( findIf( "bcecg"[], ( char c ) { return c == 'a'; } ), 5 ); 568 test!("==")( findIf( "bcecg"[], ( char c ) { return c == 'b'; } ), 0 ); 569 test!("==")( findIf( "bcecg"[], ( char c ) { return c == 'c'; } ), 1 ); 570 test!("==")( findIf( "bcecg"[], ( char c ) { return c == 'd'; } ), 5 ); 571 test!("==")( findIf( "bcecg"[], ( char c ) { return c == 'g'; } ), 4 ); 572 test!("==")( findIf( "bcecg"[], ( char c ) { return c == 'h'; } ), 5 ); 573 } 574 575 /******************************************************************************* 576 577 Performs a linear scan of haystack from $(LP)haystack.length .. 0], returning 578 the index of the first element where pred returns true. 579 580 Params: 581 haystack = The array to search. 582 pred = The evaluation predicate, which should return true if the 583 element is a valid match and false if not. This predicate 584 may be any callable type. 585 586 Returns: 587 The index of the first match or haystack.length if no match was found. 588 589 *******************************************************************************/ 590 591 size_t rfindIf ( T, Pred ) ( in T[] haystack, Pred pred ) 592 { 593 static assert( isCallableType!(Pred) ); 594 595 if( haystack.length == 0 ) 596 return haystack.length; 597 598 size_t pos = haystack.length; 599 600 do 601 { 602 if( pred( haystack[--pos] ) ) 603 return pos; 604 } while( pos > 0 ); 605 return haystack.length; 606 } 607 608 /// 609 unittest 610 { 611 test!("==")(rfindIf("bcecg", ( char c ) { return c == 'a'; }), 5); 612 } 613 614 /// ditto 615 size_t rfindIf ( T, Pred ) ( ref Buffer!(T) haystack, Pred pred ) 616 { 617 return rfindIf(haystack[], pred); 618 } 619 620 /// 621 unittest 622 { 623 auto buffer = createBuffer("bcecg"); 624 test!("==")(rfindIf(buffer, ( char c ) { return c == 'a'; }), 5); 625 } 626 627 unittest 628 { 629 test!("==")(rfindIf("", ( char c ) { return c == 'a'; }), 0); 630 test!("==")(rfindIf("bcecg", ( char c ) { return c == 'a'; } ), 5); 631 test!("==")(rfindIf("bcecg", ( char c ) { return c == 'b'; } ), 0); 632 test!("==")(rfindIf("bcecg", ( char c ) { return c == 'c'; } ), 3); 633 test!("==")(rfindIf("bcecg", ( char c ) { return c == 'd'; } ), 5); 634 test!("==")(rfindIf("bcecg", ( char c ) { return c == 'g'; } ), 4); 635 test!("==")(rfindIf("bcecg", ( char c ) { return c == 'h'; } ), 5); 636 } 637 638 /******************************************************************************* 639 640 Performs a linear scan of haystack from [0 .. haystack.length$(RP), returning 641 the index of the first element that compares equal to the next element 642 in the sequence. Comparisons will be performed using the supplied 643 predicate or '==' if none is supplied. 644 645 Params: 646 haystack = The array to scan. 647 pred = The evaluation predicate, which should return true if e1 is 648 equal to e2 and false if not. This predicate may be any 649 callable type. 650 651 Returns: 652 The index of the first match or haystack.length if no match was found. 653 654 *******************************************************************************/ 655 656 size_t findAdj( T, Pred = DefaultPredicates.IsEqual!(T) ) 657 ( in T[] haystack, Pred pred = Pred.init ) 658 { 659 static assert( isCallableType!(Pred) ); 660 661 if( haystack.length < 2 ) 662 return haystack.length; 663 664 Unqual!(T) sav = haystack[0]; 665 666 foreach( size_t pos, T cur; haystack[1 .. $] ) 667 { 668 if( pred( cur, sav ) ) 669 return pos; 670 sav = cur; 671 } 672 return haystack.length; 673 } 674 675 /// 676 unittest 677 { 678 test!("==")(findAdj("abcddef"), 3); 679 } 680 681 /// ditto 682 size_t findAdj( T, Pred = DefaultPredicates.IsEqual!(T) ) 683 ( ref Buffer!(T) haystack, Pred pred = Pred.init ) 684 { 685 return findAdj(haystack[], pred); 686 } 687 688 /// 689 unittest 690 { 691 auto buffer = createBuffer("abcddef"); 692 test!("==")(findAdj(buffer), 3); 693 } 694 695 unittest 696 { 697 test!("==")(findAdj(""), 0); 698 test!("==")(findAdj("aabcdef"), 0); 699 test!("==")(findAdj("abcdeff"), 5); 700 test!("==")(findAdj("abcdefg"), 7); 701 } 702 703 /******************************************************************************* 704 705 Performs a linear scan of haystack from [0 .. haystack.length$(RP), returning 706 true if an element matching needle is found. Comparisons will be performed 707 using the supplied predicate or '==' if none is supplied. 708 709 Params: 710 haystack = The array to search. 711 needle = The pattern to search for. 712 pred = The evaluation predicate, which should return true if e1 is 713 equal to e2 and false if not. This predicate may be any 714 callable type. 715 716 Returns: 717 True if an element equivalent to needle is found, false if not. 718 719 *******************************************************************************/ 720 721 equals_t contains ( T, Pred = DefaultPredicates.IsEqual!(T) ) 722 ( in T[] haystack, in T needle, Pred pred = Pred.init ) 723 { 724 return find(haystack, needle, pred) != haystack.length; 725 } 726 727 /// 728 unittest 729 { 730 test( contains("abc", 'a')); 731 test(!contains("abc", 'd')); 732 } 733 734 /// ditto 735 equals_t contains ( T, Pred = DefaultPredicates.IsEqual!(T) ) 736 ( in T[] haystack, in T[] needle, Pred pred = Pred.init ) 737 { 738 return find(haystack, needle, pred) != haystack.length; 739 } 740 741 /// 742 unittest 743 { 744 test( contains("abc", "a")); 745 test(!contains("abc", "d")); 746 } 747 748 /// ditto 749 equals_t contains ( T, Pred = DefaultPredicates.IsEqual!(T) ) 750 ( ref Buffer!(T) haystack, in T needle, Pred pred = Pred.init ) 751 { 752 return find(haystack, needle, pred) != haystack.length; 753 } 754 755 /// 756 unittest 757 { 758 auto buffer = createBuffer("abc"); 759 test( contains(buffer, 'a')); 760 test(!contains(buffer, 'd')); 761 } 762 763 /// ditto 764 equals_t contains ( T, Pred = DefaultPredicates.IsEqual!(T) ) 765 ( ref Buffer!(T) haystack, in T[] needle, Pred pred = Pred.init ) 766 { 767 return find(haystack, needle, pred) != haystack.length; 768 } 769 770 /// 771 unittest 772 { 773 auto buffer = createBuffer("abc"); 774 test( contains(buffer, "a")); 775 test(!contains(buffer, "d")); 776 } 777 778 unittest 779 { 780 mixin (Typedef!(hash_t, "Hash")); 781 auto id = cast(Hash) 42; 782 Hash[] arr = [ cast(Hash) 1, cast(Hash) 2, cast(Hash) 3 ]; 783 auto result = contains(arr, id); 784 } 785 786 unittest 787 { 788 // find element 789 test(!contains( ""[], 'a' )); 790 test( contains( "abc"[], 'a' )); 791 test( contains( "abc"[], 'b' )); 792 test( contains( "abc"[], 'c' )); 793 test(!contains( "abc"[], 'd' )); 794 795 // null parameters 796 test(!contains( ""[], ""[] )); 797 test(!contains( "a"[], ""[] )); 798 test(!contains( ""[], "a"[] )); 799 800 // exact match 801 test( contains( "abc"[], "abc"[] )); 802 803 // simple substring match 804 test( contains( "abc"[], "a"[] )); 805 test( contains( "abca"[], "a"[] )); 806 test( contains( "abc"[], "b"[] )); 807 test( contains( "abc"[], "c"[] )); 808 test(!contains( "abc"[], "d"[] )); 809 810 // multi-char substring match 811 test( contains( "abc"[], "ab"[] )); 812 test( contains( "abcab"[], "ab"[] )); 813 test( contains( "abc"[], "bc"[] )); 814 test(!contains( "abc"[], "ac"[] )); 815 test( contains( "abrabracadabra"[], "abracadabra"[] )); 816 } 817 818 /******************************************************************************* 819 820 Performs a parallel linear scan of arr and arr_against from [0 .. N$(RP) 821 where N = min(arr.length, arr_against.length), returning the index of 822 the first element in arr which does not match the corresponding element 823 in arr_against or N if no mismatch occurs. Comparisons will be performed 824 using the supplied predicate or '==' if none is supplied. 825 826 If the input arrays are not sorted in the same way, or they have different 827 number of duplicate items, see `bsubset`. 828 829 Params: 830 arr = The array to evaluate. 831 arr_against = The array to match against. 832 pred = The evaluation predicate, which should return true if e1 833 is equal to e2 and false if not. This predicate may be any 834 callable type. 835 836 Returns: 837 The index of the first mismatch or N if the first N elements of arr 838 and arr_against match, where 839 N = min$(LP)arr.length, arr_against.length$(RP). 840 841 *******************************************************************************/ 842 843 size_t mismatch ( T, Pred = DefaultPredicates.IsEqual!(T) ) 844 ( in T[] arr, in T[] arr_against, Pred pred = Pred.init ) 845 { 846 static assert( isCallableType!(Pred) ); 847 848 size_t posA = 0, 849 posB = 0; 850 851 while( posA < arr.length && posB < arr_against.length ) 852 { 853 if( !pred( arr_against[posB], arr[posA] ) ) 854 break; 855 ++posA, ++posB; 856 } 857 return posA; 858 } 859 860 /// 861 unittest 862 { 863 // result must not change from swapping argument order: 864 test!("==")(mismatch("abcxefg", "abcdefg"), 3); 865 test!("==")(mismatch("abcdefg", "abcxefg"), 3); 866 } 867 868 /// ditto 869 size_t mismatch ( T, Pred = DefaultPredicates.IsEqual!(T) ) 870 ( ref Buffer!(T) arr, ref Buffer!(T) arr_against, Pred pred = Pred.init ) 871 { 872 return mismatch(arr[0..arr.length], 873 arr_against[0..arr_against.length], pred); 874 } 875 876 /// 877 unittest 878 { 879 // result must not change from swapping argument order: 880 auto buffer1 = createBuffer("abcxefg"); 881 auto buffer2 = createBuffer("abcdefg"); 882 test!("==")( mismatch(buffer1, buffer2), 3 ); 883 test!("==")( mismatch(buffer2, buffer1), 3 ); 884 } 885 886 unittest 887 { 888 test!("==")( mismatch( "a"[], "abcdefg"[] ), 1 ); 889 test!("==")( mismatch( "abcdefg"[], "a"[] ), 1 ); 890 891 test!("==")( mismatch( "x"[], "abcdefg"[] ), 0 ); 892 test!("==")( mismatch( "abcdefg"[], "x"[] ), 0 ); 893 894 test!("==")( mismatch( "xbcdefg"[], "abcdefg"[] ), 0 ); 895 test!("==")( mismatch( "abcdefg"[], "xbcdefg"[] ), 0 ); 896 897 test!("==")( mismatch( "abcxefg"[], "abcdefg"[] ), 3 ); 898 test!("==")( mismatch( "abcdefg"[], "abcxefg"[] ), 3 ); 899 900 test!("==")( mismatch( "abcdefx"[], "abcdefg"[] ), 6 ); 901 test!("==")( mismatch( "abcdefg"[], "abcdefx"[] ), 6 ); 902 } 903 904 /****************************************************************************** 905 906 Performs a linear scan of haystack from [0 .. haystack.length$(RP), returning 907 a count of the number of elements matching needle. Comparisons will be 908 performed using the supplied predicate or '==' if none is supplied. 909 910 Params: 911 haystack = The array to scan. 912 needle = The pattern to match. 913 pred = The evaluation predicate, which should return true if e1 is 914 equal to e2 and false if not. This predicate may be any 915 callable type. 916 917 Returns: 918 The number of elements matching needle. 919 920 ******************************************************************************/ 921 922 size_t count ( T, Pred = DefaultPredicates.IsEqual!(T) ) 923 ( in T[] haystack, in T needle, Pred pred = Pred.init ) 924 { 925 static assert( isCallableType!(Pred) ); 926 927 size_t cnt = 0; 928 929 foreach( size_t pos, T cur; haystack ) 930 { 931 if( pred( cur, needle ) ) 932 ++cnt; 933 } 934 return cnt; 935 } 936 937 /// 938 unittest 939 { 940 test!("==")(count("gbbbi", 'b'), 3); 941 } 942 943 /// ditto 944 size_t count ( T, Pred = DefaultPredicates.IsEqual!(T) ) 945 ( ref Buffer!(T) haystack, in T needle, Pred pred = Pred.init ) 946 { 947 return count(haystack[], needle, pred); 948 } 949 950 /// 951 unittest 952 { 953 auto buffer = createBuffer("gbbbi"); 954 test!("==")(count(buffer, 'b'), 3); 955 } 956 957 unittest 958 { 959 test!("==")( count( "gbbbi"[], 'a' ), 0 ); 960 test!("==")( count( "gbbbi"[], 'g' ), 1 ); 961 test!("==")( count( "gbbbi"[], 'b' ), 3 ); 962 test!("==")( count( "gbbbi"[], 'i' ), 1 ); 963 test!("==")( count( "gbbbi"[], 'd' ), 0 ); 964 } 965 966 /******************************************************************************* 967 968 Performs a linear scan of haystack from [0 .. haystack.length$(RP), returning 969 a count of the number of elements where pred returns true. 970 971 Params: 972 haystack = The array to scan. 973 pred = The evaluation predicate, which should return true if the 974 element is a valid match and false if not. This predicate 975 may be any callable type. 976 977 Returns: 978 The number of elements where pred returns true. 979 980 *******************************************************************************/ 981 982 size_t countIf ( T, Pred = DefaultPredicates.IsEqual!(T) ) 983 ( in T[] haystack, Pred pred = Pred.init ) 984 { 985 static assert( isCallableType!(Pred) ); 986 987 size_t cnt = 0; 988 989 foreach( size_t pos, T cur; haystack ) 990 { 991 if( pred( cur ) ) 992 ++cnt; 993 } 994 return cnt; 995 } 996 997 /// 998 unittest 999 { 1000 test!("==")(countIf("gbbbi", ( char c ) { return c == 'b'; }), 3); 1001 } 1002 1003 size_t countIf ( T, Pred = DefaultPredicates.IsEqual!(T) ) 1004 ( ref Buffer!(T) haystack, Pred pred = Pred.init ) 1005 { 1006 return countIf(haystack[], pred); 1007 } 1008 1009 /// 1010 unittest 1011 { 1012 auto buffer = createBuffer("gbbbi"); 1013 test!("==")(countIf(buffer, ( char c ) { return c == 'b'; }), 3); 1014 } 1015 1016 unittest 1017 { 1018 test!("==")( countIf( "gbbbi"[], ( char c ) { return c == 'a'; } ), 0 ); 1019 test!("==")( countIf( "gbbbi"[], ( char c ) { return c == 'g'; } ), 1 ); 1020 test!("==")( countIf( "gbbbi"[], ( char c ) { return c == 'b'; } ), 3 ); 1021 test!("==")( countIf( "gbbbi"[], ( char c ) { return c == 'i'; } ), 1 ); 1022 test!("==")( countIf( "gbbbi"[], ( char c ) { return c == 'd'; } ), 0 ); 1023 } 1024 1025 /******************************************************************************* 1026 1027 Checks if array B is a subset of array A. Array A is assumed to be 1028 pre-sorted in ascending order. Array B doesn't need to be sorted and might 1029 have duplicate elements. It checks if array A contains each item of array B 1030 using binary search. If T is a class or struct, comparison is performed 1031 using T.opCmp(). Otherwise, elements of T are compared using ">" and ">=" 1032 or, if T is compatible to size_t (which includes ssize_t, the signed version 1033 of size_t), by calculating the difference. 1034 1035 If both of the arrays are sorted in the same way and don't contain different 1036 number of duplicate items, see `mismatch`. 1037 1038 Params: 1039 a = array A 1040 b = array B 1041 1042 Returns: 1043 false if there is any item of array B, which is not an item of array A 1044 1045 In: 1046 See `bsearch` for input constraints 1047 1048 *******************************************************************************/ 1049 1050 public bool bsubset ( T ) ( T[] a, T[] b ) 1051 { 1052 if ( a.length == 0 ) 1053 return b.length == 0; 1054 1055 foreach ( ref T item; b ) 1056 { 1057 if ( !bcontains(a, item) ) 1058 return false; 1059 } 1060 1061 return true; 1062 } 1063 1064 unittest 1065 { 1066 test(bsubset!(int)([], [])); 1067 test(!bsubset!(int)([], [0])); 1068 test(bsubset!(int)([0], [])); 1069 test(bsubset([0], [0])); 1070 test(!bsubset([0], [1])); 1071 test(bsubset([0, 1], [1])); 1072 test(bsubset([0, 1], [0, 1, 1])); 1073 } 1074 1075 /******************************************************************************* 1076 1077 Searches a sorted array for the specified element. The array is assumed to 1078 be pre-sorted in ascending order, the search will not work properly if it 1079 is not. If T is a class or struct, comparison is performed using T.opCmp(). 1080 Otherwise, elements of T are compared using ">" and ">=" or, if T is 1081 compatible to size_t (which includes ssize_t, the signed version of size_t), 1082 by calculating the difference. 1083 1084 Params: 1085 array = array to search 1086 match = element to search for 1087 1088 Returns: 1089 true if the element was found in the array 1090 1091 In: 1092 See `bsearch` for input constraints 1093 1094 *******************************************************************************/ 1095 1096 public bool bcontains ( T ) ( T[] array, T match ) 1097 { 1098 size_t position; 1099 1100 return bsearch(array, match, position); 1101 } 1102 1103 unittest 1104 { 1105 auto arr = [ 1, 2, 4, 6, 20, 100, 240 ]; 1106 1107 test(bcontains(arr, 6)); 1108 test(!bcontains(arr, 0)); 1109 test(!bcontains(arr, 300)); 1110 test(!bcontains(arr, 10)); 1111 } 1112 1113 /******************************************************************************* 1114 1115 Searches a sorted array for the specified element or for the insert 1116 position of the element. The array is assumed to be pre-sorted in ascending 1117 order, the search will not work properly if it is not. 1118 If T is a class or struct, comparison is performed using T.opCmp(). 1119 Otherwise, elements of T are compared using ">" and ">=" or, if T is 1120 compatible to size_t (which includes ssize_t, the signed version of size_t), 1121 by calculating the difference. 1122 1123 Params: 1124 array = array to search 1125 match = element to search for 1126 position = out value, value depends on whether the element was found: 1127 1128 1. If found, the position at which element was found is output. 1129 1130 2. If not found, the position at which the element could be inserted 1131 is output, as follows: 1132 1133 * A value of 0 means that the element is smaller than all 1134 elements in the array, and would need to be inserted at the 1135 beginning of the array, and all other elements shifted to the 1136 right. 1137 * A value of array.length means that the element is larger than 1138 all elements in the array, and would need to be appended to the 1139 end of the array. 1140 * A value of > 0 and < array.length means that the element would 1141 need to be inserted at the specified position, and all elements 1142 of index >= the specified position shifted to the right. 1143 1144 Returns: 1145 true if the element was found in the array 1146 1147 In: 1148 array.length must be at most ssize_t.max (int.max if size_t is uint or 1149 long.max if size_t is ulong). TODO: Remove this restriction by 1150 rephrasing the implementation in bsearchCustom(). 1151 1152 *******************************************************************************/ 1153 1154 public bool bsearch ( T ) ( T[] array, T match, out size_t position ) 1155 out (found) 1156 { 1157 if (found) 1158 { 1159 assert (position < array.length); 1160 } 1161 else 1162 { 1163 assert (position <= array.length); 1164 } 1165 } 1166 do 1167 { 1168 return bsearchCustom( 1169 array.length, 1170 delegate ssize_t ( size_t i ) 1171 { 1172 static if (is (T : size_t)) // will also be true if T is ssize_t 1173 { 1174 // If T is unsigned, check if cast (ssize_t) (0 - 1) == -1. 1175 // TODO: Is this behaviour guaranteed? If so, remove the 1176 // check. 1177 1178 static if (T.min == 0) 1179 { 1180 static assert (cast (ssize_t) (T.min - cast (T) 1) == -1, 1181 "bsearch: 0 - 1 != -1 for type " ~ T.stringof); 1182 } 1183 1184 return match - array[i]; 1185 } 1186 else static if (is (T == class) || is (T == struct)) 1187 { 1188 return match.opCmp(array[i]); 1189 } 1190 else 1191 { 1192 return (match >= array[i])? (match > array[i]) : -1; 1193 } 1194 }, 1195 position 1196 ); 1197 } 1198 1199 unittest 1200 { 1201 auto arr = [ 1, 2, 4, 6, 20, 100, 240 ]; 1202 size_t pos; 1203 bool found = bsearch(arr, 6, pos); 1204 } 1205 1206 /******************************************************************************* 1207 1208 Searches a sorted array for an element or an insert position for an element. 1209 The array is assumed to be pre-sorted according to cmp. 1210 1211 Params: 1212 array_length = length of array to search 1213 cmp = comparison callback delegate, should return 1214 * a positive value if the array element at index i compares 1215 greater than the element to search for, 1216 * a negative value if the array element at index i compares 1217 less than the element to search for, 1218 * 0 if if the array element at index i compares equal to 1219 the element to search for. 1220 position = out value, value depends on whether the element was found: 1221 1222 1. If found, the position at which element was found is output. 1223 1224 2. If not found, the position at which the element could be inserted 1225 is output, as follows: 1226 1227 * A value of 0 means that the element is smaller than all 1228 elements in the array, and would need to be inserted at the 1229 beginning of the array, and all other elements shifted to the 1230 right. 1231 * A value of array.length means that the element is larger than 1232 all elements in the array, and would need to be appended to the 1233 end of the array. 1234 * A value of > 0 and < array.length means that the element would 1235 need to be inserted at the specified position, and all elements 1236 of index >= the specified position shifted to the right. 1237 1238 Returns: 1239 true if the element was found in the array 1240 1241 In: 1242 array_length must be at most ssize_t.max (int.max if size_t is uint or 1243 long.max if size_t is ulong). TODO: Remove this restriction by 1244 rephrasing the implementation so that min/max cannot be less than 0. 1245 1246 *******************************************************************************/ 1247 1248 public bool bsearchCustom ( size_t array_length, scope ssize_t delegate ( size_t i ) cmp, out size_t position ) 1249 out (found) 1250 { 1251 if (found) 1252 { 1253 assert (position < array_length); 1254 } 1255 else 1256 { 1257 assert (position <= array_length); 1258 } 1259 } 1260 do 1261 { 1262 verify(cast (ssize_t) array_length >= 0, 1263 "bsearchCustom: array_length integer overflow (maximum is " ~ 1264 ssize_t.stringof ~ ".max = " ~ ssize_t.max.stringof ~ ')'); 1265 1266 if ( array_length == 0 ) 1267 { 1268 return false; 1269 } 1270 1271 ssize_t min = 0; 1272 ssize_t max = array_length - 1; 1273 1274 ssize_t c = cmp(position = (min + max) / 2); 1275 1276 while ( min <= max && c ) 1277 { 1278 if ( c < 0 ) // match < array[position] 1279 { 1280 max = position - 1; 1281 } 1282 else // match > array[position] 1283 { 1284 min = position + 1; 1285 } 1286 1287 c = cmp(position = (min + max) / 2); 1288 } 1289 1290 position += c > 0; 1291 1292 return !c; 1293 } 1294 1295 /******************************************************************************* 1296 1297 Performs a parallel linear scan of setA and setB from [0 .. N$(RP) 1298 where N = min(setA.length, setB.length), returning true if setA 1299 includes all elements in setB and false if not. Both setA and setB are 1300 required to be sorted, and duplicates in setB require an equal number of 1301 duplicates in setA. Comparisons will be performed using the supplied 1302 predicate or '<' if none is supplied. 1303 1304 Params: 1305 setA = The sorted array to evaluate. 1306 setB = The sorted array to match against. 1307 pred = The evaluation predicate, which should return true if e1 is 1308 less than e2 and false if not. This predicate may be any 1309 callable type. 1310 1311 Returns: 1312 True if setA includes all elements in setB, false if not. 1313 1314 *******************************************************************************/ 1315 1316 bool includes ( T, Pred = DefaultPredicates.IsLess!(T) ) 1317 ( in T[] setA, in T[] setB, Pred pred = Pred.init ) 1318 { 1319 static assert( isCallableType!(Pred ) ); 1320 1321 size_t posA = 0, 1322 posB = 0; 1323 1324 while( posA < setA.length && posB < setB.length ) 1325 { 1326 if( pred( setB[posB], setA[posA] ) ) 1327 return false; 1328 else if( pred( setA[posA], setB[posB] ) ) 1329 ++posA; 1330 else 1331 ++posA, ++posB; 1332 } 1333 return posB == setB.length; 1334 } 1335 1336 /// 1337 unittest 1338 { 1339 test(includes("abcdefg", "cde")); 1340 } 1341 1342 unittest 1343 { 1344 test( includes( "abcdefg"[], "a"[] ) ); 1345 test( includes( "abcdefg"[], "g"[] ) ); 1346 test( includes( "abcdefg"[], "d"[] ) ); 1347 test( includes( "abcdefg"[], "abcdefg"[] ) ); 1348 test( includes( "aaaabbbcdddefgg"[], "abbbcdefg"[] ) ); 1349 1350 test( !includes( "abcdefg"[], "aaabcdefg"[] ) ); 1351 test( !includes( "abcdefg"[], "abcdefggg"[] ) ); 1352 test( !includes( "abbbcdefg"[], "abbbbcdefg"[] ) ); 1353 } 1354 1355 /******************************************************************************* 1356 1357 Check if the given array starts with the given prefix 1358 1359 Params: 1360 arr = The array to be tested 1361 prefix = The prefix to test for 1362 1363 Returns: 1364 True if the array starts with the prefix, false otherwise 1365 1366 *******************************************************************************/ 1367 1368 bool startsWith ( T ) ( in T[] arr, in T[] prefix ) 1369 { 1370 // https://issues.dlang.org/show_bug.cgi?id=17917 1371 // WORKAROUND: with -O -cov compiler doesn't early return from 1372 // && expression, thus the code here is manually split into two 1373 // conditions 1374 if (arr.length < prefix.length) 1375 return false; 1376 return arr[0..prefix.length] == prefix[]; 1377 } 1378 1379 unittest 1380 { 1381 test( startsWith("abcd", "abc")); 1382 test( startsWith("abcd", "abcd")); 1383 test(!startsWith("ab", "abc")); 1384 test( startsWith("ab", "")); 1385 test(!startsWith("", "xx")); 1386 1387 test( startsWith([1,2,3,4], [1,2,3])); 1388 test( startsWith([1,2,3,4], [1,2,3,4])); 1389 test(!startsWith([1,2], [1,2,3])); 1390 test( startsWith([1,2], (int[]).init)); 1391 test(!startsWith((int[]).init, [1,2])); 1392 } 1393 1394 /******************************************************************************* 1395 1396 Check if the given array ends with the given suffix 1397 1398 Params: 1399 arr = The array to be tested 1400 suffix = The suffix to test for 1401 1402 Returns: 1403 True if the array ends with the suffix, false otherwise 1404 1405 *******************************************************************************/ 1406 1407 bool endsWith ( T ) ( in T[] arr, in T[] suffix ) 1408 { 1409 // https://issues.dlang.org/show_bug.cgi?id=17917 1410 // WORKAROUND: with -O -cov compiler doesn't early return from 1411 // && expression, thus the code here is manually split into two 1412 // conditions 1413 if (arr.length < suffix.length) 1414 return false; 1415 return arr[$ - suffix.length .. $] == suffix[]; 1416 } 1417 1418 unittest 1419 { 1420 test( endsWith("abcd", "bcd")); 1421 test( endsWith("abcd", "abcd")); 1422 test(!endsWith("ab", "abc")); 1423 test( endsWith("ab", "")); 1424 test(!endsWith("", "xx")); 1425 1426 test( endsWith([1,2,3,4], [2,3,4])); 1427 test( endsWith([1,2,3,4], [1,2,3,4])); 1428 test(!endsWith([1,2], [1,2,3])); 1429 test( endsWith([1,2], (int[]).init)); 1430 test(!endsWith((int[]).init, [1,2])); 1431 } 1432 1433 /******************************************************************************* 1434 1435 Remove the given prefix from the given array. 1436 1437 Params: 1438 arr = The array from which the prefix is to be removed 1439 prefix = The prefix to remove 1440 1441 Returns: 1442 A slice without the prefix if successful, the original array otherwise 1443 1444 *******************************************************************************/ 1445 1446 public T1[] removePrefix ( T1, T2 ) ( T1[] arr, in T2[] prefix ) 1447 { 1448 return ((arr.length >= prefix.length) && (startsWith(arr, prefix)) 1449 ? arr[prefix.length .. $] 1450 : arr); 1451 } 1452 1453 unittest 1454 { 1455 test(removePrefix("abcd", "abc") == "d"); 1456 test(removePrefix("abcd", "abcd") == ""); 1457 test(removePrefix("abcd", "abcde") == "abcd"); 1458 test(removePrefix("abcd", "") == "abcd"); 1459 test(removePrefix("", "xx") == ""); 1460 test("abcd".removePrefix("abc") == "d"); 1461 test("abcd".removePrefix("abcd") == ""); 1462 test("abcd".removePrefix("abcde") == "abcd"); 1463 test("abcd".removePrefix("") == "abcd"); 1464 test("".removePrefix("xx") == ""); 1465 1466 test(removePrefix([1,2,3,4], [1,2,3]) == [ 4 ]); 1467 test(removePrefix([1,2,3,4], [1,2,3,4]) == cast(int[]) null); 1468 test(removePrefix([1,2], [1,2,3]) == [ 1, 2 ]); 1469 test(removePrefix([1,2], (int[]).init) == [ 1, 2 ]); 1470 test(removePrefix((int[]).init, [1,2]) == cast(int[]) null); 1471 } 1472 1473 /******************************************************************************* 1474 1475 Remove the given suffix from the given array. 1476 1477 Params: 1478 arr = The array from which the suffix is to be removed 1479 suffix = The suffix to remove 1480 1481 Returns: 1482 A slice without the suffix if successful, the original array otherwise 1483 1484 *******************************************************************************/ 1485 1486 public T1[] removeSuffix ( T1, T2 ) ( T1[] arr, in T2[] suffix ) 1487 { 1488 return ((arr.length >= suffix.length) && (endsWith(arr, suffix)) 1489 ? arr[0 .. $-suffix.length] 1490 : arr); 1491 } 1492 1493 unittest 1494 { 1495 test(removeSuffix("abcd", "cd") == "ab"); 1496 test(removeSuffix("abcd", "abcd") == ""); 1497 test(removeSuffix("abcd", "abcde") == "abcd"); 1498 test(removeSuffix("abcd", "") == "abcd"); 1499 test(removeSuffix("", "xx") == ""); 1500 test("abcd".removeSuffix("cd") == "ab"); 1501 test("abcd".removeSuffix("abcd") == ""); 1502 test("abcd".removeSuffix("abcde") == "abcd"); 1503 test("abcd".removeSuffix("") == "abcd"); 1504 test("".removeSuffix("xx") == ""); 1505 1506 test(removeSuffix([1,2,3,4], [2,3,4]) == [ 1 ]); 1507 test(removeSuffix([1,2,3,4], [1,2,3,4]) == cast(int[]) null); 1508 test(removeSuffix([1,2], [1,2,3]) == [ 1, 2 ]); 1509 test(removeSuffix([1,2], (int[]).init) == [ 1, 2 ]); 1510 test(removeSuffix((int[]).init, [1,2]) == cast(int[]) null); 1511 }