mirror of
https://github.com/josdejong/mathjs.git
synced 2025-12-08 19:46:04 +00:00
This spec tests `reshape()` with more cases than the one in test/utils/array.test.js, including various types. Also, in `_reshape()`, throw a brand new `DimensionError` instead of modifying the caught one (the error message does not change when other attributes are modified, as it is computed when the error is created).
427 lines
11 KiB
JavaScript
427 lines
11 KiB
JavaScript
'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
|
|
* when length is provided
|
|
* @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 || (typeof length === 'number' && 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.<number>} 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] = defaultValue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Re-shape a multi dimensional array to fit the specified dimensions
|
|
* @param {Array} array Array to be reshaped
|
|
* @param {Array.<number>} sizes List of sizes for each dimension
|
|
* @returns {Array} Array whose data has been formatted to fit the
|
|
* specified dimensions
|
|
*
|
|
* @throws {DimensionError} If the product of the new dimension sizes does
|
|
* not equal that of the old ones
|
|
*/
|
|
exports.reshape = function(array, sizes) {
|
|
var flatArray = exports.flatten(array);
|
|
var newArray;
|
|
|
|
var product = function (arr) {
|
|
return arr.reduce(function (prev, curr) {
|
|
return prev * curr;
|
|
});
|
|
};
|
|
|
|
if (!Array.isArray(array) || !Array.isArray(sizes)) {
|
|
throw new TypeError('Array expected');
|
|
}
|
|
|
|
if (sizes.length === 0) {
|
|
throw new DimensionError(0, product(exports.size(array)), '!=');
|
|
}
|
|
|
|
try {
|
|
newArray = _reshape(flatArray, sizes);
|
|
} catch (e) {
|
|
if (e instanceof DimensionError) {
|
|
throw new DimensionError(
|
|
product(sizes),
|
|
product(exports.size(array)),
|
|
'!='
|
|
);
|
|
}
|
|
throw e;
|
|
}
|
|
|
|
if (flatArray.length > 0) {
|
|
throw new DimensionError(
|
|
product(sizes),
|
|
product(exports.size(array)),
|
|
'!='
|
|
);
|
|
}
|
|
|
|
return newArray;
|
|
};
|
|
|
|
/**
|
|
* Recursively re-shape a multi dimensional array to fit the specified dimensions
|
|
* @param {Array} array Array to be reshaped
|
|
* @param {Array.<number>} sizes List of sizes for each dimension
|
|
* @returns {Array} Array whose data has been formatted to fit the
|
|
* specified dimensions
|
|
*
|
|
* @throws {DimensionError} If the product of the new dimension sizes does
|
|
* not equal that of the old ones
|
|
*/
|
|
function _reshape(array, sizes) {
|
|
var accumulator = [];
|
|
var i;
|
|
|
|
if (sizes.length === 0) {
|
|
if (array.length === 0) {
|
|
throw new DimensionError(null, null, '!=');
|
|
}
|
|
return array.shift();
|
|
}
|
|
for (i = 0; i < sizes[0]; i += 1) {
|
|
accumulator.push(_reshape(array, sizes.slice(1)));
|
|
}
|
|
return accumulator;
|
|
}
|
|
|
|
|
|
/**
|
|
* 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
|
|
*
|
|
* Paramter `size` will be mutated to match the new, unqueezed matrix size.
|
|
*
|
|
* @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;
|