1 Arrays in ucode are ordered collections that can store any ucode value. Unlike 2 many other scripting languages where arrays are implemented as hash tables or 3 linked lists, ucode arrays are true arrays in memory. This implementation detail 4 provides several distinctive characteristics that developers should understand 5 when working with arrays in ucode. 6 7 ## Key Characteristics of Ucode Arrays 8 9 ### True Memory Arrays 10 11 Ucode arrays are implemented as true arrays in memory, which means: 12 - They offer fast random access to elements by index 13 - They are stored contiguously in memory 14 - Memory allocation expands as needed to accommodate the highest used index 15 16 ### Sparse Array Behavior 17 18 Because ucode arrays are true arrays in memory: 19 - Memory is always allocated contiguously up to the highest used index 20 - All positions (including unused ones) consume memory 21 - "Empty" or unused positions contain `null` values 22 - There is no special optimization for sparse arrays 23 24 ### Negative Index Support 25 26 Ucode arrays provide convenient negative indexing: 27 - `-1` refers to the last element 28 - `-2` refers to the second-last element 29 - And so on, allowing easy access to elements from the end of the array 30 31 ### Type Flexibility 32 33 Arrays in ucode can hold any ucode value type: 34 - Booleans, numbers (integers and doubles), strings 35 - Objects and arrays (allowing nested arrays) 36 - Functions and null values 37 - No type restrictions between elements (unlike typed arrays in some languages) 38 39 ## Core Array Functions 40 41 ### Array Information Functions 42 43 #### {@link module:core#length|length(x)} → {number} 44 45 Returns the number of elements in an array. This is one of the most fundamental 46 array operations in ucode. 47 48 ``` 49 let fruits = ["apple", "banana", "orange"]; 50 length(fruits); // 3 51 52 let sparse = []; 53 sparse[10] = "value"; 54 length(sparse); // 11 (includes empty slots) 55 ``` 56 57 For arrays, `length()` returns the highest index plus one, which means it 58 includes empty slots in sparse arrays. If the input is not an array, string, or 59 object, `length()` returns null. 60 61 #### {@link module:core#index|index(arr_or_str, needle)} → {number} 62 63 Searches for a value in an array and returns the index of the first matching occurrence. 64 65 ``` 66 let colors = ["red", "green", "blue", "green"]; 67 index(colors, "green"); // 1 (returns first match) 68 index(colors, "yellow"); // -1 (not found) 69 ``` 70 71 Unlike many other languages where array search functions return -1 or null for 72 non-matching items, `index()` in ucode specifically returns -1 when the value 73 isn't found. It returns null only if the first argument is neither an array nor 74 a string. 75 76 #### {@link module:core#rindex|rindex(arr_or_str, needle)} → {number} 77 78 Similar to `index()`, but searches backward from the end of the array: 79 80 ``` 81 let colors = ["red", "green", "blue", "green"]; 82 rindex(colors, "green"); // 3 (last occurrence) 83 ``` 84 85 #### Checking if a Value is an Array 86 87 To determine if a value is an array, use the `type()` function: 88 89 ``` 90 function isArray(value) { 91 return type(value) == "array"; 92 } 93 94 isArray([1, 2, 3]); // true 95 isArray("string"); // false 96 isArray({key: "value"}); // false 97 isArray(null); // false 98 ``` 99 100 The `type()` function is extremely useful for defensive programming, especially 101 in ucode where functions often need to determine the type of their arguments to 102 process them correctly. 103 104 ### Manipulation Functions 105 106 #### {@link module:core#push|push(arr, ...values)} → {*} 107 108 Adds one or more elements to the end of an array and returns the last pushed 109 value. 110 111 ``` 112 let x = [1, 2, 3]; 113 push(x, 4, 5, 6); // 6 114 print(x); // [1, 2, 3, 4, 5, 6] 115 ``` 116 117 Returns null if the array was empty or if a non-array argument was passed. 118 119 #### {@link module:core#pop|pop(arr)} → {*} 120 121 Removes the last element from an array and returns it. 122 123 ``` 124 let x = [1, 2, 3]; 125 let lastItem = pop(x); // 3 126 print(x); // [1, 2] 127 ``` 128 129 Returns null if the array was empty or if a non-array argument was passed. 130 131 #### {@link module:core#unshift|unshift(arr, ...values)} → {*} 132 133 Adds one or more elements to the beginning of an array and returns the last 134 value added. 135 136 ``` 137 let x = [3, 4, 5]; 138 unshift(x, 1, 2); // 2 139 print(x); // [1, 2, 3, 4, 5] 140 ``` 141 142 #### {@link module:core#shift|shift(arr)} → {*} 143 144 Removes the first element from an array and returns it. 145 146 ``` 147 let x = [1, 2, 3]; 148 let firstItem = shift(x); // 1 149 print(x); // [2, 3] 150 ``` 151 152 Returns null if the array was empty or if a non-array argument was passed. 153 154 ### Transformation Functions 155 156 #### {@link module:core#map|map(arr, fn)} → {Array} 157 158 Creates a new array populated with the results of calling a provided function on 159 every element in the calling array. 160 161 ``` 162 let numbers = [1, 2, 3, 4]; 163 let squares = map(numbers, x => x * x); // [1, 4, 9, 16] 164 ``` 165 166 Note: The callback function receives three arguments: 167 1. The current element value 168 2. The current index 169 3. The array being processed 170 171 ``` 172 let values = map(["foo", "bar", "baz"], function(value, index, array) { 173 return `${index}: ${value} (from array of length ${length(array)})`; 174 }); 175 ``` 176 177 ##### Important Pitfall with Built-in Functions 178 179 A common mistake when using `map()` is passing a built-in function directly as 180 the callback. Consider this example attempting to convert an array of strings to 181 integers: 182 183 ``` 184 // ⚠️ INCORRECT: This will not work as expected! 185 let strings = ["10", "32", "13"]; 186 let nums = map(strings, int); // Results will be unpredictable! 187 ``` 188 189 This fails because the `map()` function calls the callback with three arguments: 190 1. The current value (`"10"`, `"32"`, etc.) 191 2. The current index (`0`, `1`, `2`) 192 3. The original array (`["10", "32", "13"]`) 193 194 So what actually happens is equivalent to: 195 ``` 196 int("10", 0, ["10", "32", "13"]) // Interprets 0 as base parameter! 197 int("32", 1, ["10", "32", "13"]) // Interprets 1 as base parameter! 198 int("13", 2, ["10", "32", "13"]) // Interprets 2 as base parameter! 199 ``` 200 201 The second argument to `int()` is interpreted as the numeric base, causing 202 unexpected conversion results: 203 204 - `"10"` in base 0 is interpreted as decimal 10 (base 0 is a special case that auto-detects the base) 205 - `"32"` in base 1 produces `NaN` because base 1 is invalid (a numeral system needs at least 2 distinct digits) 206 - `"13"` in base 2 produces `1` because in binary only `0` and `1` are valid digits - it converts `"1"` successfully and stops at the invalid character `"3"` 207 208 The actual result would be `[10, NaN, 1]`, which is certainly not what you'd 209 expect when trying to convert string numbers to integers! 210 211 To fix this, wrap the function call in an arrow function or a regular function 212 that controls the number of arguments: 213 214 ``` 215 // ✓ CORRECT: Using arrow function to control arguments 216 let strings = ["10", "32", "13"]; 217 let nums = map(strings, x => int(x)); // [10, 32, 13] 218 219 // Alternative approach using a named function 220 function toInt(str) { 221 return int(str); 222 } 223 let nums2 = map(strings, toInt); // [10, 32, 13] 224 ``` 225 226 This pattern applies to many other built-in functions like `length()`, `trim()`, 227 `b64enc()`, etc. Always wrap built-in functions when using them with `map()` to 228 ensure they receive only the intended arguments. 229 230 #### {@link module:core#filter|filter(arr, fn)} → {Array} 231 232 Creates a new array with all elements that pass the test implemented by the 233 provided function. 234 235 ``` 236 let numbers = [1, 2, 3, 4, 5, 6]; 237 let evens = filter(numbers, x => x % 2 == 0); // [2, 4, 6] 238 ``` 239 240 The callback function receives the same three arguments as in `map()`. 241 242 #### {@link module:core#sort|sort(arr, fn)} → {Array} 243 244 Sorts the elements of an array in place and returns the sorted array, optionally 245 using a custom compare function. 246 247 ``` 248 let numbers = [3, 1, 4, 2]; 249 sort(numbers); // [1, 2, 3, 4] 250 ``` 251 252 With a custom compare function: 253 254 ``` 255 let people = [ 256 { name: "Alice", age: 25 }, 257 { name: "Bob", age: 30 }, 258 { name: "Charlie", age: 20 } 259 ]; 260 261 sort(people, (a, b) => a.age - b.age); 262 // [{ name: "Charlie", age: 20 }, { name: "Alice", age: 25 }, { name: "Bob", age: 30 }] 263 ``` 264 265 #### {@link module:core#reverse|reverse(arr)} → {Array} 266 267 Returns a new array with the order of all elements reversed. 268 269 ``` 270 let arr = [1, 2, 3]; 271 reverse(arr); // [3, 2, 1] 272 ``` 273 274 This function also works on strings: 275 276 ``` 277 reverse("hello"); // "olleh" 278 ``` 279 280 Returns null if the argument is neither an array nor a string. 281 282 #### {@link module:core#uniq|uniq(array)} → {Array} 283 284 Creates a new array with all duplicate elements removed. 285 286 ``` 287 let array = [1, 2, 2, 3, 1, 4, 5, 4]; 288 uniq(array); // [1, 2, 3, 4, 5] 289 ``` 290 291 Returns null if a non-array argument is given. 292 293 ### Helper Functions and Recipes 294 295 The ucode standard library provides essential array functions, but many common 296 operations must be implemented manually. Below, you'll find example 297 implementations for frequently needed array operations that aren't built into 298 the core library. 299 300 These recipes demonstrate how to leverage ucode's existing functions to build 301 more complex array utilities. 302 303 #### Array Intersection 304 305 Returns a new array containing elements present in all provided arrays. 306 307 ``` 308 function intersect(...arrays) { 309 if (!length(arrays)) 310 return []; 311 312 let result = arrays[0]; 313 314 for (let i = 1; i < length(arrays); i++) { 315 result = filter(result, item => item in arrays[i]); 316 } 317 318 return uniq(result); 319 } 320 321 // Example usage: 322 let a = [1, 2, 3, 4]; 323 let b = [2, 3, 5]; 324 let c = [2, 3, 6]; 325 intersect(a, b, c); // [2, 3] 326 ``` 327 328 This implementation takes advantage of ucode's `in` operator, which checks if a 329 value exists in an array using strict equality comparison. This makes the code 330 more concise than using `index()` and checking if the result is not -1. 331 332 #### Array Merge/Concatenation 333 334 Combines multiple arrays into a new array. Taking advantage of ucode's variadic 335 `push()` function with the spread operator provides an elegant solution: 336 337 ``` 338 function merge(...arrays) { 339 let result = []; 340 341 for (arr in arrays) { 342 push(result, ...arr); // Spreads all elements from the array directly into push 343 } 344 345 return result; 346 } 347 348 // Example usage: 349 let a = [1, 2]; 350 let b = [3, 4]; 351 let c = [5, 6]; 352 merge(a, b, c); // [1, 2, 3, 4, 5, 6] 353 ``` 354 355 This implementation leverages the variadic nature of `push()`, which accepts any 356 number of arguments. The spread operator (`...`) unpacks each array, passing all 357 its elements as individual arguments to `push()`. This is both more efficient 358 and more readable than nested loops. 359 360 For processing very large arrays, you might want to use a batching approach to 361 avoid potential call stack limitations: 362 363 ``` 364 function mergeWithBatching(...arrays) { 365 let result = []; 366 const BATCH_SIZE = 1000; 367 368 for (arr in arrays) { 369 // Handle array in batches to avoid excessive function arguments 370 for (let i = 0; i < length(arr); i += BATCH_SIZE) { 371 let batch = slice(arr, i, i + BATCH_SIZE); 372 push(result, ...batch); 373 } 374 } 375 376 return result; 377 } 378 ``` 379 380 #### Array Difference 381 382 Returns elements in the first array not present in subsequent arrays. 383 384 ``` 385 function difference(array, ...others) { 386 return filter(array, item => { 387 for (other in others) { 388 if (item in other) 389 return false; 390 } 391 return true; 392 }); 393 } 394 395 // Example usage: 396 let a = [1, 2, 3, 4, 5]; 397 let b = [2, 3]; 398 let c = [4]; 399 difference(a, b, c); // [1, 5] 400 ``` 401 402 This implementation uses the `in` operator for concise and efficient membership 403 testing, filtering out any elements from the first array that appear in any of 404 the subsequent arrays. 405 406 #### Array Chunk 407 408 Splits an array into chunks of specified size. 409 410 ``` 411 function chunk(array, size) { 412 if (size <= 0) 413 return []; 414 415 let result = []; 416 417 for (let i = 0; i < length(array); i += size) { 418 push(result, slice(array, i, i + size)); 419 } 420 421 return result; 422 } 423 424 // Example usage: 425 let nums = [1, 2, 3, 4, 5, 6, 7, 8]; 426 chunk(nums, 3); // [[1, 2, 3], [4, 5, 6], [7, 8]] 427 ``` 428 429 This implementation uses a counting `for` loop combined with `slice()`, which is 430 both more idiomatic and more efficient. The approach: 431 432 1. Iterates through the array in steps of `size` 433 2. Uses `slice()` to extract chunks of the appropriate size 434 3. Automatically handles the last chunk being smaller if the array length isn't divisible by the chunk size 435 436 This pattern leverages ucode's built-in functions for cleaner, more maintainable 437 code. No temporary variables are needed to track the current chunk or count, 438 making the implementation more straightforward. 439 440 #### Array Sum 441 442 Calculates the sum of all numeric elements in an array. 443 444 ``` 445 function sum(array) { 446 let result = 0; 447 448 for (item in array) { 449 if (type(item) == "int" || type(item) == "double") 450 result += item; 451 } 452 453 return result; 454 } 455 456 // Example usage: 457 let nums = [1, 2, 3, 4, 5]; 458 sum(nums); // 15 459 ``` 460 461 #### Array Flatten 462 463 Flattens a nested array structure. 464 465 ``` 466 function flatten(array, depth) { 467 if (depth === undefined) 468 depth = 1; 469 470 let result = []; 471 472 for (item in array) { 473 if (type(item) == "array" && depth > 0) { 474 let flattened = flatten(item, depth - 1); 475 for (subItem in flattened) { 476 push(result, subItem); 477 } 478 } else { 479 push(result, item); 480 } 481 } 482 483 return result; 484 } 485 486 // Example usage: 487 let nested = [1, [2, [3, 4], 5], 6]; 488 flatten(nested); // [1, 2, [3, 4], 5, 6] 489 flatten(nested, 2); // [1, 2, 3, 4, 5, 6] 490 ``` 491 492 ## Advanced Array Techniques and Considerations 493 494 ### Memory Management 495 496 When working with arrays in ucode, you should understand several important 497 memory characteristics that affect performance and resource usage: 498 499 #### Sparse Array Memory Implications 500 501 Since ucode arrays are true arrays in memory, array memory consumption scales 502 linearly with the highest index used, regardless of how many elements are 503 actually stored: 504 505 ``` 506 let arr = []; 507 arr[1000000] = "value"; // Allocates memory for 1,000,001 pointers 508 ``` 509 510 Important technical details about ucode array memory usage: 511 - Each array element consumes pointer-sized memory (4 bytes on 32-bit systems, 8 bytes on 64-bit systems) 512 - No optimizations exist for sparse arrays - every position up to the highest index is allocated 513 - When an array grows beyond its capacity, it's reallocated with a growth factor of 1.5 514 - Memory is allocated even for "empty" slots (which contain the `null` value) 515 516 For example, on a 64-bit system, creating an array with a single element at 517 index 1,000,000 would consume approximately 8MB of memory (1,000,001 * 8 bytes), 518 even though only one actual value is stored. 519 520 ``` 521 // Demonstrates memory consumption 522 let smallArray = []; 523 for (let i = 0; i < 10; i++) 524 smallArray[i] = i; 525 526 let sparseArray = []; 527 sparseArray[1000000] = "far away"; 528 529 print(`Small array has ${length(smallArray)} elements\n`); 530 print(`Sparse array has ${length(sparseArray)} elements\n`); 531 // Even though only one value is actually set, memory is allocated for all positions 532 ``` 533 534 This behavior makes ucode arrays efficient for random access but potentially 535 wasteful for very sparse data structures. For data with large gaps or when 536 working on memory-constrained systems, consider alternative approaches like 537 objects with numeric string keys. 538 539 #### Negative Index Implementation 540 541 While negative indices provide convenient access to elements from the end of an 542 array, they involve an internal conversion process: 543 544 ``` 545 let arr = [1, 2, 3, 4, 5]; 546 arr[-1]; // Internally converted to arr[length(arr) - 1], or arr[4] 547 arr[-3]; // Internally converted to arr[length(arr) - 3], or arr[2] 548 ``` 549 550 This conversion adds a small computational overhead compared to direct positive 551 indexing. For performance-critical code processing large arrays, consider using 552 positive indices when possible. 553 554 #### Mixed-Type Arrays and Sorting 555 556 Arrays in ucode can contain mixed types, offering great flexibility but 557 requiring careful handling, especially with operations like `sort()`: 558 559 ``` 560 let mixed = ["apple", 10, true, {name: "object"}, [1, 2]]; 561 562 // Sort behaves differently with mixed types 563 // Numbers come first, then arrays, then strings, then booleans, then objects 564 sort(mixed); // [10, [1, 2], "apple", true, {name: "object"}] 565 ``` 566 567 When sorting mixed-type arrays, consider implementing a custom comparison 568 function to define the sorting behavior explicitly: 569 570 ``` 571 function mixedTypeSort(a, b) { 572 // Sort by type first, then by value 573 let typeA = type(a); 574 let typeB = type(b); 575 576 if (typeA != typeB) { 577 // Define a type precedence order 578 let typePrecedence = { 579 "int": 1, 580 "double": 2, 581 "string": 3, 582 "bool": 4, 583 "array": 5, 584 "object": 6 585 }; 586 return typePrecedence[typeA] - typePrecedence[typeB]; 587 } 588 589 // If same type, compare values appropriately 590 if (typeA == "string" || typeA == "array") 591 return length(a) - length(b); 592 return a - b; 593 } 594 595 // Now sorting is more predictable 596 sort(mixed, mixedTypeSort); 597 ``` 598 599 ### Performance Optimization 600 601 When working with large arrays, consider these optimization techniques: 602 603 1. **Pre-allocation**: Where possible, create arrays with known capacity rather than growing incrementally 604 2. **Batch operations**: Minimize individual push/pop/shift/unshift calls by processing in batches 605 3. **Avoid unnecessary copies**: Use in-place operations when possible 606 4. **Filter early**: Filter arrays early in processing pipelines to reduce subsequent operation sizes 607 608 ### Array Deep Copying 609 610 Since arrays are reference types, creating true copies requires special handling: 611 612 ``` 613 function deepCopy(arr) { 614 if (type(arr) != "array") 615 return arr; 616 617 let result = []; 618 for (item in arr) { 619 if (type(item) == "array") 620 push(result, deepCopy(item)); 621 else if (type(item) == "object") 622 push(result, deepCopyObject(item)); 623 else 624 push(result, item); 625 } 626 return result; 627 } 628 629 function deepCopyObject(obj) { 630 if (type(obj) != "object") 631 return obj; 632 633 let result = {}; 634 for (key in keys(obj)) { 635 if (type(obj[key]) == "array") 636 result[key] = deepCopy(obj[key]); 637 else if (type(obj[key]) == "object") 638 result[key] = deepCopyObject(obj[key]); 639 else 640 result[key] = obj[key]; 641 } 642 return result; 643 } 644 ``` 645 646 This approach ensures all nested arrays and objects are properly copied rather 647 than referenced.
This page was automatically generated by LXR 0.3.1. • OpenWrt