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.traits; 25 26 import ae.utils.meta; 27 28 public import ae.utils.aa; 29 public import ae.utils.appender; 30 31 /// Slice a variable. 32 T[] toArray(T)(ref T v) 33 { 34 return (&v)[0..1]; 35 } 36 37 /// std.array.staticArray shim 38 static if (__traits(hasMember, std.array, "staticArray")) 39 public import std.array : staticArray; 40 else 41 pragma(inline, true) T[n] staticArray(T, size_t n)(auto ref T[n] a) { return a; } 42 43 /// Convert a dynamic array to a static array 44 template toStaticArray(size_t n) 45 { 46 ref T[n] toStaticArray(T)(return T[] a) 47 { 48 assert(a.length == n, "Size mismatch"); 49 return a[0 .. n]; 50 } 51 } 52 53 unittest 54 { 55 auto a = [1, 2, 3]; 56 assert(a.toStaticArray!3 == [1, 2, 3]); 57 58 import std.range : chunks; 59 auto b = [1, 2, 3, 4]; 60 assert(b.chunks(2).map!(toStaticArray!2).array == [[1, 2], [3, 4]]); 61 } 62 63 /// Return the value represented as an array of bytes. 64 @property inout(ubyte)[] bytes(T)(ref inout(T) value) 65 if (!hasIndirections!T) 66 { 67 return value.toArray().bytes; 68 } 69 70 /// ditto 71 @property inout(ubyte)[] bytes(T)(inout(T) value) 72 if (is(T U : U[]) && !hasIndirections!U) 73 { 74 return cast(inout(ubyte)[])value; 75 } 76 77 unittest 78 { 79 ubyte b = 5; 80 assert(b.bytes == [5]); 81 82 struct S { ubyte b = 5; } 83 S s; 84 assert(s.bytes == [5]); 85 86 ubyte[1] sa = [5]; 87 assert(sa.bytes == [5]); 88 89 void[] va = sa[]; 90 assert(va.bytes == [5]); 91 } 92 93 /// Reverse of bytes() 94 ref inout(T) fromBytes(T)(inout(ubyte)[] bytes) 95 if (!hasIndirections!T) 96 { 97 assert(bytes.length == T.sizeof, "Data length mismatch for %s".format(T.stringof)); 98 return *cast(inout(T)*)bytes.ptr; 99 } 100 101 /// ditto 102 inout(T) fromBytes(T)(inout(ubyte)[] bytes) 103 if (is(T U : U[]) && !hasIndirections!U) 104 { 105 return cast(inout(T))bytes; 106 } 107 108 unittest 109 { 110 { ubyte b = 5; assert(b.bytes.fromBytes!ubyte == 5); } 111 { const ubyte b = 5; assert(b.bytes.fromBytes!ubyte == 5); } 112 struct S { ubyte b; } 113 { ubyte b = 5; assert(b.bytes.fromBytes!S == S(5)); } 114 } 115 116 unittest 117 { 118 struct S { ubyte a, b; } 119 ubyte[] arr = [1, 2]; 120 assert(arr.fromBytes!S == S(1, 2)); 121 assert(arr.fromBytes!(S[]) == [S(1, 2)]); 122 } 123 124 /// Returns an empty, but non-null slice of T. 125 auto emptySlice(T)() pure 126 { 127 static if (false) // LDC optimizes this out 128 { 129 T[0] arr; 130 auto p = arr.ptr; 131 } 132 else 133 auto p = cast(T*)1; 134 return p[0..0]; 135 } 136 137 unittest 138 { 139 int[] arr = emptySlice!int; 140 assert(arr.ptr); 141 immutable int[] iarr = emptySlice!int; 142 assert(iarr.ptr); 143 } 144 145 /// C `memcmp` wrapper. 146 int memcmp(in ubyte[] a, in ubyte[] b) 147 { 148 assert(a.length == b.length); 149 import core.stdc.string : memcmp; 150 return memcmp(a.ptr, b.ptr, a.length); 151 } 152 153 /// Like std.algorithm.copy, but without the auto-decode bullshit. 154 /// https://issues.dlang.org/show_bug.cgi?id=13650 155 void memmove(T)(T[] dst, in T[] src) 156 { 157 assert(src.length == dst.length); 158 import core.stdc.string : memmove; 159 memmove(dst.ptr, src.ptr, dst.length * T.sizeof); 160 } 161 162 /// Performs binary operation `op` on every element of `a` and `b`. 163 T[] vector(string op, T)(T[] a, T[] b) 164 { 165 assert(a.length == b.length); 166 T[] result = new T[a.length]; 167 foreach (i, ref r; result) 168 r = mixin("a[i]" ~ op ~ "b[i]"); 169 return result; 170 } 171 172 /// Performs in-place binary operation `op` on every element of `a` and `b`. 173 T[] vectorAssign(string op, T)(T[] a, T[] b) 174 { 175 assert(a.length == b.length); 176 foreach (i, ref r; a) 177 mixin("r " ~ op ~ "= b[i];"); 178 return a; 179 } 180 181 /// Return `s` expanded to at least `l` elements, filling them with `c`. 182 T[] padRight(T)(T[] s, size_t l, T c) 183 { 184 auto ol = s.length; 185 if (ol < l) 186 { 187 s.length = l; 188 s[ol..$] = c; 189 } 190 return s; 191 } 192 193 /// Return a new `T[]` of length `l`, filled with `c`. 194 T[] repeatOne(T)(T c, size_t l) 195 { 196 T[] result = new T[l]; 197 result[] = c; 198 return result; 199 } 200 201 /// Complement to std.string.indexOf which works with arrays 202 /// of non-character types. 203 /// Unlike std.algorithm.countUntil, it does not auto-decode, 204 /// and returns an index usable for array indexing/slicing. 205 sizediff_t indexOf(T, D)(in T[] arr, in D val) 206 // if (!isSomeChar!T) 207 if (!isSomeChar!T && is(typeof(arr.countUntil(val))) && is(typeof(arr[0]==val))) 208 { 209 //assert(arr[0]==val); 210 return arr.countUntil(val); 211 } 212 213 sizediff_t indexOf(T)(in T[] arr, in T[] val) /// ditto 214 if (!isSomeChar!T && is(typeof(arr.countUntil(val)))) 215 { 216 return arr.countUntil(val); 217 } /// ditto 218 219 /// Reimplementation of `std.algorithm.indexOf`, 220 /// but with no auto-decoding. 221 sizediff_t indexOfElement(T, D)(in T[] arr, auto ref const D val) 222 if (is(typeof(arr[0]==val))) 223 { 224 foreach (i, ref v; arr) 225 if (v == val) 226 return i; 227 return -1; 228 } 229 230 /// Whether array contains value, no BS. 231 bool contains(T, V)(in T[] arr, auto ref const V val) 232 if (is(typeof(arr[0]==val))) 233 { 234 return arr.indexOfElement(val) >= 0; 235 } 236 237 /// Ditto, for substrings 238 bool contains(T, U)(T[] str, U[] what) 239 if (is(Unqual!T == Unqual!U)) 240 { 241 return str._indexOf(what) >= 0; 242 } 243 244 unittest 245 { 246 assert( "abc".contains('b')); 247 assert(!"abc".contains('x')); 248 assert( "abc".contains("b")); 249 assert(!"abc".contains("x")); 250 } 251 252 /// Like startsWith, but with an offset. 253 bool containsAt(T)(in T[] haystack, in T[] needle, size_t offset) 254 { 255 return haystack.length >= offset + needle.length 256 && haystack[offset..offset+needle.length] == needle; 257 } 258 259 unittest 260 { 261 assert( "abracadabra".containsAt("ada", 5)); 262 assert(!"abracadabra".containsAt("ada", 6)); 263 assert(!"abracadabra".containsAt("ada", 99)); 264 } 265 266 /// Returns `true` if one of the elements of `arr` contains `val`. 267 bool isIn(T)(T val, in T[] arr) 268 { 269 return arr.contains(val); 270 } 271 272 /// Returns `true` if one of the elements of `arr` contains `val`. 273 bool isOneOf(T)(T val, T[] arr...) 274 { 275 return arr.contains(val); 276 } 277 278 /// Like AA.get - soft indexing, throws an 279 /// Exception (not an Error) on out-of-bounds, 280 /// even in release builds. 281 ref T get(T)(T[] arr, size_t index) 282 { 283 enforce(index < arr.length, "Out-of-bounds array access"); 284 return arr[index]; 285 } 286 287 /// Like AA.get - soft indexing, returns 288 /// default value on out-of-bounds. 289 auto get(T)(T[] arr, size_t index, auto ref T defaultValue) 290 { 291 if (index >= arr.length) 292 return defaultValue; 293 return arr[index]; 294 } 295 296 /// Expand the array if index is out-of-bounds. 297 ref T getExpand(T)(ref T[] arr, size_t index) 298 { 299 if (index >= arr.length) 300 arr.length = index + 1; 301 return arr[index]; 302 } 303 304 /// ditto 305 ref T putExpand(T)(ref T[] arr, size_t index, auto ref T value) 306 { 307 if (index >= arr.length) 308 arr.length = index + 1; 309 return arr[index] = value; 310 } 311 312 /// Slices an array. Throws an Exception (not an Error) 313 /// on out-of-bounds, even in release builds. 314 T[] slice(T)(T[] arr, size_t p0, size_t p1) 315 { 316 enforce(p0 < p1 && p1 < arr.length, "Out-of-bounds array slice"); 317 return arr[p0..p1]; 318 } 319 320 /// Given an array and a reference to an element inside it, returns its index. 321 /// The reverse operation of indexing an array. 322 size_t elementIndex(T)(in T[] arr, in ref T element) 323 { 324 auto start = arr.ptr; 325 auto end = start + arr.length; 326 auto p = &element; 327 assert(start <= p && p < end, "Element is not in array"); 328 return p - start; 329 } 330 331 unittest 332 { 333 auto arr = [1, 2, 3]; 334 assert(arr.elementIndex(arr[1]) == 1); 335 } 336 337 /// Given an array and its slice, returns the 338 /// start index of the slice inside the array. 339 /// The reverse operation of slicing an array. 340 size_t sliceIndex(T)(in T[] arr, in T[] slice) 341 { 342 auto a = arr.ptr; 343 auto b = a + arr.length; 344 auto p = slice.ptr; 345 assert(a <= p && p <= b, "Out-of-bounds array slice"); 346 return p - a; 347 } 348 349 /// Like std.array.split, but returns null if val was empty. 350 auto splitEmpty(T, S)(T value, S separator) 351 { 352 return value.length ? split(value, separator) : null; 353 } 354 355 /// Like std.array.split, but always returns a non-empty array. 356 auto split1(T, S)(T value, S separator) 357 { 358 auto result = split(value, separator); 359 return result.length ? result : [value]; 360 } 361 362 /// Include delimiter in result chunks as suffix 363 H[] splitWithSuffix(H, S)(H haystack, S separator) 364 { 365 H[] result; 366 while (haystack.length) 367 { 368 auto pos = haystack._indexOf(separator); 369 if (pos < 0) 370 pos = haystack.length; 371 else 372 { 373 static if (is(typeof(haystack[0] == separator))) 374 pos += 1; 375 else 376 static if (is(typeof(haystack[0..1] == separator))) 377 pos += separator.length; 378 else 379 static assert(false, "Don't know how to split " ~ H.stringof ~ " by " ~ S.stringof); 380 } 381 result ~= haystack[0..pos]; 382 haystack = haystack[pos..$]; 383 } 384 return result; 385 } 386 387 unittest 388 { 389 assert("a\nb".splitWithSuffix('\n') == ["a\n", "b"]); 390 assert([1, 0, 2].splitWithSuffix(0) == [[1, 0], [2]]); 391 392 assert("a\r\nb".splitWithSuffix("\r\n") == ["a\r\n", "b"]); 393 assert([1, 0, 0, 2].splitWithSuffix([0, 0]) == [[1, 0, 0], [2]]); 394 } 395 396 /// Include delimiter in result chunks as prefix 397 H[] splitWithPrefix(H, S)(H haystack, S separator) 398 { 399 H[] result; 400 while (haystack.length) 401 { 402 auto pos = haystack[1..$]._indexOf(separator); 403 if (pos < 0) 404 pos = haystack.length; 405 else 406 pos++; 407 result ~= haystack[0..pos]; 408 haystack = haystack[pos..$]; 409 } 410 return result; 411 } 412 413 unittest 414 { 415 assert("a\nb".splitWithPrefix('\n') == ["a", "\nb"]); 416 assert([1, 0, 2].splitWithPrefix(0) == [[1], [0, 2]]); 417 418 assert("a\r\nb".splitWithPrefix("\r\n") == ["a", "\r\nb"]); 419 assert([1, 0, 0, 2].splitWithPrefix([0, 0]) == [[1], [0, 0, 2]]); 420 } 421 422 /// Include delimiters in result chunks as prefix/suffix 423 S[] splitWithPrefixAndSuffix(S)(S haystack, S prefix, S suffix) 424 { 425 S[] result; 426 auto separator = suffix ~ prefix; 427 while (haystack.length) 428 { 429 auto pos = haystack._indexOf(separator); 430 if (pos < 0) 431 pos = haystack.length; 432 else 433 pos += suffix.length; 434 result ~= haystack[0..pos]; 435 haystack = haystack[pos..$]; 436 } 437 return result; 438 } 439 440 /// 441 unittest 442 { 443 auto s = q"EOF 444 Section 1: 445 10 446 11 447 12 448 Section 2: 449 21 450 22 451 23 452 Section 3: 453 31 454 32 455 33 456 EOF"; 457 auto parts = s.splitWithPrefixAndSuffix("Section ", "\n"); 458 assert(parts.length == 3 && parts.join == s); 459 foreach (part; parts) 460 assert(part.startsWith("Section ") && part.endsWith("\n")); 461 } 462 463 /// Ensure that arr is non-null if empty. 464 T[] nonNull(T)(T[] arr) 465 { 466 if (arr !is null) 467 return arr; 468 return emptySlice!(typeof(arr[0])); 469 } 470 471 /// If arr is null, return null. Otherwise, return a non-null 472 /// transformation dg over arr. 473 template mapNull(alias dg) 474 { 475 auto mapNull(T)(T arr) 476 { 477 if (arr is null) 478 return null; 479 return dg(arr).nonNull; 480 } 481 } 482 483 unittest 484 { 485 assert(string.init.mapNull!(s => s ) is null); 486 assert(string.init.mapNull!(s => "" ) is null); 487 assert("" .mapNull!(s => s ) !is null); 488 assert("" .mapNull!(s => string.init) !is null); 489 } 490 491 /// Select and return a random element from the array. 492 auto ref sample(T)(T[] arr) 493 { 494 import std.random; 495 return arr[uniform(0, $)]; 496 } 497 498 unittest 499 { 500 assert([7, 7, 7].sample == 7); 501 auto s = ["foo", "bar"].sample(); // Issue 13807 502 const(int)[] a2 = [5]; sample(a2); 503 } 504 505 /// Select and return a random element from the array, 506 /// and remove it from the array. 507 T pluck(T)(ref T[] arr) 508 { 509 import std.random; 510 auto pos = uniform(0, arr.length); 511 auto result = arr[pos]; 512 arr = arr.remove(pos); 513 return result; 514 } 515 516 unittest 517 { 518 auto arr = [1, 2, 3]; 519 auto res = [arr.pluck, arr.pluck, arr.pluck]; 520 res.sort(); 521 assert(res == [1, 2, 3]); 522 } 523 524 import std.functional; 525 526 /// Sorts `arr` in-place using counting sort. 527 /// The difference between the lowest and highest element of `arr` shouldn't be too big. 528 T[] countSort(alias value = "a", T)(T[] arr) 529 { 530 alias unaryFun!value getValue; 531 alias typeof(getValue(arr[0])) V; 532 if (arr.length == 0) return arr; 533 V min = getValue(arr[0]), max = getValue(arr[0]); 534 foreach (el; arr[1..$]) 535 { 536 auto v = getValue(el); 537 if (min > v) 538 min = v; 539 if (max < v) 540 max = v; 541 } 542 auto n = max-min+1; 543 auto counts = new size_t[n]; 544 foreach (el; arr) 545 counts[getValue(el)-min]++; 546 auto indices = new size_t[n]; 547 foreach (i; 1..n) 548 indices[i] = indices[i-1] + counts[i-1]; 549 T[] result = new T[arr.length]; 550 foreach (el; arr) 551 result[indices[getValue(el)-min]++] = el; 552 return result; 553 } 554 555 // *************************************************************************** 556 557 /// Push `val` into `arr`, treating it like a stack. 558 void stackPush(T)(ref T[] arr, auto ref T val) 559 { 560 arr ~= val; 561 } 562 563 /// Push `val` into `arr`, treating it like a queue. 564 alias stackPush queuePush; 565 566 /// Peek at the front of `arr`, treating it like a stack. 567 ref T stackPeek(T)(T[] arr) { return arr[$-1]; } 568 569 /// Pop a value off the front of `arr`, treating it like a stack. 570 ref T stackPop(T)(ref T[] arr) 571 { 572 auto ret = &arr[$-1]; 573 arr = arr[0..$-1]; 574 return *ret; 575 } 576 577 /// Peek at the front of `arr`, treating it like a queue. 578 ref T queuePeek(T)(T[] arr) { return arr[0]; } 579 580 /// Peek at the back of `arr`, treating it like a queue. 581 ref T queuePeekLast(T)(T[] arr) { return arr[$-1]; } 582 583 /// Pop a value off the front of `arr`, treating it like a queue. 584 ref T queuePop(T)(ref T[] arr) 585 { 586 auto ret = &arr[0]; 587 arr = arr[1..$]; 588 if (!arr.length) arr = null; 589 return *ret; 590 } 591 592 /// Remove the first element of `arr` and return it. 593 ref T shift(T)(ref T[] arr) { auto oldArr = arr; arr = arr[1..$]; return oldArr[0]; } 594 595 /// Remove the `n` first elements of `arr` and return them. 596 T[] shift(T)(ref T[] arr, size_t n) { T[] result = arr[0..n]; arr = arr[n..$]; return result; } 597 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 598 599 /// Insert elements in the front of `arr`. 600 void unshift(T)(ref T[] arr, T value) { arr.insertInPlace(0, value); } 601 void unshift(T)(ref T[] arr, T[] value) { arr.insertInPlace(0, value); } /// ditto 602 603 unittest 604 { 605 int[] arr = [1, 2, 3]; 606 assert(arr.shift == 1); 607 assert(arr == [2, 3]); 608 assert(arr.shift(2) == [2, 3]); 609 assert(arr == []); 610 611 arr = [3]; 612 arr.unshift([1, 2]); 613 assert(arr == [1, 2, 3]); 614 arr.unshift(0); 615 assert(arr == [0, 1, 2, 3]); 616 617 assert(arr.shift!2 == [0, 1]); 618 assert(arr == [2, 3]); 619 } 620 621 /// If arr starts with prefix, slice it off and return true. 622 /// Otherwise leave arr unchaned and return false. 623 deprecated("Use std.algorithm.skipOver instead") 624 bool eat(T)(ref T[] arr, T[] prefix) 625 { 626 if (arr.startsWith(prefix)) 627 { 628 arr = arr[prefix.length..$]; 629 return true; 630 } 631 return false; 632 } 633 634 // Overload disambiguator 635 private sizediff_t _indexOf(H, N)(H haystack, N needle) 636 { 637 static import std.string; 638 639 static if (is(typeof(ae.utils.array.indexOf(haystack, needle)))) 640 alias indexOf = ae.utils.array.indexOf; 641 else 642 static if (is(typeof(std..string.indexOf(haystack, needle)))) 643 alias indexOf = std..string.indexOf; 644 else 645 static assert(false, "No suitable indexOf overload found"); 646 return indexOf(haystack, needle); 647 } 648 649 /// Returns the slice of source up to the first occurrence of delim, 650 /// and fast-forwards source to the point after delim. 651 /// If delim is not found, the behavior depends on orUntilEnd: 652 /// - If orUntilEnd is false (default), it returns null 653 /// and leaves source unchanged. 654 /// - If orUntilEnd is true, it returns source, 655 /// and then sets source to null. 656 T[] skipUntil(T, D)(ref T[] source, D delim, bool orUntilEnd = false) 657 { 658 enum bool isSlice = is(typeof(source[0..1]==delim)); 659 enum bool isElem = is(typeof(source[0] ==delim)); 660 static assert(isSlice || isElem, "Can't skip " ~ T.stringof ~ " until " ~ D.stringof); 661 static assert(isSlice != isElem, "Ambiguous types for skipUntil: " ~ T.stringof ~ " and " ~ D.stringof); 662 static if (isSlice) 663 auto delimLength = delim.length; 664 else 665 enum delimLength = 1; 666 667 auto i = _indexOf(source, delim); 668 if (i < 0) 669 { 670 if (orUntilEnd) 671 { 672 auto result = source; 673 source = null; 674 return result; 675 } 676 else 677 return null; 678 } 679 auto result = source[0..i]; 680 source = source[i+delimLength..$]; 681 return result; 682 } 683 684 /// Like `std.algorithm.skipOver`, but stops when 685 /// `pred(suffix)` or `pred(suffix[0])` returns false. 686 template skipWhile(alias pred) 687 { 688 T[] skipWhile(T)(ref T[] source, bool orUntilEnd = false) 689 { 690 enum bool isSlice = is(typeof(pred(source[0..1]))); 691 enum bool isElem = is(typeof(pred(source[0] ))); 692 static assert(isSlice || isElem, "Can't skip " ~ T.stringof ~ " until " ~ pred.stringof); 693 static assert(isSlice != isElem, "Ambiguous types for skipWhile: " ~ T.stringof ~ " and " ~ pred.stringof); 694 695 foreach (i; 0 .. source.length) 696 { 697 bool match; 698 static if (isSlice) 699 match = pred(source[i .. $]); 700 else 701 match = pred(source[i]); 702 if (!match) 703 { 704 auto result = source[0..i]; 705 source = source[i .. $]; 706 return result; 707 } 708 } 709 710 if (orUntilEnd) 711 { 712 auto result = source; 713 source = null; 714 return result; 715 } 716 else 717 return null; 718 } 719 } 720 721 unittest 722 { 723 import std.ascii : isDigit; 724 725 string s = "01234abcde"; 726 auto p = s.skipWhile!isDigit; 727 assert(p == "01234" && s == "abcde", p ~ "|" ~ s); 728 } 729 730 deprecated("Use skipUntil instead") 731 enum OnEof { returnNull, returnRemainder, throwException } 732 733 deprecated("Use skipUntil instead") 734 template eatUntil(OnEof onEof = OnEof.throwException) 735 { 736 T[] eatUntil(T, D)(ref T[] source, D delim) 737 { 738 static if (onEof == OnEof.returnNull) 739 return skipUntil(source, delim, false); 740 else 741 static if (onEof == OnEof.returnRemainder) 742 return skipUntil(source, delim, true); 743 else 744 return skipUntil(source, delim, false).enforce("Delimiter not found in source"); 745 } 746 } 747 748 deprecated unittest 749 { 750 string s; 751 752 s = "Mary had a little lamb"; 753 assert(s.eatUntil(" ") == "Mary"); 754 assert(s.eatUntil(" ") == "had"); 755 assert(s.eatUntil(' ') == "a"); 756 757 assertThrown!Exception(s.eatUntil("#")); 758 assert(s.eatUntil!(OnEof.returnNull)("#") is null); 759 assert(s.eatUntil!(OnEof.returnRemainder)("#") == "little lamb"); 760 761 ubyte[] bytes = [1, 2, 0, 3, 4, 0, 0]; 762 assert(bytes.eatUntil(0) == [1, 2]); 763 assert(bytes.eatUntil([ubyte(0), ubyte(0)]) == [3, 4]); 764 } 765 766 // *************************************************************************** 767 768 /// Equivalents of `array(xxx(...))`. 769 auto amap(alias pred, T)(T[] arr) { return array(map!pred(arr)); } 770 auto afilter(alias pred, T)(T[] arr) { return array(filter!pred(arr)); } /// ditto 771 auto auniq(T)(T[] arr) { return array(uniq(arr)); } /// ditto 772 auto asort(alias pred, T)(T[] arr) { sort!pred(arr); return arr; } /// ditto 773 774 unittest 775 { 776 assert([1, 2, 3].amap!`a*2`() == [2, 4, 6]); 777 assert([1, 2, 3].amap!(n => n*n)() == [1, 4, 9]); 778 } 779 780 /// Like `amap` but with a static array. 781 auto amap(alias pred, T, size_t n)(T[n] arr) 782 { 783 alias R = typeof(unaryFun!pred(arr[0])); 784 R[n] result; 785 foreach (i, ref r; result) 786 r = unaryFun!pred(arr[i]); 787 return result; 788 } 789 790 // *************************************************************************** 791 792 /// Array with normalized comparison and hashing. 793 /// Params: 794 /// T = array element type to wrap. 795 /// normalize = function which should return a range of normalized elements. 796 struct NormalizedArray(T, alias normalize) 797 { 798 T[] arr; /// Underlying array. 799 800 this(T[] arr) { this.arr = arr; } /// 801 802 int opCmp (in T[] other) const { return std.algorithm.cmp(normalize(arr), normalize(other )) ; } /// 803 int opCmp ( const typeof(this) other) const { return std.algorithm.cmp(normalize(arr), normalize(other.arr)) ; } /// 804 int opCmp (ref const typeof(this) other) const { return std.algorithm.cmp(normalize(arr), normalize(other.arr)) ; } /// 805 bool opEquals(in T[] other) const { return std.algorithm.cmp(normalize(arr), normalize(other ))==0; } /// 806 bool opEquals( const typeof(this) other) const { return std.algorithm.cmp(normalize(arr), normalize(other.arr))==0; } /// 807 bool opEquals(ref const typeof(this) other) const { return std.algorithm.cmp(normalize(arr), normalize(other.arr))==0; } /// 808 809 private hash_t toHashReal() const 810 { 811 import std.digest.crc; 812 CRC32 crc; 813 foreach (c; normalize(arr)) 814 crc.put(cast(ubyte[])((&c)[0..1])); 815 static union Result { ubyte[4] crcResult; hash_t hash; } 816 return Result(crc.finish()).hash; 817 } 818 819 hash_t toHash() const nothrow @trusted 820 { 821 return (cast(hash_t delegate() nothrow @safe)&toHashReal)(); 822 } /// 823 } 824 825 // *************************************************************************** 826 827 /// Equivalent of PHP's `list` language construct: 828 /// http://php.net/manual/en/function.list.php 829 /// Works with arrays and tuples. 830 /// Specify `null` as an argument to ignore that index 831 /// (equivalent of `list(x, , y)` in PHP). 832 auto list(Args...)(auto ref Args args) 833 { 834 struct List 835 { 836 auto dummy() { return args[0]; } // https://issues.dlang.org/show_bug.cgi?id=11886 837 void opAssign(T)(auto ref T t) 838 { 839 assert(t.length == args.length, 840 "Assigning %d elements to list with %d elements" 841 .format(t.length, args.length)); 842 foreach (i; RangeTuple!(Args.length)) 843 static if (!is(Args[i] == typeof(null))) 844 args[i] = t[i]; 845 } 846 } 847 return List(); 848 } 849 850 /// 851 unittest 852 { 853 string name, value; 854 list(name, null, value) = "NAME=VALUE".findSplit("="); 855 assert(name == "NAME" && value == "VALUE"); 856 } 857 858 version(LittleEndian) 859 unittest 860 { 861 uint onlyValue; 862 ubyte[] data = [ubyte(42), 0, 0, 0]; 863 list(onlyValue) = cast(uint[])data; 864 assert(onlyValue == 42); 865 }