mathjs
Version:
Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser and offers an integrated solution to work with numbers, big numbers, complex numbers, units, and matrices.
284 lines (248 loc) • 7.13 kB
JavaScript
var number = require('./number'),
string = require('./string'),
object = require('./object'),
types = require('./types'),
DimensionError = require('../error/DimensionError'),
IndexError = require('../error/IndexError'),
isArray = Array.isArray;
/**
* Calculate the size of a multi dimensional array.
* @param {Array} x
* @Return {Number[]} size
* @private
*/
function _size(x) {
var size = [];
while (isArray(x)) {
size.push(x.length);
x = x[0];
}
return size;
}
/**
* Calculate the size of a multi dimensional array.
* All elements in the array are checked for matching dimensions using the
* method validate
* @param {Array} x
* @Return {Number[]} size
* @throws RangeError
*/
exports.size = function size (x) {
// calculate the size
var s = _size(x);
// verify the size
exports.validate(x, s);
// TODO: don't validate here? only in a Matrix constructor?
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 (!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 (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 validate(array, size) {
var isScalar = (size.length == 0);
if (isScalar) {
// scalar
if (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 validateIndex (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);
}
};
/**
* 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] Value to be filled in in new entries,
* undefined by default
* @return {Array} array The resized array
*/
exports.resize = function resize(array, size, defaultValue) {
// TODO: add support for scalars, having size=[] ?
// check the type of the arguments
if (!isArray(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) + ')');
}
});
// count the current number of dimensions
var dims = 1;
var elem = array[0];
while (isArray(elem)) {
dims++;
elem = elem[0];
}
// adjust the number of dimensions when needed
while (dims < size.length) { // add dimensions
array = [array];
dims++;
}
while (dims > size.length) { // remove dimensions
array = array[0];
dims--;
}
// recursively resize the array
_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) {
if (!isArray(array)) throw Error('Array expected');
var i, elem,
oldLen = array.length,
newLen = size[dim],
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];
_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
if(defaultValue !== undefined) {
// fill new elements with the default value
for (i = oldLen; i < newLen; i++) {
array[i] = object.clone(defaultValue);
}
}
}
}
/**
* Squeeze a multi dimensional array
* @param {Array} array
* @return {Array} array
* @private
*/
exports.squeeze = function squeeze(array) {
while(isArray(array) && array.length === 1) {
array = array[0];
}
return array;
};
/**
* Unsqueeze a multi dimensional array: add dimensions when missing
* @param {Array} array
* @param {Number} dims Number of desired dimensions
* @return {Array} array
* @private
*/
exports.unsqueeze = function unsqueeze(array, dims) {
var size = exports.size(array);
for (var i = 0, ii = (dims - size.length); i < ii; i++) {
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)
* @private
*/
exports.flatten = function flatten(array) {
var flat = array,
isArray = Array.isArray;
while (isArray(flat[0])) {
var next = [];
for (var i = 0, ii = flat.length; i < ii; i++) {
next = next.concat.apply(next, flat[i]);
}
flat = next;
}
return flat;
};
/**
* Test whether an object is an array
* @param {*} value
* @return {Boolean} isArray
*/
exports.isArray = isArray;