1 /** 2 * This file is part of the Amalthea library. 3 * Copyright (C) 2018-2024 Eugene 'Vindex' Stulin 4 * Distributed under the BSL 1.0 or (at your option) the GNU LGPL 3.0 or later. 5 * 6 * The module contains some useful functions for data processing, 7 * including functions for working with dynamic arrays and associative arrays, 8 * functions for working with command line arguments, 9 * operations on arrays as sets, 10 * some functions for working with strings, etc. 11 */ 12 13 module amalthea.dataprocessing; 14 15 public import amalthea.libcore; 16 17 import std.algorithm, std.array, std.json; 18 import std.format : formattedWrite; 19 import std.range.primitives : isInputRange; 20 static import std.traits; 21 22 23 /******************************************************************************* 24 * Implementation of the intersection operation for arrays. 25 */ 26 T calcIntersection(T)(const T[] arrays ...) { 27 import std.algorithm.setops : setIntersection; 28 if (arrays.length == 1) return copyArray(arrays[0]); 29 T result = setIntersection( 30 sort(copyArray(arrays[0])), sort(copyArray(arrays[1])) 31 ).array; 32 for (auto i = 2; i < arrays.length; ++i) { 33 result = setIntersection(result, sort(copyArray(arrays[i]))).array; 34 } 35 return result; 36 } 37 /// 38 unittest { 39 int[] arr = calcIntersection([3, 4, 1, -15], [9, 3, 1], [1, 99, 3]); 40 assert(arr == [1, 3]); 41 } 42 43 44 /******************************************************************************* 45 * Implementation of the union operation for arrays. 46 */ 47 T calcUnion(T)(const T[] arrays ...) { 48 if (arrays.length == 1) { 49 return copyArray(arrays[0]); 50 } 51 T result = copyArray(arrays[0]); 52 for (size_t i = 1; i < arrays.length; ++i) { 53 foreach(el; copyArray(arrays[i])) { 54 if (!canFind(result, el)) { 55 result ~= el; 56 } 57 } 58 } 59 return std.algorithm.sort(result).array; 60 } 61 /// 62 unittest { 63 int[] arr = calcUnion([3, 4, 1, -15], [9, 3, 1], [1, 99, 3]); 64 assert(arr == [-15, 1, 3, 4, 9, 99]); 65 } 66 67 68 /******************************************************************************* 69 * Implementation of the complement operation for arrays. 70 */ 71 T calcComplement(T)(const T[] arrays ...) { 72 if (arrays.length == 1) { 73 return arrays[0].copyArray; 74 } 75 T result = setDifference( 76 arrays[0].copyArray.sort, 77 arrays[1].copyArray.sort 78 ).array; 79 for (auto i = 2; i < arrays.length; ++i) { 80 result = array(setDifference(result, arrays[i].copyArray.sort)); 81 } 82 return result; 83 } 84 /// 85 unittest { 86 int[] arr = calcComplement([3, 4, 1, -15], [9, 3, 1]); 87 assert(arr == [-15, 4]); 88 } 89 90 91 /******************************************************************************* 92 * The function removes consecutive string duplicates. 93 */ 94 string removeDuplicateConsecutiveSubstring(string s, string substring) { 95 while(s.canFind(substring~substring)) { 96 s = s.replace(substring~substring, substring); 97 } 98 return s; 99 } 100 /// 101 unittest { 102 string s = removeDuplicateConsecutiveSubstring("AAAAbooeAAAuuvAww", "A"); 103 assert(s == "AbooeAuuvAww"); 104 s = removeDuplicateConsecutiveSubstring("AAAAbcooeAAAgguvAww", "AA"); 105 assert(s == "AAbcooeAAAgguvAww"); 106 } 107 108 109 /******************************************************************************* 110 * The function removes duplicate elements from an array. 111 */ 112 ref T[] removeDuplicate(T)(ref T[] arr) { 113 if (arr.empty) return arr; 114 for (auto i = arr.length-1; i > 0; --i) { 115 if (arr[0 .. i].canFind(arr[i])) { 116 arr = arr.remove(i); 117 } 118 } 119 return arr; 120 } 121 /// 122 unittest { 123 int[] values = [1, 3, 1, 7, 2, 19, 5, 2, 2]; 124 removeDuplicate(values); 125 assert(values == [1, 3, 7, 2, 19, 5]); 126 } 127 128 129 /******************************************************************************* 130 * Returns array of tuples with pairs of keys and values from associative array. 131 */ 132 auto assocArrayItems(V, K)(V[K] aarray) { 133 Tuple!(K, V)[] pairs; 134 foreach(item; aarray.byPair) { 135 pairs ~= item; 136 } 137 return pairs; 138 } 139 140 141 /******************************************************************************* 142 * Makes key-sorted array of tuples with pairs of keys and values 143 * from associative array. 144 * Params: 145 * aarray = Associative array. 146 * desc = Flag for sorting (descending/ascending). By default, No.desc. 147 * Returns: Key-sorted array of tuples with elements of the associative array. 148 */ 149 auto sortAssocArrayItemsByKeys(V, K)(V[K] aarray, Flag!"desc" desc = No.desc) { 150 if (!desc) { 151 return aarray.assocArrayItems.sort!((a, b) => a[0] < b[0]).array; 152 } 153 return aarray.assocArrayItems.sort!((a, b) => a[0] > b[0]).array; 154 } 155 unittest { 156 int[string] aarray = ["x": 9, "y": 7]; 157 auto expected = [tuple("x", 9), tuple("y", 7)]; 158 assert(expected == sortAssocArrayItemsByKeys(aarray)); 159 expected = [tuple("y", 7), tuple("x", 9)]; 160 assert(expected == sortAssocArrayItemsByKeys(aarray, Yes.desc)); 161 } 162 163 164 /******************************************************************************* 165 * Makes value-sorted array of tuples with pairs of keys and values 166 * from associative array. 167 * Params: 168 * aarray = Associative array. 169 * desc = Flag for sorting (descending/ascending). By default, No.desc. 170 * Returns: Value-sorted array of tuples with elements of the associative array. 171 */ 172 auto sortAssocArrayItemsByValues(V, K)( 173 V[K] aarray,Flag!"desc" desc = No.desc 174 ) { 175 if (!desc) { 176 return aarray.assocArrayItems.sort!((a, b) => a[1] < b[1]).array; 177 } 178 return aarray.assocArrayItems.sort!((a, b) => a[1] > b[1]).array; 179 } 180 unittest { 181 int[string] aarray = ["x": 9, "y": 7]; 182 auto expected = [tuple("y", 7), tuple("x", 9)]; 183 assert(expected == sortAssocArrayItemsByValues(aarray)); 184 expected = [tuple("x", 9), tuple("y", 7)]; 185 assert(expected == sortAssocArrayItemsByValues(aarray, Yes.desc)); 186 } 187 188 189 /******************************************************************************* 190 * The function arranges the associative array 191 * by the required sorting method (string "by" is equal to "values" or "keys"), 192 * returns an array of keys and an array of values. 193 */ 194 deprecated("Use sortAssocArrayItemsByKeys and sortAssocArrayItemsByValues") 195 void orderAssociativeArray(alias by, V, K)( 196 in V[K] aarray, out K[] orderedKeys, out V[] orderedValues 197 ) 198 if (by == "values" || by == "keys") { 199 if (aarray.length == 0) { 200 return; 201 } 202 if (by == "keys") { 203 orderedKeys = aarray.keys.dup; 204 orderedKeys.sort; 205 orderedValues.length = orderedKeys.length; 206 foreach(i, k; orderedKeys) { 207 orderedValues[i] = aarray[k]; 208 } 209 } else { 210 orderedValues = aarray.values.dup; 211 orderedValues.sort; 212 orderedKeys.length = orderedValues.length; 213 size_t[] passedIndexes; 214 foreach(k; aarray.keys) { 215 foreach(i, v; orderedValues) { 216 if (passedIndexes.canFind(i)) { 217 continue; 218 } 219 if (aarray[k] == v) { 220 orderedKeys[i] = k; 221 passedIndexes ~= i; 222 break; 223 } 224 } 225 } 226 } 227 } 228 229 230 /******************************************************************************* 231 * The function arranges the associative array by keys, 232 * returns an array of keys and an array of values. 233 */ 234 deprecated("Use sortAssocArrayItemsByKeys") 235 void orderAssociativeArrayByKeys(V, K)( 236 in V[K] aarray, out K[] orderedKeys, out V[] orderedValues 237 ) { 238 orderAssociativeArray!"keys"(aarray, orderedKeys, orderedValues); 239 } 240 241 242 /******************************************************************************* 243 * The function arranges the associative array by values, 244 * returns an array of keys and an array of values. 245 */ 246 deprecated("sortAssocArrayItemsByValues") 247 void orderAssociativeArrayByValues(V, K)( 248 in V[K] aarray, out K[] orderedKeys, out V[] orderedValues 249 ) { 250 orderAssociativeArray!"values"(aarray, orderedKeys, orderedValues); 251 } 252 253 254 /******************************************************************************* 255 * This function combines two associative arrays, returns new associative array. 256 * If two arrays have the same keys, the values from the first array are used. 257 */ 258 V[K] mixAssociativeArrays(K,V)(V[K] firstArray, V[K] secondArray) { 259 V[K] array = firstArray.dup; 260 foreach(k, v; secondArray) { 261 if (k !in array) { 262 array[k] = v; 263 } 264 } 265 return array; 266 } 267 268 269 /******************************************************************************* 270 * Function to get a value by parameter. 271 * Used to process command line arguments. 272 * Params: 273 * args = Command line arguments. 274 * flag = Parameter with "--". 275 * altFlag = Synonym for the flat. By default, empty. 276 * Returns: The value of the required parameter. 277 */ 278 string getOptionValue(string[] args, string flag, string altFlag = "") { 279 auto tmpArgs = args.dup; 280 return extractValueForFlag(tmpArgs, flag, altFlag); 281 } 282 alias getValueForFlag = getOptionValue; 283 284 285 /******************************************************************************* 286 * Function for extracting of value by flag. 287 * Unlike getOptionValue(), this function changes the passed `args`, 288 * removing the flag with a value. 289 * Params: 290 * args = Command line arguments. Changes while the function is running. 291 * flag = Parameter with "--". 292 * altFlag = Synonym for the flat. By default, empty. 293 * Returns: The value of the required parameter. 294 */ 295 string extractOptionValue(ref string[] args, string flag, string altFlag = "") { 296 string value; 297 ssize_t index1 = -1; 298 299 cycle1: foreach(option; [flag, altFlag]) { 300 foreach(i, arg; args) { 301 if (arg == option || arg.startsWith(option~"=")) { 302 index1 = i; 303 flag = option; 304 break cycle1; 305 } 306 } 307 } 308 if (index1 == -1) return ""; 309 310 if (args[index1].startsWith(flag~"=")) { 311 value = args[index1][(flag~"=").length .. $]; 312 auto tempArgs = args[0 .. index1]; 313 if (args.length > index1+1) tempArgs ~= args[index1+1 .. $]; 314 args = tempArgs; 315 } else { 316 if (args.length < index1+2) return ""; 317 value = args[index1+1]; 318 auto tempArgs = args[0 .. index1]; 319 if (args.length > index1+2) tempArgs ~= args[index1+2 .. $]; 320 args = tempArgs; 321 } 322 return value; 323 } 324 alias extractValueForFlag = extractOptionValue; 325 326 /// 327 unittest { 328 string[] args = "app -a value1 value2 -b parameter --nothing".split; 329 assert("parameter" == args.getOptionValue("-b")); 330 assert("value1" == args.getOptionValue("-a")); 331 assert("" == args.getOptionValue("--nothing")); 332 333 assert(args == "app -a value1 value2 -b parameter --nothing".split); 334 335 assert("parameter" == args.extractValueForFlag("-b")); 336 assert("app -a value1 value2 --nothing".split == args); 337 assert("value1" == args.extractValueForFlag("-a")); 338 assert("app value2 --nothing".split == args); 339 340 args = "-a value1 value2 -b parameter --nothing".split; 341 assert("parameter" == args.getOptionValue("-b")); 342 assert("value1" == args.getOptionValue("-a")); 343 assert("" == args.getOptionValue("--nothing")); 344 assert("parameter" == args.extractValueForFlag("-b")); 345 assert("-a value1 value2 --nothing".split == args); 346 assert("value1" == args.extractValueForFlag("-a")); 347 assert("value2 --nothing".split == args); 348 349 args = `build --file=/home/user/Project_1/Makefile`.split; 350 assert("/home/user/Project_1/Makefile" == args.getOptionValue("--file")); 351 352 } 353 354 355 /******************************************************************************* 356 * Function to get values as array by flag. 357 * Params: 358 * args = Command line arguments. 359 * flag = Parameter with "--". 360 * altFlag = Synonym for the flat. By default, empty. 361 * Returns: array of values after the flag. 362 */ 363 string[] getOptionRange(string[] args, string flag, string altFlag = "") { 364 auto argsCopy = args.dup; 365 return extractOptionRange(argsCopy, flag, altFlag); 366 } 367 368 369 /******************************************************************************* 370 * Function for extracting of value as array by flag. 371 * Unlike getOptionRange(), this function changes the passed arguments (args), 372 * removing the flag with a value. 373 * Params: 374 * args = Command line arguments. Changes while the function is running. 375 * flag = Parameter with "--". 376 * altFlag = Synonym for the flat. By default, empty. 377 * Returns: array of values after the flag. 378 */ 379 string[] extractOptionRange(ref string[] args, 380 string flag, 381 string altFlag = "") { 382 string[] range; 383 ssize_t index1 = args.countUntil(flag); 384 ssize_t index2 = -1; 385 386 if (index1 == -1) index1 = args.countUntil(altFlag); 387 if (index1 == -1) return range; 388 if (index1 != args.length-1 && !args[index1+1].startsWith("-")) { 389 foreach(i, arg; args[index1+1 .. $]) { 390 if (arg.startsWith("-")) { 391 index2 = index1 + i; 392 break; 393 } 394 } 395 if (index2 != -1) { 396 range = args[index1+1 .. index2+1]; 397 args = args[0 .. index1] ~ args[index2+1 .. $]; 398 } else { 399 index2 = args.length - 1; 400 range = args[index1+1 .. index2+1]; 401 args = args[0 .. index1]; 402 } 403 } 404 return range; 405 } 406 407 408 /// 409 unittest { 410 string[] args = "app -a value1 value2 -b parameter --nothing".split; 411 assert(["value1", "value2"] == getOptionRange(args, "-a")); 412 assert(["parameter"] == getOptionRange(args, "-b")); 413 string[] nothing = getOptionRange(args, "--nothing"); 414 assert(nothing.empty); 415 416 args = "app --alt value1 value2 -b parameter --nothing".split; 417 assert(["value1", "value2"] == getOptionRange(args, "-a", "--alt")); 418 419 args = "app -a -b thing".split; 420 string[] vacuum = getOptionRange(args, "-a"); 421 assert(vacuum.empty); 422 423 args = "app -a value1 value2 -b parameter --nothing".split; 424 assert("value1 value2".split == extractOptionRange(args, "-a")); 425 assert("app -b parameter --nothing".split == args); 426 427 args = "-a value1 value2 -b parameter --nothing".split; 428 assert("value1 value2".split == extractOptionRange(args, "-a")); 429 assert("-b parameter --nothing".split == args); 430 } 431 432 433 /******************************************************************************* 434 * The function converts integer number into an octal form, returns as a string. 435 */ 436 string getOctalForm(ulong value) { 437 auto writer = std.array.appender!string(); 438 formattedWrite(writer, "%o", value); 439 return writer.data; 440 } 441 alias toOctalString = getOctalForm; 442 /// 443 unittest { 444 short eight = 8; 445 assert(getOctalForm(eight) == "10"); 446 assert(33261.getOctalForm == "100755"); 447 } 448 449 450 /******************************************************************************* 451 * The function converts integer number into an binary form. 452 */ 453 string getBinaryForm(ulong value) { 454 auto writer = std.array.appender!string(); 455 formattedWrite(writer, "%b", value); 456 return writer.data; 457 } 458 459 460 /******************************************************************************* 461 * The function returns the index of the array element by value. 462 * Params: 463 * haystack = The array in which the search is performed. 464 * needle = Element value in the array. 465 * Returns: The index of the array element. -1 if the element was not found. 466 */ 467 ssize_t getIndex(T)(T[] haystack, T needle) { 468 foreach(i, el; haystack) { 469 if (el == needle) { 470 return i; 471 } 472 } 473 return -1; 474 } 475 476 477 /******************************************************************************* 478 * The function returns an array without unnecessary elements. 479 */ 480 T[] removeElementsByContent(T)(T[] haystack, T needle) { 481 for(ssize_t i; i < haystack.length; ++i) { 482 if (haystack[i] == needle) { 483 if (haystack.length-1 != i) { 484 haystack = haystack[0 .. i] ~ haystack[i+1 .. $]; 485 } else { 486 haystack = haystack[0 .. i]; 487 } 488 --i; 489 } 490 } 491 return haystack.dup; 492 } 493 /// 494 unittest { 495 auto arr = [1, 8, 17, 4, 23, 8, 8, 3, 13, 19, 8]; 496 auto res = arr.removeElementsByContent(8); 497 assert(res == [1, 17, 4, 23, 3, 13, 19]); 498 499 auto lines = ["", "straw", "", "", "one more straw", "", ""]; 500 auto straws = lines.removeElementsByContent(""); 501 assert(straws == ["straw", "one more straw"]); 502 } 503 504 505 /******************************************************************************* 506 * Encode associative array using www-form-urlencoding (copied from std.uri). 507 * Params: 508 * values = An associative array containing the values to be encoded. 509 * Returns: A string encoded using www-form-urlencoding. 510 */ 511 string urlEncode(in string[string] values) { 512 import std.uri : encodeComponent; 513 import std.array : Appender; 514 import std.format : formattedWrite; 515 if (values.length == 0) return ""; 516 Appender!string enc; 517 enc.reserve(values.length * 128); 518 bool first = true; 519 foreach (k, v; values) { 520 if (!first) enc.put('&'); 521 formattedWrite(enc, "%s=%s", encodeComponent(k), encodeComponent(v)); 522 first = false; 523 } 524 return enc.data; 525 } 526 527 528 /******************************************************************************* 529 * The function converts JSONValue to double. 530 */ 531 double convJSONValueToDouble(std.json.JSONValue json) { 532 double number; 533 static if (__traits(compiles, JSONType.float_)) { 534 switch(json.type) { 535 case JSONType.float_: number = json.floating; break; 536 case JSONType.integer: number = to!double(json.integer); break; 537 case JSONType.uinteger: number = to!double(json.uinteger); break; 538 case JSONType..string: number = to!double(json.str); break; 539 default: break; 540 } 541 } else { 542 switch(json.type) { 543 case JSON_TYPE.FLOAT: number = json.floating; break; 544 case JSON_TYPE.INTEGER: number = to!double(json.integer); break; 545 case JSON_TYPE.UINTEGER: number = to!double(json.uinteger); break; 546 case JSON_TYPE.STRING: number = to!double(json.str); break; 547 default: break; 548 } 549 } 550 return number; 551 } 552 553 554 /******************************************************************************* 555 * The function similar to functions "canFind" and "among". 556 */ 557 bool isAmong(alias pred = (a, b) => a == b, Value, Values...) 558 (Value value, Values values) 559 if ( 560 Values.length != 0 561 ) { 562 foreach (ref v; values) { 563 import std.functional : binaryFun; 564 if (binaryFun!pred(value, v)) return true; 565 } 566 return false; 567 } 568 /// ditto 569 bool isAmong(alias pred = (a, b) => a == b, T) 570 (T value, const T[] values) { 571 foreach (ref v; values) { 572 import std.functional : binaryFun; 573 if (binaryFun!pred(value, v)) return true; 574 } 575 return false; 576 } 577 578 579 /******************************************************************************* 580 * Creates new array with specified content. 581 */ 582 T[] makeFilledArray(T)(size_t len, T filler) { 583 T[] arr = new T[len]; 584 std.algorithm.fill(arr, filler); 585 return arr; 586 } 587 /// 588 unittest { 589 assert(makeFilledArray(4, 5) == [5, 5, 5, 5]); 590 assert(makeFilledArray(10, ' ') == " "); 591 } 592 593 594 /******************************************************************************* 595 * Cuts off consecutive unnecessary characters from the left side. 596 * Returns: truncated string. 597 */ 598 string stripLeft(string line, in char symbol=' ') nothrow { 599 while (!line.empty && line[0] == symbol) { 600 line = line[1 .. $]; 601 } 602 return line; 603 } 604 unittest { 605 assert(stripLeft(" string ") == "string "); 606 assert(stripLeft("ssstring", 's') == "tring"); 607 } 608 609 610 /// ditto 611 string stripLeft(string line, in char[] symbols) nothrow { 612 foreach(char ch; line.dup) { 613 if (ch.isAmong(symbols)) { 614 line = line[1 .. $]; 615 } else { 616 break; 617 } 618 } 619 return line; 620 } 621 unittest { 622 auto str = " \t\t string\t"; 623 auto spaces = [' ', '\t']; 624 auto res = "string\t"; 625 assert(stripLeft(str, spaces) == res); 626 } 627 628 629 /******************************************************************************* 630 * Cuts off consecutive unnecessary characters from the right side. 631 * Returns: truncated string. 632 */ 633 string stripRight(string line, in char symbol=' ') nothrow { 634 while (!line.empty && line[$-1] == symbol) { 635 line = line[0 .. $-1]; 636 } 637 return line; 638 } 639 unittest { 640 assert(stripRight(" string ") == " string"); 641 assert(stripRight("ssstringgg", 'g') == "ssstrin"); 642 } 643 644 645 /// ditto 646 string stripRight(string line, in char[] symbols) nothrow { 647 foreach_reverse(char ch; line.dup) { 648 if (ch.isAmong(symbols)) { 649 line = line[0 .. $-1]; 650 } else { 651 break; 652 } 653 } 654 return line; 655 } 656 unittest { 657 auto str = " \t\t string\t \t"; 658 auto spaces = [' ', '\t']; 659 auto res = " \t\t string"; 660 assert(stripRight(str, spaces) == res); 661 } 662 663 664 /******************************************************************************* 665 * Counts the number of required elements in a range. 666 * Params: 667 * range = Some input range. 668 * value = Value to find. 669 * Returns: number of required elements. 670 */ 671 size_t countElements(R, E)(R range, E value) nothrow pure @safe 672 if (isInputRange!R) 673 do { 674 size_t res; 675 foreach(elem; range) { 676 res += (value == elem); 677 } 678 return res; 679 } 680 unittest { 681 int[] arr = [0, 1, 3, 1, 5, 8, 5, 1]; 682 assert(countElements(arr, 1) == 3); 683 assert(countElements(arr, 5) == 2); 684 assert(countElements(arr, 9) == 0); 685 } 686 687 688 /******************************************************************************* 689 * Divides an array: makes a new array of arrays containing elements 690 * from source array. Each subarray contains specified number of elements. 691 * Params: 692 * arr = Source array. 693 * n = Number of elements in each new subarray. 694 */ 695 T[] divideByElemNumber(T)(T arr, size_t n) nothrow pure @safe 696 if (std.traits.isArray!T) 697 in { 698 assert(arr.length % n == 0); 699 } do { 700 T[] res; 701 for (size_t i = 0; i < arr.length; i += n) { 702 res ~= arr[i .. i + n]; 703 } 704 return res; 705 } 706 unittest { 707 string s = "123456"; 708 assert(divideByElemNumber(s, 2) == ["12", "34", "56"]); 709 assert(divideByElemNumber(s, 3) == ["123", "456"]); 710 } 711 712 713 /******************************************************************************* 714 * Creates a new dynamic array of the same size and copies the contents of 715 * the dynamic array into it. This function supports arrays with complex 716 * constant structures, unlike the 'dup' function. 717 * Params: 718 * arr = The dynamic array. 719 * Returns: 720 * A copy of the required array. 721 */ 722 T[] copyArray(T)(const T[] arr) if (!is(T == class)) { 723 T[] copy = new T[arr.length]; 724 for (size_t i = 0; i < arr.length; i++) { 725 T elem = arr[i]; 726 copy[i] = elem; 727 } 728 return copy; 729 } 730 unittest { 731 // copied for testing 732 T[] copyArray(T)(const T[] arr) if (!is(T == class)) { 733 T[] copy = new T[arr.length]; 734 for (size_t i = 0; i < arr.length; i++) { 735 // it doesn't work with postblit, only with modern copy ctor 736 T elem = arr[i]; 737 copy[i] = elem; 738 } 739 return copy; 740 } 741 struct S { 742 int x, y; 743 bool[] a; 744 this(ref return scope const S rhs) { 745 this.x = rhs.x; 746 this.y = rhs.y; 747 this.a = rhs.a.dup; 748 } 749 } 750 const S[] arr = [ 751 S(1, 2, [true, false]), 752 S(3, 4, [false, true]) 753 ]; 754 // S[] copy = arr.dup; // It can't be compiled! 755 S[] copy = copyArray(arr); // from const array to non-const one 756 757 assert(copy.length == arr.length); 758 for (size_t i = 0; i < arr.length; i++) { 759 assert(arr[i].x == copy[i].x); 760 assert(arr[i].y == copy[i].y); 761 assert(arr[i].a == copy[i].a); 762 assert(arr[i].a.ptr != copy[i].a.ptr); 763 } 764 }