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