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 Template params: 1039 T = type of array element 1040 1041 Params: 1042 a = array A 1043 b = array B 1044 1045 Returns: 1046 false if there is any item of array B, which is not an item of array A 1047 1048 In: 1049 See `bsearch` for input constraints 1050 1051 *******************************************************************************/ 1052 1053 public bool bsubset ( T ) ( T[] a, T[] b ) 1054 { 1055 if ( a.length == 0 ) 1056 return b.length == 0; 1057 1058 foreach ( ref T item; b ) 1059 { 1060 if ( !bcontains(a, item) ) 1061 return false; 1062 } 1063 1064 return true; 1065 } 1066 1067 unittest 1068 { 1069 test(bsubset!(int)([], [])); 1070 test(!bsubset!(int)([], [0])); 1071 test(bsubset!(int)([0], [])); 1072 test(bsubset([0], [0])); 1073 test(!bsubset([0], [1])); 1074 test(bsubset([0, 1], [1])); 1075 test(bsubset([0, 1], [0, 1, 1])); 1076 } 1077 1078 /******************************************************************************* 1079 1080 Searches a sorted array for the specified element. The array is assumed to 1081 be pre-sorted in ascending order, the search will not work properly if it 1082 is not. If T is a class or struct, comparison is performed using T.opCmp(). 1083 Otherwise, elements of T are compared using ">" and ">=" or, if T is 1084 compatible to size_t (which includes ssize_t, the signed version of size_t), 1085 by calculating the difference. 1086 1087 Template params: 1088 T = type of array element 1089 1090 Params: 1091 array = array to search 1092 match = element to search for 1093 1094 Returns: 1095 true if the element was found in the array 1096 1097 In: 1098 See `bsearch` for input constraints 1099 1100 *******************************************************************************/ 1101 1102 public bool bcontains ( T ) ( T[] array, T match ) 1103 { 1104 size_t position; 1105 1106 return bsearch(array, match, position); 1107 } 1108 1109 unittest 1110 { 1111 auto arr = [ 1, 2, 4, 6, 20, 100, 240 ]; 1112 1113 test(bcontains(arr, 6)); 1114 test(!bcontains(arr, 0)); 1115 test(!bcontains(arr, 300)); 1116 test(!bcontains(arr, 10)); 1117 } 1118 1119 /******************************************************************************* 1120 1121 Searches a sorted array for the specified element or for the insert 1122 position of the element. The array is assumed to be pre-sorted in ascending 1123 order, the search will not work properly if it is not. 1124 If T is a class or struct, comparison is performed using T.opCmp(). 1125 Otherwise, elements of T are compared using ">" and ">=" or, if T is 1126 compatible to size_t (which includes ssize_t, the signed version of size_t), 1127 by calculating the difference. 1128 1129 Template params: 1130 T = type of array element 1131 1132 Params: 1133 array = array to search 1134 match = element to search for 1135 position = out value, value depends on whether the element was found: 1136 1137 1. If found, the position at which element was found is output. 1138 1139 2. If not found, the position at which the element could be inserted 1140 is output, as follows: 1141 1142 * A value of 0 means that the element is smaller than all 1143 elements in the array, and would need to be inserted at the 1144 beginning of the array, and all other elements shifted to the 1145 right. 1146 * A value of array.length means that the element is larger than 1147 all elements in the array, and would need to be appended to the 1148 end of the array. 1149 * A value of > 0 and < array.length means that the element would 1150 need to be inserted at the specified position, and all elements 1151 of index >= the specified position shifted to the right. 1152 1153 Returns: 1154 true if the element was found in the array 1155 1156 In: 1157 array.length must be at most ssize_t.max (int.max if size_t is uint or 1158 long.max if size_t is ulong). TODO: Remove this restriction by 1159 rephrasing the implementation in bsearchCustom(). 1160 1161 *******************************************************************************/ 1162 1163 public bool bsearch ( T ) ( T[] array, T match, out size_t position ) 1164 out (found) 1165 { 1166 if (found) 1167 { 1168 assert (position < array.length); 1169 } 1170 else 1171 { 1172 assert (position <= array.length); 1173 } 1174 } 1175 do 1176 { 1177 return bsearchCustom( 1178 array.length, 1179 delegate ssize_t ( size_t i ) 1180 { 1181 static if (is (T : size_t)) // will also be true if T is ssize_t 1182 { 1183 // If T is unsigned, check if cast (ssize_t) (0 - 1) == -1. 1184 // TODO: Is this behaviour guaranteed? If so, remove the 1185 // check. 1186 1187 static if (T.min == 0) 1188 { 1189 static assert (cast (ssize_t) (T.min - cast (T) 1) == -1, 1190 "bsearch: 0 - 1 != -1 for type " ~ T.stringof); 1191 } 1192 1193 return match - array[i]; 1194 } 1195 else static if (is (T == class) || is (T == struct)) 1196 { 1197 return match.opCmp(array[i]); 1198 } 1199 else 1200 { 1201 return (match >= array[i])? (match > array[i]) : -1; 1202 } 1203 }, 1204 position 1205 ); 1206 } 1207 1208 unittest 1209 { 1210 auto arr = [ 1, 2, 4, 6, 20, 100, 240 ]; 1211 size_t pos; 1212 bool found = bsearch(arr, 6, pos); 1213 } 1214 1215 /******************************************************************************* 1216 1217 Searches a sorted array for an element or an insert position for an element. 1218 The array is assumed to be pre-sorted according to cmp. 1219 1220 Params: 1221 array_length = length of array to search 1222 cmp = comparison callback delegate, should return 1223 * a positive value if the array element at index i compares 1224 greater than the element to search for, 1225 * a negative value if the array element at index i compares 1226 less than the element to search for, 1227 * 0 if if the array element at index i compares equal to 1228 the element to search for. 1229 position = out value, value depends on whether the element was found: 1230 1231 1. If found, the position at which element was found is output. 1232 1233 2. If not found, the position at which the element could be inserted 1234 is output, as follows: 1235 1236 * A value of 0 means that the element is smaller than all 1237 elements in the array, and would need to be inserted at the 1238 beginning of the array, and all other elements shifted to the 1239 right. 1240 * A value of array.length means that the element is larger than 1241 all elements in the array, and would need to be appended to the 1242 end of the array. 1243 * A value of > 0 and < array.length means that the element would 1244 need to be inserted at the specified position, and all elements 1245 of index >= the specified position shifted to the right. 1246 1247 Returns: 1248 true if the element was found in the array 1249 1250 In: 1251 array_length must be at most ssize_t.max (int.max if size_t is uint or 1252 long.max if size_t is ulong). TODO: Remove this restriction by 1253 rephrasing the implementation so that min/max cannot be less than 0. 1254 1255 *******************************************************************************/ 1256 1257 public bool bsearchCustom ( size_t array_length, scope ssize_t delegate ( size_t i ) cmp, out size_t position ) 1258 out (found) 1259 { 1260 if (found) 1261 { 1262 assert (position < array_length); 1263 } 1264 else 1265 { 1266 assert (position <= array_length); 1267 } 1268 } 1269 do 1270 { 1271 verify(cast (ssize_t) array_length >= 0, 1272 "bsearchCustom: array_length integer overflow (maximum is " ~ 1273 ssize_t.stringof ~ ".max = " ~ ssize_t.max.stringof ~ ')'); 1274 1275 if ( array_length == 0 ) 1276 { 1277 return false; 1278 } 1279 1280 ssize_t min = 0; 1281 ssize_t max = array_length - 1; 1282 1283 ssize_t c = cmp(position = (min + max) / 2); 1284 1285 while ( min <= max && c ) 1286 { 1287 if ( c < 0 ) // match < array[position] 1288 { 1289 max = position - 1; 1290 } 1291 else // match > array[position] 1292 { 1293 min = position + 1; 1294 } 1295 1296 c = cmp(position = (min + max) / 2); 1297 } 1298 1299 position += c > 0; 1300 1301 return !c; 1302 } 1303 1304 /******************************************************************************* 1305 1306 Performs a parallel linear scan of setA and setB from [0 .. N$(RP) 1307 where N = min(setA.length, setB.length), returning true if setA 1308 includes all elements in setB and false if not. Both setA and setB are 1309 required to be sorted, and duplicates in setB require an equal number of 1310 duplicates in setA. Comparisons will be performed using the supplied 1311 predicate or '<' if none is supplied. 1312 1313 Params: 1314 setA = The sorted array to evaluate. 1315 setB = The sorted array to match against. 1316 pred = The evaluation predicate, which should return true if e1 is 1317 less than e2 and false if not. This predicate may be any 1318 callable type. 1319 1320 Returns: 1321 True if setA includes all elements in setB, false if not. 1322 1323 *******************************************************************************/ 1324 1325 bool includes ( T, Pred = DefaultPredicates.IsLess!(T) ) 1326 ( in T[] setA, in T[] setB, Pred pred = Pred.init ) 1327 { 1328 static assert( isCallableType!(Pred ) ); 1329 1330 size_t posA = 0, 1331 posB = 0; 1332 1333 while( posA < setA.length && posB < setB.length ) 1334 { 1335 if( pred( setB[posB], setA[posA] ) ) 1336 return false; 1337 else if( pred( setA[posA], setB[posB] ) ) 1338 ++posA; 1339 else 1340 ++posA, ++posB; 1341 } 1342 return posB == setB.length; 1343 } 1344 1345 /// 1346 unittest 1347 { 1348 test(includes("abcdefg", "cde")); 1349 } 1350 1351 unittest 1352 { 1353 test( includes( "abcdefg"[], "a"[] ) ); 1354 test( includes( "abcdefg"[], "g"[] ) ); 1355 test( includes( "abcdefg"[], "d"[] ) ); 1356 test( includes( "abcdefg"[], "abcdefg"[] ) ); 1357 test( includes( "aaaabbbcdddefgg"[], "abbbcdefg"[] ) ); 1358 1359 test( !includes( "abcdefg"[], "aaabcdefg"[] ) ); 1360 test( !includes( "abcdefg"[], "abcdefggg"[] ) ); 1361 test( !includes( "abbbcdefg"[], "abbbbcdefg"[] ) ); 1362 } 1363 1364 /******************************************************************************* 1365 1366 Check if the given array starts with the given prefix 1367 1368 Template Params: 1369 T = The type of the array element 1370 1371 Params: 1372 arr = The array to be tested 1373 prefix = The prefix to test for 1374 1375 Returns: 1376 True if the array starts with the prefix, false otherwise 1377 1378 *******************************************************************************/ 1379 1380 bool startsWith ( T ) ( in T[] arr, in T[] prefix ) 1381 { 1382 // https://issues.dlang.org/show_bug.cgi?id=17917 1383 // WORKAROUND: with -O -cov compiler doesn't early return from 1384 // && expression, thus the code here is manually split into two 1385 // conditions 1386 if (arr.length < prefix.length) 1387 return false; 1388 return arr[0..prefix.length] == prefix[]; 1389 } 1390 1391 unittest 1392 { 1393 test( startsWith("abcd", "abc")); 1394 test( startsWith("abcd", "abcd")); 1395 test(!startsWith("ab", "abc")); 1396 test( startsWith("ab", "")); 1397 test(!startsWith("", "xx")); 1398 1399 test( startsWith([1,2,3,4], [1,2,3])); 1400 test( startsWith([1,2,3,4], [1,2,3,4])); 1401 test(!startsWith([1,2], [1,2,3])); 1402 test( startsWith([1,2], (int[]).init)); 1403 test(!startsWith((int[]).init, [1,2])); 1404 } 1405 1406 /******************************************************************************* 1407 1408 Check if the given array ends with the given suffix 1409 1410 Template Params: 1411 T = The type of the array element 1412 1413 Params: 1414 arr = The array to be tested 1415 suffix = The suffix to test for 1416 1417 Returns: 1418 True if the array ends with the suffix, false otherwise 1419 1420 *******************************************************************************/ 1421 1422 bool endsWith ( T ) ( in T[] arr, in T[] suffix ) 1423 { 1424 // https://issues.dlang.org/show_bug.cgi?id=17917 1425 // WORKAROUND: with -O -cov compiler doesn't early return from 1426 // && expression, thus the code here is manually split into two 1427 // conditions 1428 if (arr.length < suffix.length) 1429 return false; 1430 return arr[$ - suffix.length .. $] == suffix[]; 1431 } 1432 1433 unittest 1434 { 1435 test( endsWith("abcd", "bcd")); 1436 test( endsWith("abcd", "abcd")); 1437 test(!endsWith("ab", "abc")); 1438 test( endsWith("ab", "")); 1439 test(!endsWith("", "xx")); 1440 1441 test( endsWith([1,2,3,4], [2,3,4])); 1442 test( endsWith([1,2,3,4], [1,2,3,4])); 1443 test(!endsWith([1,2], [1,2,3])); 1444 test( endsWith([1,2], (int[]).init)); 1445 test(!endsWith((int[]).init, [1,2])); 1446 } 1447 1448 /******************************************************************************* 1449 1450 Remove the given prefix from the given array. 1451 1452 Template Params: 1453 T = The type of the array element 1454 1455 Params: 1456 arr = The array from which the prefix is to be removed 1457 prefix = The prefix to remove 1458 1459 Returns: 1460 A slice without the prefix if successful, the original array otherwise 1461 1462 *******************************************************************************/ 1463 1464 public T1[] removePrefix ( T1, T2 ) ( T1[] arr, in T2[] prefix ) 1465 { 1466 return ((arr.length >= prefix.length) && (startsWith(arr, prefix)) 1467 ? arr[prefix.length .. $] 1468 : arr); 1469 } 1470 1471 unittest 1472 { 1473 test(removePrefix("abcd", "abc") == "d"); 1474 test(removePrefix("abcd", "abcd") == ""); 1475 test(removePrefix("abcd", "abcde") == "abcd"); 1476 test(removePrefix("abcd", "") == "abcd"); 1477 test(removePrefix("", "xx") == ""); 1478 test("abcd".removePrefix("abc") == "d"); 1479 test("abcd".removePrefix("abcd") == ""); 1480 test("abcd".removePrefix("abcde") == "abcd"); 1481 test("abcd".removePrefix("") == "abcd"); 1482 test("".removePrefix("xx") == ""); 1483 1484 test(removePrefix([1,2,3,4], [1,2,3]) == [ 4 ]); 1485 test(removePrefix([1,2,3,4], [1,2,3,4]) == cast(int[]) null); 1486 test(removePrefix([1,2], [1,2,3]) == [ 1, 2 ]); 1487 test(removePrefix([1,2], (int[]).init) == [ 1, 2 ]); 1488 test(removePrefix((int[]).init, [1,2]) == cast(int[]) null); 1489 } 1490 1491 /******************************************************************************* 1492 1493 Remove the given suffix from the given array. 1494 1495 Template Params: 1496 T = The type of the array element 1497 1498 Params: 1499 arr = The array from which the suffix is to be removed 1500 suffix = The suffix to remove 1501 1502 Returns: 1503 A slice without the suffix if successful, the original array otherwise 1504 1505 *******************************************************************************/ 1506 1507 public T1[] removeSuffix ( T1, T2 ) ( T1[] arr, in T2[] suffix ) 1508 { 1509 return ((arr.length >= suffix.length) && (endsWith(arr, suffix)) 1510 ? arr[0 .. $-suffix.length] 1511 : arr); 1512 } 1513 1514 unittest 1515 { 1516 test(removeSuffix("abcd", "cd") == "ab"); 1517 test(removeSuffix("abcd", "abcd") == ""); 1518 test(removeSuffix("abcd", "abcde") == "abcd"); 1519 test(removeSuffix("abcd", "") == "abcd"); 1520 test(removeSuffix("", "xx") == ""); 1521 test("abcd".removeSuffix("cd") == "ab"); 1522 test("abcd".removeSuffix("abcd") == ""); 1523 test("abcd".removeSuffix("abcde") == "abcd"); 1524 test("abcd".removeSuffix("") == "abcd"); 1525 test("".removeSuffix("xx") == ""); 1526 1527 test(removeSuffix([1,2,3,4], [2,3,4]) == [ 1 ]); 1528 test(removeSuffix([1,2,3,4], [1,2,3,4]) == cast(int[]) null); 1529 test(removeSuffix([1,2], [1,2,3]) == [ 1, 2 ]); 1530 test(removeSuffix([1,2], (int[]).init) == [ 1, 2 ]); 1531 test(removeSuffix((int[]).init, [1,2]) == cast(int[]) null); 1532 }