array.js (16857B)
1 "use strict"; 2 3 Object.defineProperty(exports, "__esModule", { 4 value: true 5 }); 6 exports.arraySize = arraySize; 7 exports.contains = contains; 8 exports.filter = filter; 9 exports.filterRegExp = filterRegExp; 10 exports.flatten = flatten; 11 exports.forEach = forEach; 12 exports.generalize = generalize; 13 exports.getArrayDataType = getArrayDataType; 14 exports.identify = identify; 15 exports.initial = initial; 16 exports.join = join; 17 exports.last = last; 18 exports.map = map; 19 exports.processSizesWildcard = processSizesWildcard; 20 exports.reshape = reshape; 21 exports.resize = resize; 22 exports.squeeze = squeeze; 23 exports.unsqueeze = unsqueeze; 24 exports.validate = validate; 25 exports.validateIndex = validateIndex; 26 27 var _number = require("./number.js"); 28 29 var _is = require("./is.js"); 30 31 var _string = require("./string.js"); 32 33 var _DimensionError = require("../error/DimensionError.js"); 34 35 var _IndexError = require("../error/IndexError.js"); 36 37 /** 38 * Calculate the size of a multi dimensional array. 39 * This function checks the size of the first entry, it does not validate 40 * whether all dimensions match. (use function `validate` for that) 41 * @param {Array} x 42 * @Return {Number[]} size 43 */ 44 function arraySize(x) { 45 var s = []; 46 47 while (Array.isArray(x)) { 48 s.push(x.length); 49 x = x[0]; 50 } 51 52 return s; 53 } 54 /** 55 * Recursively validate whether each element in a multi dimensional array 56 * has a size corresponding to the provided size array. 57 * @param {Array} array Array to be validated 58 * @param {number[]} size Array with the size of each dimension 59 * @param {number} dim Current dimension 60 * @throws DimensionError 61 * @private 62 */ 63 64 65 function _validate(array, size, dim) { 66 var i; 67 var len = array.length; 68 69 if (len !== size[dim]) { 70 throw new _DimensionError.DimensionError(len, size[dim]); 71 } 72 73 if (dim < size.length - 1) { 74 // recursively validate each child array 75 var dimNext = dim + 1; 76 77 for (i = 0; i < len; i++) { 78 var child = array[i]; 79 80 if (!Array.isArray(child)) { 81 throw new _DimensionError.DimensionError(size.length - 1, size.length, '<'); 82 } 83 84 _validate(array[i], size, dimNext); 85 } 86 } else { 87 // last dimension. none of the childs may be an array 88 for (i = 0; i < len; i++) { 89 if (Array.isArray(array[i])) { 90 throw new _DimensionError.DimensionError(size.length + 1, size.length, '>'); 91 } 92 } 93 } 94 } 95 /** 96 * Validate whether each element in a multi dimensional array has 97 * a size corresponding to the provided size array. 98 * @param {Array} array Array to be validated 99 * @param {number[]} size Array with the size of each dimension 100 * @throws DimensionError 101 */ 102 103 104 function validate(array, size) { 105 var isScalar = size.length === 0; 106 107 if (isScalar) { 108 // scalar 109 if (Array.isArray(array)) { 110 throw new _DimensionError.DimensionError(array.length, 0); 111 } 112 } else { 113 // array 114 _validate(array, size, 0); 115 } 116 } 117 /** 118 * Test whether index is an integer number with index >= 0 and index < length 119 * when length is provided 120 * @param {number} index Zero-based index 121 * @param {number} [length] Length of the array 122 */ 123 124 125 function validateIndex(index, length) { 126 if (!(0, _is.isNumber)(index) || !(0, _number.isInteger)(index)) { 127 throw new TypeError('Index must be an integer (value: ' + index + ')'); 128 } 129 130 if (index < 0 || typeof length === 'number' && index >= length) { 131 throw new _IndexError.IndexError(index, length); 132 } 133 } 134 /** 135 * Resize a multi dimensional array. The resized array is returned. 136 * @param {Array} array Array to be resized 137 * @param {Array.<number>} size Array with the size of each dimension 138 * @param {*} [defaultValue=0] Value to be filled in in new entries, 139 * zero by default. Specify for example `null`, 140 * to clearly see entries that are not explicitly 141 * set. 142 * @return {Array} array The resized array 143 */ 144 145 146 function resize(array, size, defaultValue) { 147 // TODO: add support for scalars, having size=[] ? 148 // check the type of the arguments 149 if (!Array.isArray(array) || !Array.isArray(size)) { 150 throw new TypeError('Array expected'); 151 } 152 153 if (size.length === 0) { 154 throw new Error('Resizing to scalar is not supported'); 155 } // check whether size contains positive integers 156 157 158 size.forEach(function (value) { 159 if (!(0, _is.isNumber)(value) || !(0, _number.isInteger)(value) || value < 0) { 160 throw new TypeError('Invalid size, must contain positive integers ' + '(size: ' + (0, _string.format)(size) + ')'); 161 } 162 }); // recursively resize the array 163 164 var _defaultValue = defaultValue !== undefined ? defaultValue : 0; 165 166 _resize(array, size, 0, _defaultValue); 167 168 return array; 169 } 170 /** 171 * Recursively resize a multi dimensional array 172 * @param {Array} array Array to be resized 173 * @param {number[]} size Array with the size of each dimension 174 * @param {number} dim Current dimension 175 * @param {*} [defaultValue] Value to be filled in in new entries, 176 * undefined by default. 177 * @private 178 */ 179 180 181 function _resize(array, size, dim, defaultValue) { 182 var i; 183 var elem; 184 var oldLen = array.length; 185 var newLen = size[dim]; 186 var minLen = Math.min(oldLen, newLen); // apply new length 187 188 array.length = newLen; 189 190 if (dim < size.length - 1) { 191 // non-last dimension 192 var dimNext = dim + 1; // resize existing child arrays 193 194 for (i = 0; i < minLen; i++) { 195 // resize child array 196 elem = array[i]; 197 198 if (!Array.isArray(elem)) { 199 elem = [elem]; // add a dimension 200 201 array[i] = elem; 202 } 203 204 _resize(elem, size, dimNext, defaultValue); 205 } // create new child arrays 206 207 208 for (i = minLen; i < newLen; i++) { 209 // get child array 210 elem = []; 211 array[i] = elem; // resize new child array 212 213 _resize(elem, size, dimNext, defaultValue); 214 } 215 } else { 216 // last dimension 217 // remove dimensions of existing values 218 for (i = 0; i < minLen; i++) { 219 while (Array.isArray(array[i])) { 220 array[i] = array[i][0]; 221 } 222 } // fill new elements with the default value 223 224 225 for (i = minLen; i < newLen; i++) { 226 array[i] = defaultValue; 227 } 228 } 229 } 230 /** 231 * Re-shape a multi dimensional array to fit the specified dimensions 232 * @param {Array} array Array to be reshaped 233 * @param {Array.<number>} sizes List of sizes for each dimension 234 * @returns {Array} Array whose data has been formatted to fit the 235 * specified dimensions 236 * 237 * @throws {DimensionError} If the product of the new dimension sizes does 238 * not equal that of the old ones 239 */ 240 241 242 function reshape(array, sizes) { 243 var flatArray = flatten(array); 244 var currentLength = flatArray.length; 245 246 if (!Array.isArray(array) || !Array.isArray(sizes)) { 247 throw new TypeError('Array expected'); 248 } 249 250 if (sizes.length === 0) { 251 throw new _DimensionError.DimensionError(0, currentLength, '!='); 252 } 253 254 sizes = processSizesWildcard(sizes, currentLength); 255 var newLength = product(sizes); 256 257 if (currentLength !== newLength) { 258 throw new _DimensionError.DimensionError(newLength, currentLength, '!='); 259 } 260 261 try { 262 return _reshape(flatArray, sizes); 263 } catch (e) { 264 if (e instanceof _DimensionError.DimensionError) { 265 throw new _DimensionError.DimensionError(newLength, currentLength, '!='); 266 } 267 268 throw e; 269 } 270 } 271 /** 272 * Replaces the wildcard -1 in the sizes array. 273 * @param {Array.<number>} sizes List of sizes for each dimension. At most on wildcard. 274 * @param {number} currentLength Number of elements in the array. 275 * @throws {Error} If more than one wildcard or unable to replace it. 276 * @returns {Array.<number>} The sizes array with wildcard replaced. 277 */ 278 279 280 function processSizesWildcard(sizes, currentLength) { 281 var newLength = product(sizes); 282 var processedSizes = sizes.slice(); 283 var WILDCARD = -1; 284 var wildCardIndex = sizes.indexOf(WILDCARD); 285 var isMoreThanOneWildcard = sizes.indexOf(WILDCARD, wildCardIndex + 1) >= 0; 286 287 if (isMoreThanOneWildcard) { 288 throw new Error('More than one wildcard in sizes'); 289 } 290 291 var hasWildcard = wildCardIndex >= 0; 292 var canReplaceWildcard = currentLength % newLength === 0; 293 294 if (hasWildcard) { 295 if (canReplaceWildcard) { 296 processedSizes[wildCardIndex] = -currentLength / newLength; 297 } else { 298 throw new Error('Could not replace wildcard, since ' + currentLength + ' is no multiple of ' + -newLength); 299 } 300 } 301 302 return processedSizes; 303 } 304 /** 305 * Computes the product of all array elements. 306 * @param {Array<number>} array Array of factors 307 * @returns {number} Product of all elements 308 */ 309 310 311 function product(array) { 312 return array.reduce(function (prev, curr) { 313 return prev * curr; 314 }, 1); 315 } 316 /** 317 * Iteratively re-shape a multi dimensional array to fit the specified dimensions 318 * @param {Array} array Array to be reshaped 319 * @param {Array.<number>} sizes List of sizes for each dimension 320 * @returns {Array} Array whose data has been formatted to fit the 321 * specified dimensions 322 */ 323 324 325 function _reshape(array, sizes) { 326 // testing if there are enough elements for the requested shape 327 var tmpArray = array; 328 var tmpArray2; // for each dimensions starting by the last one and ignoring the first one 329 330 for (var sizeIndex = sizes.length - 1; sizeIndex > 0; sizeIndex--) { 331 var size = sizes[sizeIndex]; 332 tmpArray2 = []; // aggregate the elements of the current tmpArray in elements of the requested size 333 334 var length = tmpArray.length / size; 335 336 for (var i = 0; i < length; i++) { 337 tmpArray2.push(tmpArray.slice(i * size, (i + 1) * size)); 338 } // set it as the new tmpArray for the next loop turn or for return 339 340 341 tmpArray = tmpArray2; 342 } 343 344 return tmpArray; 345 } 346 /** 347 * Squeeze a multi dimensional array 348 * @param {Array} array 349 * @param {Array} [size] 350 * @returns {Array} returns the array itself 351 */ 352 353 354 function squeeze(array, size) { 355 var s = size || arraySize(array); // squeeze outer dimensions 356 357 while (Array.isArray(array) && array.length === 1) { 358 array = array[0]; 359 s.shift(); 360 } // find the first dimension to be squeezed 361 362 363 var dims = s.length; 364 365 while (s[dims - 1] === 1) { 366 dims--; 367 } // squeeze inner dimensions 368 369 370 if (dims < s.length) { 371 array = _squeeze(array, dims, 0); 372 s.length = dims; 373 } 374 375 return array; 376 } 377 /** 378 * Recursively squeeze a multi dimensional array 379 * @param {Array} array 380 * @param {number} dims Required number of dimensions 381 * @param {number} dim Current dimension 382 * @returns {Array | *} Returns the squeezed array 383 * @private 384 */ 385 386 387 function _squeeze(array, dims, dim) { 388 var i, ii; 389 390 if (dim < dims) { 391 var next = dim + 1; 392 393 for (i = 0, ii = array.length; i < ii; i++) { 394 array[i] = _squeeze(array[i], dims, next); 395 } 396 } else { 397 while (Array.isArray(array)) { 398 array = array[0]; 399 } 400 } 401 402 return array; 403 } 404 /** 405 * Unsqueeze a multi dimensional array: add dimensions when missing 406 * 407 * Paramter `size` will be mutated to match the new, unqueezed matrix size. 408 * 409 * @param {Array} array 410 * @param {number} dims Desired number of dimensions of the array 411 * @param {number} [outer] Number of outer dimensions to be added 412 * @param {Array} [size] Current size of array. 413 * @returns {Array} returns the array itself 414 * @private 415 */ 416 417 418 function unsqueeze(array, dims, outer, size) { 419 var s = size || arraySize(array); // unsqueeze outer dimensions 420 421 if (outer) { 422 for (var i = 0; i < outer; i++) { 423 array = [array]; 424 s.unshift(1); 425 } 426 } // unsqueeze inner dimensions 427 428 429 array = _unsqueeze(array, dims, 0); 430 431 while (s.length < dims) { 432 s.push(1); 433 } 434 435 return array; 436 } 437 /** 438 * Recursively unsqueeze a multi dimensional array 439 * @param {Array} array 440 * @param {number} dims Required number of dimensions 441 * @param {number} dim Current dimension 442 * @returns {Array | *} Returns the squeezed array 443 * @private 444 */ 445 446 447 function _unsqueeze(array, dims, dim) { 448 var i, ii; 449 450 if (Array.isArray(array)) { 451 var next = dim + 1; 452 453 for (i = 0, ii = array.length; i < ii; i++) { 454 array[i] = _unsqueeze(array[i], dims, next); 455 } 456 } else { 457 for (var d = dim; d < dims; d++) { 458 array = [array]; 459 } 460 } 461 462 return array; 463 } 464 /** 465 * Flatten a multi dimensional array, put all elements in a one dimensional 466 * array 467 * @param {Array} array A multi dimensional array 468 * @return {Array} The flattened array (1 dimensional) 469 */ 470 471 472 function flatten(array) { 473 if (!Array.isArray(array)) { 474 // if not an array, return as is 475 return array; 476 } 477 478 var flat = []; 479 array.forEach(function callback(value) { 480 if (Array.isArray(value)) { 481 value.forEach(callback); // traverse through sub-arrays recursively 482 } else { 483 flat.push(value); 484 } 485 }); 486 return flat; 487 } 488 /** 489 * A safe map 490 * @param {Array} array 491 * @param {function} callback 492 */ 493 494 495 function map(array, callback) { 496 return Array.prototype.map.call(array, callback); 497 } 498 /** 499 * A safe forEach 500 * @param {Array} array 501 * @param {function} callback 502 */ 503 504 505 function forEach(array, callback) { 506 Array.prototype.forEach.call(array, callback); 507 } 508 /** 509 * A safe filter 510 * @param {Array} array 511 * @param {function} callback 512 */ 513 514 515 function filter(array, callback) { 516 if (arraySize(array).length !== 1) { 517 throw new Error('Only one dimensional matrices supported'); 518 } 519 520 return Array.prototype.filter.call(array, callback); 521 } 522 /** 523 * Filter values in a callback given a regular expression 524 * @param {Array} array 525 * @param {RegExp} regexp 526 * @return {Array} Returns the filtered array 527 * @private 528 */ 529 530 531 function filterRegExp(array, regexp) { 532 if (arraySize(array).length !== 1) { 533 throw new Error('Only one dimensional matrices supported'); 534 } 535 536 return Array.prototype.filter.call(array, function (entry) { 537 return regexp.test(entry); 538 }); 539 } 540 /** 541 * A safe join 542 * @param {Array} array 543 * @param {string} separator 544 */ 545 546 547 function join(array, separator) { 548 return Array.prototype.join.call(array, separator); 549 } 550 /** 551 * Assign a numeric identifier to every element of a sorted array 552 * @param {Array} a An array 553 * @return {Array} An array of objects containing the original value and its identifier 554 */ 555 556 557 function identify(a) { 558 if (!Array.isArray(a)) { 559 throw new TypeError('Array input expected'); 560 } 561 562 if (a.length === 0) { 563 return a; 564 } 565 566 var b = []; 567 var count = 0; 568 b[0] = { 569 value: a[0], 570 identifier: 0 571 }; 572 573 for (var i = 1; i < a.length; i++) { 574 if (a[i] === a[i - 1]) { 575 count++; 576 } else { 577 count = 0; 578 } 579 580 b.push({ 581 value: a[i], 582 identifier: count 583 }); 584 } 585 586 return b; 587 } 588 /** 589 * Remove the numeric identifier from the elements 590 * @param {array} a An array 591 * @return {array} An array of values without identifiers 592 */ 593 594 595 function generalize(a) { 596 if (!Array.isArray(a)) { 597 throw new TypeError('Array input expected'); 598 } 599 600 if (a.length === 0) { 601 return a; 602 } 603 604 var b = []; 605 606 for (var i = 0; i < a.length; i++) { 607 b.push(a[i].value); 608 } 609 610 return b; 611 } 612 /** 613 * Check the datatype of a given object 614 * This is a low level implementation that should only be used by 615 * parent Matrix classes such as SparseMatrix or DenseMatrix 616 * This method does not validate Array Matrix shape 617 * @param {Array} array 618 * @param {function} typeOf Callback function to use to determine the type of a value 619 * @return {string} 620 */ 621 622 623 function getArrayDataType(array, typeOf) { 624 var type; // to hold type info 625 626 var length = 0; // to hold length value to ensure it has consistent sizes 627 628 for (var i = 0; i < array.length; i++) { 629 var item = array[i]; 630 var isArray = Array.isArray(item); // Saving the target matrix row size 631 632 if (i === 0 && isArray) { 633 length = item.length; 634 } // If the current item is an array but the length does not equal the targetVectorSize 635 636 637 if (isArray && item.length !== length) { 638 return undefined; 639 } 640 641 var itemType = isArray ? getArrayDataType(item, typeOf) // recurse into a nested array 642 : typeOf(item); 643 644 if (type === undefined) { 645 type = itemType; // first item 646 } else if (type !== itemType) { 647 return 'mixed'; 648 } else {// we're good, everything has the same type so far 649 } 650 } 651 652 return type; 653 } 654 /** 655 * Return the last item from an array 656 * @param array 657 * @returns {*} 658 */ 659 660 661 function last(array) { 662 return array[array.length - 1]; 663 } 664 /** 665 * Get all but the last element of array. 666 */ 667 668 669 function initial(array) { 670 return array.slice(0, array.length - 1); 671 } 672 /** 673 * Test whether an array or string contains an item 674 * @param {Array | string} array 675 * @param {*} item 676 * @return {boolean} 677 */ 678 679 680 function contains(array, item) { 681 return array.indexOf(item) !== -1; 682 }