simple-squiggle

A restricted subset of Squiggle
Log | Files | Refs | README

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 }