'use strict'; var number = require('./number'); var string = require('./string'); var object = require('./object'); var types = require('./types'); var DimensionError = require('../error/DimensionError'); var IndexError = require('../error/IndexError'); /** * Calculate the size of a multi dimensional array. * This function checks the size of the first entry, it does not validate * whether all dimensions match. (use function `validate` for that) * @param {Array} x * @Return {Number[]} size */ exports.size = function (x) { var s = []; while (Array.isArray(x)) { s.push(x.length); x = x[0]; } return s; }; /** * Recursively validate whether each element in a multi dimensional array * has a size corresponding to the provided size array. * @param {Array} array Array to be validated * @param {number[]} size Array with the size of each dimension * @param {number} dim Current dimension * @throws DimensionError * @private */ function _validate(array, size, dim) { var i; var len = array.length; if (len != size[dim]) { throw new DimensionError(len, size[dim]); } if (dim < size.length - 1) { // recursively validate each child array var dimNext = dim + 1; for (i = 0; i < len; i++) { var child = array[i]; if (!Array.isArray(child)) { throw new DimensionError(size.length - 1, size.length, '<'); } _validate(array[i], size, dimNext); } } else { // last dimension. none of the childs may be an array for (i = 0; i < len; i++) { if (Array.isArray(array[i])) { throw new DimensionError(size.length + 1, size.length, '>'); } } } } /** * Validate whether each element in a multi dimensional array has * a size corresponding to the provided size array. * @param {Array} array Array to be validated * @param {number[]} size Array with the size of each dimension * @throws DimensionError */ exports.validate = function(array, size) { var isScalar = (size.length == 0); if (isScalar) { // scalar if (Array.isArray(array)) { throw new DimensionError(array.length, 0); } } else { // array _validate(array, size, 0); } }; /** * Test whether index is an integer number with index >= 0 and index < length * @param {number} index Zero-based index * @param {number} [length] Length of the array */ exports.validateIndex = function(index, length) { if (!number.isNumber(index) || !number.isInteger(index)) { throw new TypeError('Index must be an integer (value: ' + index + ')'); } if (index < 0) { throw new IndexError(index); } if (length !== undefined && index >= length) { throw new IndexError(index, length); } }; // a constant used to specify an undefined defaultValue exports.UNINITIALIZED = {}; /** * Resize a multi dimensional array. The resized array is returned. * @param {Array} array Array to be resized * @param {Array.} size Array with the size of each dimension * @param {*} [defaultValue=0] Value to be filled in in new entries, * zero by default. To leave new entries undefined, * specify array.UNINITIALIZED as defaultValue * @return {Array} array The resized array */ exports.resize = function(array, size, defaultValue) { // TODO: add support for scalars, having size=[] ? // check the type of the arguments if (!Array.isArray(array) || !Array.isArray(size)) { throw new TypeError('Array expected'); } if (size.length === 0) { throw new Error('Resizing to scalar is not supported'); } // check whether size contains positive integers size.forEach(function (value) { if (!number.isNumber(value) || !number.isInteger(value) || value < 0) { throw new TypeError('Invalid size, must contain positive integers ' + '(size: ' + string.format(size) + ')'); } }); // recursively resize the array var _defaultValue = (defaultValue !== undefined) ? defaultValue : 0; _resize(array, size, 0, _defaultValue); return array; }; /** * Recursively resize a multi dimensional array * @param {Array} array Array to be resized * @param {number[]} size Array with the size of each dimension * @param {number} dim Current dimension * @param {*} [defaultValue] Value to be filled in in new entries, * undefined by default. * @private */ function _resize (array, size, dim, defaultValue) { var i; var elem; var oldLen = array.length; var newLen = size[dim]; var minLen = Math.min(oldLen, newLen); // apply new length array.length = newLen; if (dim < size.length - 1) { // non-last dimension var dimNext = dim + 1; // resize existing child arrays for (i = 0; i < minLen; i++) { // resize child array elem = array[i]; if (!Array.isArray(elem)) { elem = [elem]; // add a dimension array[i] = elem; } _resize(elem, size, dimNext, defaultValue); } // create new child arrays for (i = minLen; i < newLen; i++) { // get child array elem = []; array[i] = elem; // resize new child array _resize(elem, size, dimNext, defaultValue); } } else { // last dimension // remove dimensions of existing values for (i = 0; i < minLen; i++) { while (Array.isArray(array[i])) { array[i] = array[i][0]; } } if(defaultValue !== exports.UNINITIALIZED) { // fill new elements with the default value for (i = minLen; i < newLen; i++) { array[i] = object.clone(defaultValue); } } } } /** * Squeeze a multi dimensional array * @param {Array} array * @param {Array} [size] * @returns {Array} returns the array itself */ exports.squeeze = function(array, size) { var s = size || exports.size(array); // squeeze outer dimensions while (Array.isArray(array) && array.length === 1) { array = array[0]; s.shift(); } // find the first dimension to be squeezed var dims = s.length; while (s[dims - 1] === 1) { dims--; } // squeeze inner dimensions if (dims < s.length) { array = _squeeze(array, dims, 0); s.length = dims; } return array; }; /** * Recursively squeeze a multi dimensional array * @param {Array} array * @param {number} dims Required number of dimensions * @param {number} dim Current dimension * @returns {Array | *} Returns the squeezed array * @private */ function _squeeze (array, dims, dim) { var i, ii; if (dim < dims) { var next = dim + 1; for (i = 0, ii = array.length; i < ii; i++) { array[i] = _squeeze(array[i], dims, next); } } else { while (Array.isArray(array)) { array = array[0]; } } return array; } /** * Unsqueeze a multi dimensional array: add dimensions when missing * @param {Array} array * @param {number} dims Desired number of dimensions of the array * @param {number} [outer] Number of outer dimensions to be added * @param {Array} [size] Current size of array * @returns {Array} returns the array itself * @private */ exports.unsqueeze = function(array, dims, outer, size) { var s = size || exports.size(array); // unsqueeze outer dimensions if (outer) { for (var i = 0; i < outer; i++) { array = [array]; s.unshift(1); } } // unsqueeze inner dimensions array = _unsqueeze(array, dims, 0); while (s.length < dims) { s.push(1); } return array; }; /** * Recursively unsqueeze a multi dimensional array * @param {Array} array * @param {number} dims Required number of dimensions * @param {number} dim Current dimension * @returns {Array | *} Returns the squeezed array * @private */ function _unsqueeze (array, dims, dim) { var i, ii; if (Array.isArray(array)) { var next = dim + 1; for (i = 0, ii = array.length; i < ii; i++) { array[i] = _unsqueeze(array[i], dims, next); } } else { for (var d = dim; d < dims; d++) { array = [array]; } } return array; } /** * Flatten a multi dimensional array, put all elements in a one dimensional * array * @param {Array} array A multi dimensional array * @return {Array} The flattened array (1 dimensional) */ exports.flatten = function(array) { if (!Array.isArray(array)) { //if not an array, return as is return array; } var flat = []; array.forEach(function callback(value) { if (Array.isArray(value)) { value.forEach(callback); //traverse through sub-arrays recursively } else { flat.push(value); } }); return flat; }; /** * Test whether an object is an array * @param {*} value * @return {boolean} isArray */ exports.isArray = Array.isArray;