1 /** 2 * Array utility functions 3 * 4 * License: 5 * This Source Code Form is subject to the terms of 6 * the Mozilla Public License, v. 2.0. If a copy of 7 * the MPL was not distributed with this file, You 8 * can obtain one at http://mozilla.org/MPL/2.0/. 9 * 10 * Authors: 11 * Vladimir Panteleev <ae@cy.md> 12 */ 13 14 module ae.utils.array; 15 16 import std.algorithm.iteration; 17 import std.algorithm.mutation; 18 import std.algorithm.searching; 19 import std.algorithm.sorting; 20 import std.array; 21 import std.exception; 22 import std.format; 23 import std.functional; 24 import std.range.primitives; 25 import std.traits; 26 27 import ae.utils.meta; 28 29 public import ae.utils.aa; 30 public import ae.utils.appender; 31 32 /// Slice a variable. 33 @property T[] asSlice(T)(ref T v) 34 { 35 return (&v)[0..1]; 36 } 37 38 // deprecated alias toArray = asSlice; 39 // https://issues.dlang.org/show_bug.cgi?id=23968 40 deprecated T[] toArray(T)(ref T v) { return v.asSlice; } 41 42 /// Wrap `v` into a static array of length 1. 43 @property T[1] asUnitStaticArray(T)(T v) 44 { 45 return (&v)[0..1]; 46 } 47 48 /// ditto 49 @property ref T[1] asUnitStaticArray(T)(ref T v) 50 { 51 return (&v)[0..1]; 52 } 53 54 debug(ae_unittest) unittest 55 { 56 int[1] arr = 1.asUnitStaticArray; 57 int i; 58 assert(i.asUnitStaticArray.ptr == &i); 59 } 60 61 /// std.array.staticArray polyfill 62 static if (__traits(hasMember, std.array, "staticArray")) 63 public import std.array : staticArray; 64 else 65 pragma(inline, true) T[n] staticArray(T, size_t n)(auto ref T[n] a) { return a; } 66 67 /// Convert a dynamic array to a static array 68 template toStaticArray(size_t n) 69 { 70 ref T[n] toStaticArray(T)(return T[] a) 71 { 72 assert(a.length == n, "Size mismatch"); 73 return a[0 .. n]; 74 } 75 } 76 77 debug(ae_unittest) unittest 78 { 79 auto a = [1, 2, 3]; 80 assert(a.toStaticArray!3 == [1, 2, 3]); 81 82 import std.range : chunks; 83 auto b = [1, 2, 3, 4]; 84 assert(b.chunks(2).map!(toStaticArray!2).array == [[1, 2], [3, 4]]); 85 } 86 87 /// Return the value represented as an array of bytes. 88 @property inout(ubyte)[] asBytes(T)(ref inout(T) value) 89 if (!hasIndirections!T) 90 { 91 return value.asSlice.asBytes; 92 } 93 94 /// ditto 95 @property inout(ubyte)[] asBytes(T)(inout(T) value) 96 if (is(T U : U[]) && !hasIndirections!U) 97 { 98 return cast(inout(ubyte)[])value; 99 } 100 101 // deprecated alias bytes = asBytes; 102 // https://issues.dlang.org/show_bug.cgi?id=23968 103 deprecated @property inout(ubyte)[] bytes(T)(ref inout(T) value) if (!hasIndirections!T) { return value.asBytes; } 104 deprecated @property inout(ubyte)[] bytes(T)(inout(T) value) if (is(T U : U[]) && !hasIndirections!U) { return value.asBytes; } 105 106 debug(ae_unittest) unittest 107 { 108 ubyte b = 5; 109 assert(b.asBytes == [5]); 110 111 struct S { ubyte b = 5; } 112 S s; 113 assert(s.asBytes == [5]); 114 115 ubyte[1] sa = [5]; 116 assert(sa.asBytes == [5]); 117 118 void[] va = sa[]; 119 assert(va.asBytes == [5]); 120 } 121 122 /// Reverse of .asBytes 123 ref inout(T) as(T)(inout(ubyte)[] bytes) 124 if (!hasIndirections!T) 125 { 126 assert(bytes.length == T.sizeof, "Data length mismatch for %s".format(T.stringof)); 127 return *cast(inout(T)*)bytes.ptr; 128 } 129 130 /// ditto 131 inout(T) as(T)(inout(ubyte)[] bytes) 132 if (is(T U == U[]) && !hasIndirections!U) 133 { 134 return cast(inout(T))bytes; 135 } 136 137 // deprecated alias fromBytes = as; 138 // https://issues.dlang.org/show_bug.cgi?id=23968 139 deprecated ref inout(T) fromBytes(T)(inout(ubyte)[] bytes) if (!hasIndirections!T) { return bytes.as!T; } 140 deprecated inout(T) fromBytes(T)(inout(ubyte)[] bytes) if (is(T U == U[]) && !hasIndirections!U) { return bytes.as!T; } 141 142 debug(ae_unittest) unittest 143 { 144 { ubyte b = 5; assert(b.asBytes.as!ubyte == 5); } 145 { const ubyte b = 5; assert(b.asBytes.as!ubyte == 5); } 146 struct S { ubyte b; } 147 { ubyte b = 5; assert(b.asBytes.as!S == S(5)); } 148 } 149 150 debug(ae_unittest) unittest 151 { 152 struct S { ubyte a, b; } 153 ubyte[] arr = [1, 2]; 154 assert(arr.as!S == S(1, 2)); 155 assert(arr.as!(S[]) == [S(1, 2)]); 156 assert(arr.as!(S[1]) == [S(1, 2)]); 157 } 158 159 /// Return the value represented as a static array of bytes. 160 @property ref inout(ubyte)[T.sizeof] asStaticBytes(T)(ref inout(T) value) 161 if (!hasIndirections!T) 162 { 163 return *cast(inout(ubyte)[T.sizeof]*)&value; 164 } 165 166 /// ditto 167 @property inout(ubyte)[T.sizeof] asStaticBytes(T)(inout(T) value) 168 if (!hasIndirections!T) 169 { 170 return *cast(inout(ubyte)[T.sizeof]*)&value; 171 } 172 173 debug(ae_unittest) unittest 174 { 175 ubyte[4] arr = 1.asStaticBytes; 176 177 int i; 178 void* a = i.asStaticBytes.ptr; 179 void* b = &i; 180 assert(a is b); 181 } 182 183 /// Returns an empty, but non-null slice of T. 184 auto emptySlice(T)() pure @trusted 185 { 186 static if (false) // https://github.com/ldc-developers/ldc/issues/831 187 { 188 T[0] arr; 189 auto p = arr.ptr; 190 } 191 else 192 auto p = cast(T*)1; 193 return p[0..0]; 194 } 195 196 debug(ae_unittest) unittest 197 { 198 int[] arr = emptySlice!int; 199 assert(arr.ptr); 200 immutable int[] iarr = emptySlice!int; 201 assert(iarr.ptr); 202 } 203 204 // ************************************************************************ 205 206 /// A more generic alternative to the "is" operator, 207 /// which doesn't have corner cases with static arrays / floats. 208 bool isIdentical(T)(auto ref T a, auto ref T b) 209 { 210 static if (hasIndirections!T) 211 return a is b; 212 else 213 return a.asStaticBytes == b.asStaticBytes; 214 } 215 216 debug(ae_unittest) unittest 217 { 218 float nan; 219 assert(isIdentical(nan, nan)); 220 float[4] nans; 221 assert(isIdentical(nans, nans)); 222 } 223 224 /// C `memcmp` wrapper. 225 int memcmp(in ubyte[] a, in ubyte[] b) 226 { 227 assert(a.length == b.length); 228 import core.stdc.string : memcmp; 229 return memcmp(a.ptr, b.ptr, a.length); 230 } 231 232 /// Like std.algorithm.copy, but without the auto-decode bullshit. 233 /// https://issues.dlang.org/show_bug.cgi?id=13650 234 void memmove(T)(T[] dst, in T[] src) 235 { 236 assert(src.length == dst.length); 237 import core.stdc.string : memmove; 238 memmove(dst.ptr, src.ptr, dst.length * T.sizeof); 239 } 240 241 /// Performs binary operation `op` on every element of `a` and `b`. 242 T[] vector(string op, T)(T[] a, T[] b) 243 { 244 assert(a.length == b.length); 245 T[] result = new T[a.length]; 246 foreach (i, ref r; result) 247 r = mixin("a[i]" ~ op ~ "b[i]"); 248 return result; 249 } 250 251 /// Performs in-place binary operation `op` on every element of `a` and `b`. 252 T[] vectorAssign(string op, T)(T[] a, T[] b) 253 { 254 assert(a.length == b.length); 255 foreach (i, ref r; a) 256 mixin("r " ~ op ~ "= b[i];"); 257 return a; 258 } 259 260 /// Return `s` expanded to at least `l` elements, filling them with `c`. 261 T[] padRight(T)(T[] s, size_t l, T c) 262 { 263 auto ol = s.length; 264 if (ol < l) 265 { 266 s.length = l; 267 s[ol..$] = c; 268 } 269 return s; 270 } 271 272 /// Return a new `T[]` of length `l`, filled with `c`. 273 T[] repeatOne(T)(T c, size_t l) 274 { 275 T[] result = new T[l]; 276 result[] = c; 277 return result; 278 } 279 280 static import std.string; 281 /// Pull in overload set 282 private alias indexOf = std..string.indexOf; 283 private alias lastIndexOf = std..string.lastIndexOf; 284 285 /// Complement to std.string.indexOf which works with arrays 286 /// of non-character types. 287 /// Unlike std.algorithm.countUntil, it does not auto-decode, 288 /// and returns an index usable for array indexing/slicing. 289 ptrdiff_t indexOf(T, D)(in T[] arr, in D val) 290 // if (!isSomeChar!T) 291 if (!isSomeChar!T && is(typeof(arr.countUntil(val))) && is(typeof(arr[0]==val))) 292 { 293 //assert(arr[0]==val); 294 return arr.countUntil(val); 295 } 296 297 ptrdiff_t indexOf(T)(in T[] arr, in T[] val) /// ditto 298 if (!isSomeChar!T && is(typeof(arr.countUntil(val)))) 299 { 300 return arr.countUntil(val); 301 } /// ditto 302 303 debug(ae_unittest) unittest 304 { 305 assert("abc".indexOf('b') == 1); 306 assert("abc".indexOf("b") == 1); 307 assert([1, 2, 3].indexOf( 2 ) == 1); 308 assert([1, 2, 3].indexOf([2]) == 1); 309 } 310 311 /// Same for `lastIndexOf`. 312 ptrdiff_t lastIndexOf(T, D)(in T[] arr, in D val) 313 // if (!isSomeChar!T) 314 if (!isSomeChar!T && is(typeof(arr[0]==val))) 315 { 316 foreach_reverse (i, ref v; arr) 317 if (v == val) 318 return i; 319 return -1; 320 } 321 322 ptrdiff_t lastIndexOf(T)(in T[] arr, in T[] val) /// ditto 323 if (!isSomeChar!T && is(typeof(arr[0..1]==val[0..1]))) 324 { 325 if (val.length > arr.length) 326 return -1; 327 foreach_reverse (i; 0 .. arr.length - val.length + 1) 328 if (arr[i .. i + val.length] == val) 329 return i; 330 return -1; 331 } /// ditto 332 333 debug(ae_unittest) unittest 334 { 335 assert("abc".lastIndexOf('b') == 1); 336 assert("abc".lastIndexOf("b") == 1); 337 assert([1, 2, 3].lastIndexOf( 2 ) == 1); 338 assert([1, 2, 3].lastIndexOf([2]) == 1); 339 340 assert([1, 2, 3, 2, 4].lastIndexOf( 2 ) == 3); 341 assert([1, 2, 3, 2, 4].lastIndexOf([2]) == 3); 342 assert([1, 2, 3, 2, 4].lastIndexOf([2, 3]) == 1); 343 assert([1, 2, 3, 2, 4].lastIndexOf([2, 5]) == -1); 344 assert([].lastIndexOf([2, 5]) == -1); 345 } 346 347 deprecated("Use `indexOf`") 348 ptrdiff_t indexOfElement(T, D)(in T[] arr, auto ref const D val) 349 if (is(typeof(arr[0]==val))) 350 { 351 foreach (i, ref v; arr) 352 if (v == val) 353 return i; 354 return -1; 355 } 356 357 /// Whether array contains value, no BS. 358 bool contains(T, V)(in T[] arr, auto ref const V val) 359 if (is(typeof(arr[0]==val))) 360 { 361 return arr.indexOf(val) >= 0; 362 } 363 364 /// Ditto, for substrings 365 bool contains(T, U)(T[] str, U[] what) 366 if (is(Unqual!T == Unqual!U)) 367 { 368 return str._indexOf(what) >= 0; 369 } 370 371 debug(ae_unittest) unittest 372 { 373 assert( "abc".contains('b')); 374 assert(!"abc".contains('x')); 375 assert( "abc".contains("b")); 376 assert(!"abc".contains("x")); 377 } 378 379 /// Like startsWith, but with an offset. 380 bool containsAt(T)(in T[] haystack, in T[] needle, size_t offset) 381 { 382 return haystack.length >= offset + needle.length 383 && haystack[offset..offset+needle.length] == needle; 384 } 385 386 debug(ae_unittest) unittest 387 { 388 assert( "abracadabra".containsAt("ada", 5)); 389 assert(!"abracadabra".containsAt("ada", 6)); 390 assert(!"abracadabra".containsAt("ada", 99)); 391 } 392 393 /// Returns `true` if one of the elements of `arr` contains `val`. 394 bool isIn(T)(T val, in T[] arr) 395 { 396 return arr.contains(val); 397 } 398 399 /// Returns `true` if one of the elements of `arr` contains `val`. 400 bool isOneOf(T)(T val, T[] arr...) 401 { 402 return arr.contains(val); 403 } 404 405 /// Like AA.get - soft indexing, throws an 406 /// Exception (not an Error) on out-of-bounds, 407 /// even in release builds. 408 ref T get(T)(T[] arr, size_t index) 409 { 410 enforce(index < arr.length, "Out-of-bounds array access"); 411 return arr[index]; 412 } 413 414 /// Like AA.get - soft indexing, returns 415 /// default value on out-of-bounds. 416 auto get(T)(T[] arr, size_t index, auto ref T defaultValue) 417 { 418 if (index >= arr.length) 419 return defaultValue; 420 return arr[index]; 421 } 422 423 /// Expand the array if index is out-of-bounds. 424 ref T getExpand(T)(ref T[] arr, size_t index) 425 { 426 if (index >= arr.length) 427 arr.length = index + 1; 428 return arr[index]; 429 } 430 431 /// ditto 432 ref T putExpand(T)(ref T[] arr, size_t index, auto ref T value) 433 { 434 if (index >= arr.length) 435 arr.length = index + 1; 436 return arr[index] = value; 437 } 438 439 /// Slices an array. Throws an Exception (not an Error) 440 /// on out-of-bounds, even in release builds. 441 T[] slice(T)(T[] arr, size_t p0, size_t p1) 442 { 443 enforce(p0 < p1 && p1 < arr.length, "Out-of-bounds array slice"); 444 return arr[p0..p1]; 445 } 446 447 /// Given an array and a reference to an element, 448 /// return whether the array slices over the element reference. 449 bool slicesOver(T)(const(T)[] arr, ref const T element) 450 { 451 auto start = arr.ptr; 452 auto end = start + arr.length; 453 auto p = &element; 454 return start <= p && p < end; 455 } 456 457 /// Given an array and a reference to an element inside it, returns its index. 458 /// The reverse operation of indexing an array. 459 size_t elementIndex(T)(const(T)[] arr, ref const T element) @trusted 460 { 461 auto start = arr.ptr; 462 auto end = start + arr.length; 463 auto p = &element; 464 assert(start <= p && p < end, "Element is not in array"); 465 return p - start; 466 } 467 468 debug(ae_unittest) @safe unittest 469 { 470 auto arr = [1, 2, 3]; 471 assert(arr.elementIndex(arr[1]) == 1); 472 } 473 474 /// Given an array and its slice, returns the 475 /// start index of the slice inside the array. 476 /// The reverse operation of slicing an array. 477 size_t sliceIndex(T)(in T[] arr, in T[] slice) @trusted 478 { 479 auto a = arr.ptr; 480 auto b = a + arr.length; 481 auto p = slice.ptr; 482 assert(a <= p && p <= b, "Out-of-bounds array slice"); 483 return p - a; 484 } 485 486 /// Like std.array.split, but returns null if val was empty. 487 auto splitEmpty(T, S)(T value, S separator) 488 { 489 return value.length ? split(value, separator) : null; 490 } 491 492 /// Like std.array.split, but always returns a non-empty array. 493 auto split1(T, S)(T value, S separator) 494 { 495 auto result = split(value, separator); 496 return result.length ? result : [value]; 497 } 498 499 /// Include delimiter in result chunks as suffix 500 H[] splitWithSuffix(H, S)(H haystack, S separator) 501 { 502 H[] result; 503 while (haystack.length) 504 { 505 auto pos = haystack._indexOf(separator); 506 if (pos < 0) 507 pos = haystack.length; 508 else 509 { 510 static if (is(typeof(haystack[0] == separator))) 511 pos += 1; 512 else 513 static if (is(typeof(haystack[0..1] == separator))) 514 pos += separator.length; 515 else 516 static assert(false, "Don't know how to split " ~ H.stringof ~ " by " ~ S.stringof); 517 } 518 result ~= haystack[0..pos]; 519 haystack = haystack[pos..$]; 520 } 521 return result; 522 } 523 524 debug(ae_unittest) unittest 525 { 526 assert("a\nb".splitWithSuffix('\n') == ["a\n", "b"]); 527 assert([1, 0, 2].splitWithSuffix(0) == [[1, 0], [2]]); 528 529 assert("a\r\nb".splitWithSuffix("\r\n") == ["a\r\n", "b"]); 530 assert([1, 0, 0, 2].splitWithSuffix([0, 0]) == [[1, 0, 0], [2]]); 531 } 532 533 /// Include delimiter in result chunks as prefix 534 H[] splitWithPrefix(H, S)(H haystack, S separator) 535 { 536 H[] result; 537 while (haystack.length) 538 { 539 auto pos = haystack[1..$]._indexOf(separator); 540 if (pos < 0) 541 pos = haystack.length; 542 else 543 pos++; 544 result ~= haystack[0..pos]; 545 haystack = haystack[pos..$]; 546 } 547 return result; 548 } 549 550 debug(ae_unittest) unittest 551 { 552 assert("a\nb".splitWithPrefix('\n') == ["a", "\nb"]); 553 assert([1, 0, 2].splitWithPrefix(0) == [[1], [0, 2]]); 554 555 assert("a\r\nb".splitWithPrefix("\r\n") == ["a", "\r\nb"]); 556 assert([1, 0, 0, 2].splitWithPrefix([0, 0]) == [[1], [0, 0, 2]]); 557 } 558 559 /// Include delimiters in result chunks as prefix/suffix 560 S[] splitWithPrefixAndSuffix(S)(S haystack, S prefix, S suffix) 561 { 562 S[] result; 563 auto separator = suffix ~ prefix; 564 while (haystack.length) 565 { 566 auto pos = haystack._indexOf(separator); 567 if (pos < 0) 568 pos = haystack.length; 569 else 570 pos += suffix.length; 571 result ~= haystack[0..pos]; 572 haystack = haystack[pos..$]; 573 } 574 return result; 575 } 576 577 /// 578 debug(ae_unittest) unittest 579 { 580 auto s = q"EOF 581 Section 1: 582 10 583 11 584 12 585 Section 2: 586 21 587 22 588 23 589 Section 3: 590 31 591 32 592 33 593 EOF"; 594 auto parts = s.splitWithPrefixAndSuffix("Section ", "\n"); 595 assert(parts.length == 3 && parts.join == s); 596 foreach (part; parts) 597 assert(part.startsWith("Section ") && part.endsWith("\n")); 598 } 599 600 /// Ensure that arr is non-null if empty. 601 T[] nonNull(T)(T[] arr) 602 { 603 if (arr !is null) 604 return arr; 605 return emptySlice!(typeof(arr[0])); 606 } 607 608 /// If arr is null, return null. Otherwise, return a non-null 609 /// transformation dg over arr. 610 template mapNull(alias dg) 611 { 612 auto mapNull(T)(T arr) 613 { 614 if (arr is null) 615 return null; 616 return dg(arr).nonNull; 617 } 618 } 619 620 debug(ae_unittest) unittest 621 { 622 assert(string.init.mapNull!(s => s ) is null); 623 assert(string.init.mapNull!(s => "" ) is null); 624 assert("" .mapNull!(s => s ) !is null); 625 assert("" .mapNull!(s => string.init) !is null); 626 } 627 628 /// Select and return a random element from the array. 629 auto ref sample(T)(T[] arr) 630 { 631 import std.random; 632 return arr[uniform(0, $)]; 633 } 634 635 debug(ae_unittest) unittest 636 { 637 assert([7, 7, 7].sample == 7); 638 auto s = ["foo", "bar"].sample(); // Issue 13807 639 const(int)[] a2 = [5]; sample(a2); 640 } 641 642 /// Select and return a random element from the array, 643 /// and remove it from the array. 644 T pluck(T)(ref T[] arr) 645 { 646 import std.random; 647 auto pos = uniform(0, arr.length); 648 auto result = arr[pos]; 649 arr = arr.remove(pos); 650 return result; 651 } 652 653 debug(ae_unittest) unittest 654 { 655 auto arr = [1, 2, 3]; 656 auto res = [arr.pluck, arr.pluck, arr.pluck]; 657 res.sort(); 658 assert(res == [1, 2, 3]); 659 } 660 661 import std.functional; 662 663 /// Remove first matching element in an array, mutating the array. 664 /// Returns true if the array has been modified. 665 /// Cf. `K[V].remove(K)` 666 bool removeFirst(T, alias eq = binaryFun!"a == b", SwapStrategy swapStrategy = SwapStrategy.stable)(ref T[] arr, T elem) 667 { 668 foreach (i, ref e; arr) 669 if (eq(e, elem)) 670 { 671 arr = arr.remove!swapStrategy(i); 672 return true; 673 } 674 return false; 675 } 676 677 debug(ae_unittest) unittest 678 { 679 int[] arr = [1, 2, 3, 2]; 680 arr.removeFirst(2); 681 assert(arr == [1, 3, 2]); 682 } 683 684 /// Remove all matching elements in an array, mutating the array. 685 /// Returns the number of removed elements. 686 size_t removeAll(T, alias eq = binaryFun!"a == b", SwapStrategy swapStrategy = SwapStrategy.stable)(ref T[] arr, T elem) 687 { 688 auto oldLength = arr.length; 689 arr = arr.remove!((ref e) => eq(e, elem)); 690 return oldLength - arr.length; 691 } 692 693 debug(ae_unittest) unittest 694 { 695 int[] arr = [1, 2, 3, 2]; 696 arr.removeAll(2); 697 assert(arr == [1, 3]); 698 } 699 700 /// Sorts `arr` in-place using counting sort. 701 /// The difference between the lowest and highest 702 /// return value of `orderPred(arr[i])` shouldn't be too big. 703 void countSort(alias orderPred = "a", T)(T[] arr, ref T[] valuesBuf, ref size_t[] countsBuf) 704 { 705 alias getIndex = unaryFun!orderPred; 706 alias Index = typeof(getIndex(arr[0])); 707 if (arr.length == 0) 708 return; 709 Index min = getIndex(arr[0]); 710 Index max = min; 711 foreach (ref el; arr[1..$]) 712 { 713 Index i = getIndex(el); 714 if (min > i) 715 min = i; 716 if (max < i) 717 max = i; 718 } 719 720 auto n = max - min + 1; 721 if (valuesBuf.length < n) 722 valuesBuf.length = n; 723 auto values = valuesBuf[0 .. n]; 724 if (countsBuf.length < n) 725 countsBuf.length = n; 726 auto counts = countsBuf[0 .. n]; 727 728 foreach (el; arr) 729 { 730 auto i = getIndex(el) - min; 731 values[i] = el; 732 counts[i]++; 733 } 734 735 size_t p = 0; 736 foreach (i; 0 .. n) 737 { 738 auto count = counts[i]; 739 arr[p .. p + count] = values[i]; 740 p += count; 741 } 742 assert(p == arr.length); 743 } 744 745 void countSort(alias orderPred = "a", T)(T[] arr) 746 { 747 static size_t[] countsBuf; 748 static T[] valuesBuf; 749 countSort!orderPred(arr, valuesBuf, countsBuf); 750 } /// ditto 751 752 debug(ae_unittest) unittest 753 { 754 auto arr = [3, 2, 5, 2, 1]; 755 arr.countSort(); 756 assert(arr == [1, 2, 2, 3, 5]); 757 } 758 759 // *************************************************************************** 760 761 /// Push `val` into `arr`, treating it like a stack. 762 void stackPush(T)(ref T[] arr, auto ref T val) 763 { 764 arr ~= val; 765 } 766 767 /// Push `val` into `arr`, treating it like a queue. 768 alias stackPush queuePush; 769 770 /// Peek at the front of `arr`, treating it like a stack. 771 ref T stackPeek(T)(T[] arr) { return arr[$-1]; } 772 773 /// Pop a value off the front of `arr`, treating it like a stack. 774 ref T stackPop(T)(ref T[] arr) 775 { 776 auto ret = &arr[$-1]; 777 arr = arr[0..$-1]; 778 return *ret; 779 } 780 781 /// Peek at the front of `arr`, treating it like a queue. 782 ref T queuePeek(T)(T[] arr) { return arr[0]; } 783 784 /// Peek at the back of `arr`, treating it like a queue. 785 ref T queuePeekLast(T)(T[] arr) { return arr[$-1]; } 786 787 /// Pop a value off the front of `arr`, treating it like a queue. 788 ref T queuePop(T)(ref T[] arr) 789 { 790 auto ret = &arr[0]; 791 arr = arr[1..$]; 792 if (!arr.length) arr = null; 793 return *ret; 794 } 795 796 /// Remove the first element of `arr` and return it. 797 ref T shift(T)(ref T[] arr) { auto oldArr = arr; arr = arr[1..$]; return oldArr[0]; } 798 799 /// Remove the `n` first elements of `arr` and return them. 800 T[] shift(T)(ref T[] arr, size_t n) { T[] result = arr[0..n]; arr = arr[n..$]; return result; } 801 T[N] shift(size_t N, T)(ref T[] arr) { T[N] result = cast(T[N])(arr[0..N]); arr = arr[N..$]; return result; } /// ditto 802 803 /// Insert elements in the front of `arr`. 804 void unshift(T)(ref T[] arr, T value) { arr.insertInPlace(0, value); } 805 void unshift(T)(ref T[] arr, T[] value) { arr.insertInPlace(0, value); } /// ditto 806 807 debug(ae_unittest) unittest 808 { 809 int[] arr = [1, 2, 3]; 810 assert(arr.shift == 1); 811 assert(arr == [2, 3]); 812 assert(arr.shift(2) == [2, 3]); 813 assert(arr == []); 814 815 arr = [3]; 816 arr.unshift([1, 2]); 817 assert(arr == [1, 2, 3]); 818 arr.unshift(0); 819 assert(arr == [0, 1, 2, 3]); 820 821 assert(arr.shift!2 == [0, 1]); 822 assert(arr == [2, 3]); 823 } 824 825 /// If arr starts with prefix, slice it off and return true. 826 /// Otherwise leave arr unchaned and return false. 827 deprecated("Use std.algorithm.skipOver instead") 828 bool eat(T)(ref T[] arr, T[] prefix) 829 { 830 if (arr.startsWith(prefix)) 831 { 832 arr = arr[prefix.length..$]; 833 return true; 834 } 835 return false; 836 } 837 838 // Overload disambiguator 839 private sizediff_t _indexOf(H, N)(H haystack, N needle) 840 { 841 static import std.string; 842 843 static if (is(typeof(ae.utils.array.indexOf(haystack, needle)))) 844 alias indexOf = ae.utils.array.indexOf; 845 else 846 static if (is(typeof(std..string.indexOf(haystack, needle)))) 847 alias indexOf = std..string.indexOf; 848 else 849 static assert(false, "No suitable indexOf overload found"); 850 return indexOf(haystack, needle); 851 } 852 853 /// Returns the slice of source up to the first occurrence of delim, 854 /// and fast-forwards source to the point after delim. 855 /// If delim is not found, the behavior depends on orUntilEnd: 856 /// - If orUntilEnd is false (default), it returns null 857 /// and leaves source unchanged. 858 /// - If orUntilEnd is true, it returns source, 859 /// and then sets source to null. 860 T[] skipUntil(T, D)(ref T[] source, D delim, bool orUntilEnd = false) 861 { 862 enum bool isSlice = is(typeof(source[0..1]==delim)); 863 enum bool isElem = is(typeof(source[0] ==delim)); 864 static assert(isSlice || isElem, "Can't skip " ~ T.stringof ~ " until " ~ D.stringof); 865 static assert(isSlice != isElem, "Ambiguous types for skipUntil: " ~ T.stringof ~ " and " ~ D.stringof); 866 static if (isSlice) 867 auto delimLength = delim.length; 868 else 869 enum delimLength = 1; 870 871 auto i = _indexOf(source, delim); 872 if (i < 0) 873 { 874 if (orUntilEnd) 875 { 876 auto result = source; 877 source = null; 878 return result; 879 } 880 else 881 return null; 882 } 883 auto result = source[0..i]; 884 source = source[i+delimLength..$]; 885 return result; 886 } 887 888 /// Like `std.algorithm.skipOver`, but stops when 889 /// `pred(suffix)` or `pred(suffix[0])` returns false. 890 template skipWhile(alias pred) 891 { 892 T[] skipWhile(T)(ref T[] source, bool orUntilEnd = false) 893 { 894 enum bool isSlice = is(typeof(pred(source[0..1]))); 895 enum bool isElem = is(typeof(pred(source[0] ))); 896 static assert(isSlice || isElem, "Can't skip " ~ T.stringof ~ " until " ~ pred.stringof); 897 static assert(isSlice != isElem, "Ambiguous types for skipWhile: " ~ T.stringof ~ " and " ~ pred.stringof); 898 899 foreach (i; 0 .. source.length) 900 { 901 bool match; 902 static if (isSlice) 903 match = pred(source[i .. $]); 904 else 905 match = pred(source[i]); 906 if (!match) 907 { 908 auto result = source[0..i]; 909 source = source[i .. $]; 910 return result; 911 } 912 } 913 914 if (orUntilEnd) 915 { 916 auto result = source; 917 source = null; 918 return result; 919 } 920 else 921 return null; 922 } 923 } 924 925 debug(ae_unittest) unittest 926 { 927 import std.ascii : isDigit; 928 929 string s = "01234abcde"; 930 auto p = s.skipWhile!isDigit; 931 assert(p == "01234" && s == "abcde", p ~ "|" ~ s); 932 } 933 934 deprecated("Use skipUntil instead") 935 enum OnEof { returnNull, returnRemainder, throwException } 936 937 deprecated("Use skipUntil instead") 938 template eatUntil(OnEof onEof = OnEof.throwException) 939 { 940 T[] eatUntil(T, D)(ref T[] source, D delim) 941 { 942 static if (onEof == OnEof.returnNull) 943 return skipUntil(source, delim, false); 944 else 945 static if (onEof == OnEof.returnRemainder) 946 return skipUntil(source, delim, true); 947 else 948 return skipUntil(source, delim, false).enforce("Delimiter not found in source"); 949 } 950 } 951 952 debug(ae_unittest) deprecated unittest 953 { 954 string s; 955 956 s = "Mary had a little lamb"; 957 assert(s.eatUntil(" ") == "Mary"); 958 assert(s.eatUntil(" ") == "had"); 959 assert(s.eatUntil(' ') == "a"); 960 961 assertThrown!Exception(s.eatUntil("#")); 962 assert(s.eatUntil!(OnEof.returnNull)("#") is null); 963 assert(s.eatUntil!(OnEof.returnRemainder)("#") == "little lamb"); 964 965 ubyte[] bytes = [1, 2, 0, 3, 4, 0, 0]; 966 assert(bytes.eatUntil(0) == [1, 2]); 967 assert(bytes.eatUntil([ubyte(0), ubyte(0)]) == [3, 4]); 968 } 969 970 // *************************************************************************** 971 972 /// Equivalents of `array(xxx(...))`. 973 template amap(alias pred) 974 { 975 auto amap(R)(R range) 976 if (isInputRange!R && hasLength!R) 977 { 978 import core.memory : GC; 979 import core.lifetime : emplace; 980 alias T = typeof(unaryFun!pred(range.front)); 981 auto result = uninitializedArray!(Unqual!T[])(range.length); 982 foreach (i; 0 .. range.length) 983 { 984 auto value = cast()unaryFun!pred(range.front); 985 moveEmplace(value, result[i]); 986 range.popFront(); 987 } 988 assert(range.empty); 989 return cast(T[])result; 990 } 991 992 /// Like `amap` but with a static array. 993 auto amap(T, size_t n)(T[n] arr) 994 { 995 alias R = typeof(unaryFun!pred(arr[0])); 996 R[n] result; 997 foreach (i, ref r; result) 998 r = unaryFun!pred(arr[i]); 999 return result; 1000 } 1001 } 1002 template afilter(alias pred) { auto afilter(T)(T[] arr) { return array(filter!pred(arr)); } } /// ditto 1003 auto auniq(T)(T[] arr) { return array(uniq(arr)); } /// ditto 1004 auto asort(alias pred, T)(T[] arr) { sort!pred(arr); return arr; } /// ditto 1005 1006 debug(ae_unittest) unittest 1007 { 1008 assert([1, 2, 3].amap!`a*2`() == [2, 4, 6]); 1009 assert([1, 2, 3].amap!(n => n*n)() == [1, 4, 9]); 1010 assert([1, 2, 3].staticArray.amap!(n => n*n)() == [1, 4, 9].staticArray); 1011 } 1012 1013 debug(ae_unittest) unittest 1014 { 1015 struct NC 1016 { 1017 int i; 1018 @disable this(); 1019 this(int i) { this.i = i; } 1020 @disable this(this); 1021 } 1022 1023 import std.range : iota; 1024 assert(3.iota.amap!(i => NC(i))[1].i == 1); 1025 } 1026 1027 debug(ae_unittest) unittest 1028 { 1029 import std.range : iota; 1030 immutable(int)[] arr; 1031 arr = 3.iota.amap!(i => cast(immutable)i); 1032 assert(arr[1] == 1); 1033 } 1034 1035 // *************************************************************************** 1036 1037 /// Array with normalized comparison and hashing. 1038 /// Params: 1039 /// T = array element type to wrap. 1040 /// normalize = function which should return a range of normalized elements. 1041 struct NormalizedArray(T, alias normalize) 1042 { 1043 T[] arr; /// Underlying array. 1044 1045 this(T[] arr) { this.arr = arr; } /// 1046 1047 int opCmp (in T[] other) const { return std.algorithm.cmp(normalize(arr), normalize(other )) ; } /// 1048 int opCmp ( const typeof(this) other) const { return std.algorithm.cmp(normalize(arr), normalize(other.arr)) ; } /// 1049 int opCmp (ref const typeof(this) other) const { return std.algorithm.cmp(normalize(arr), normalize(other.arr)) ; } /// 1050 bool opEquals(in T[] other) const { return std.algorithm.cmp(normalize(arr), normalize(other ))==0; } /// 1051 bool opEquals( const typeof(this) other) const { return std.algorithm.cmp(normalize(arr), normalize(other.arr))==0; } /// 1052 bool opEquals(ref const typeof(this) other) const { return std.algorithm.cmp(normalize(arr), normalize(other.arr))==0; } /// 1053 1054 private hash_t toHashReal() const 1055 { 1056 import std.digest.crc; 1057 CRC32 crc; 1058 foreach (c; normalize(arr)) 1059 crc.put(cast(ubyte[])((&c)[0..1])); 1060 static union Result { ubyte[4] crcResult; hash_t hash; } 1061 return Result(crc.finish()).hash; 1062 } 1063 1064 hash_t toHash() const nothrow @trusted 1065 { 1066 return (cast(hash_t delegate() nothrow @safe)&toHashReal)(); 1067 } /// 1068 } 1069 1070 // *************************************************************************** 1071 1072 /// Equivalent of PHP's `list` language construct: 1073 /// http://php.net/manual/en/function.list.php 1074 /// Works with arrays and tuples. 1075 /// Specify `null` as an argument to ignore that index 1076 /// (equivalent of `list(x, , y)` in PHP). 1077 auto list(Args...)(auto ref Args args) 1078 { 1079 struct List 1080 { 1081 auto dummy() { return args[0]; } // https://issues.dlang.org/show_bug.cgi?id=11886 1082 void opAssign(T)(auto ref T t) 1083 { 1084 assert(t.length == args.length, 1085 "Assigning %d elements to list with %d elements" 1086 .format(t.length, args.length)); 1087 foreach (i; rangeTuple!(Args.length)) 1088 static if (!is(Args[i] == typeof(null))) 1089 args[i] = t[i]; 1090 } 1091 } 1092 return List(); 1093 } 1094 1095 /// 1096 debug(ae_unittest) unittest 1097 { 1098 string name, value; 1099 list(name, null, value) = "NAME=VALUE".findSplit("="); 1100 assert(name == "NAME" && value == "VALUE"); 1101 } 1102 1103 version(LittleEndian) 1104 debug(ae_unittest) unittest 1105 { 1106 uint onlyValue; 1107 ubyte[] data = [ubyte(42), 0, 0, 0]; 1108 list(onlyValue) = cast(uint[])data; 1109 assert(onlyValue == 42); 1110 }