mirror of
https://github.com/josdejong/mathjs.git
synced 2025-12-08 19:46:04 +00:00
511 lines
14 KiB
JavaScript
511 lines
14 KiB
JavaScript
var util = require('../util/index'),
|
|
DimensionError = require('../error/DimensionError'),
|
|
|
|
Index = require('./Index'),
|
|
|
|
number = util.number,
|
|
string = util.string,
|
|
array = util.array,
|
|
object = util.object,
|
|
|
|
isArray = Array.isArray,
|
|
validateIndex = array.validateIndex;
|
|
|
|
/**
|
|
* @constructor Matrix
|
|
*
|
|
* A Matrix is a wrapper around an Array. A matrix can hold a multi dimensional
|
|
* array. A matrix can be constructed as:
|
|
* var matrix = new Matrix(data)
|
|
*
|
|
* Matrix contains the functions to resize, get and set values, get the size,
|
|
* clone the matrix and to convert the matrix to a vector, array, or scalar.
|
|
* Furthermore, one can iterate over the matrix using map and forEach.
|
|
* The internal Array of the Matrix can be accessed using the function valueOf.
|
|
*
|
|
* Example usage:
|
|
* var matrix = new Matrix([[1, 2], [3, 4]);
|
|
* matix.size(); // [2, 2]
|
|
* matrix.resize([3, 2], 5);
|
|
* matrix.valueOf(); // [[1, 2], [3, 4], [5, 5]]
|
|
* matrix.subset([1,2]) // 3 (indexes are zero-based)
|
|
*
|
|
* @param {Array | Matrix} [data] A multi dimensional array
|
|
*/
|
|
function Matrix(data) {
|
|
if (!(this instanceof Matrix)) {
|
|
throw new SyntaxError('Constructor must be called with the new operator');
|
|
}
|
|
|
|
if (data instanceof Matrix) {
|
|
// clone data from a Matrix
|
|
this._data = data.clone()._data;
|
|
}
|
|
else if (isArray(data)) {
|
|
// use array
|
|
// replace nested Matrices with Arrays
|
|
this._data = preprocess(data);
|
|
}
|
|
else if (data != null) {
|
|
// unsupported type
|
|
throw new TypeError('Unsupported type of data (' + util.types.type(data) + ')');
|
|
}
|
|
else {
|
|
// nothing provided
|
|
this._data = [];
|
|
}
|
|
|
|
// verify the size of the array
|
|
this._size = array.size(this._data);
|
|
}
|
|
|
|
/**
|
|
* Test whether an object is a Matrix
|
|
* @param {*} object
|
|
* @return {Boolean} isMatrix
|
|
*/
|
|
Matrix.isMatrix = function isMatrix(object) {
|
|
return (object instanceof Matrix);
|
|
};
|
|
|
|
/**
|
|
* Get a subset of the matrix, or replace a subset of the matrix.
|
|
*
|
|
* Usage:
|
|
* var subset = matrix.subset(index) // retrieve subset
|
|
* var value = matrix.subset(index, replacement) // replace subset
|
|
*
|
|
* @param {Index} index
|
|
* @param {Array | Matrix | *} [replacement]
|
|
* @param {*} [defaultValue] Default value, filled in on new entries when
|
|
* the matrix is resized. If not provided,
|
|
* new matrix elements will be left undefined.
|
|
*/
|
|
Matrix.prototype.subset = function subset(index, replacement, defaultValue) {
|
|
switch (arguments.length) {
|
|
case 1:
|
|
return _get(this, index);
|
|
|
|
// intentional fall through
|
|
case 2:
|
|
case 3:
|
|
return _set(this, index, replacement, defaultValue);
|
|
|
|
default:
|
|
throw new SyntaxError('Wrong number of arguments');
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Get a single element from the matrix.
|
|
* @param {Number[]} index Zero-based index
|
|
* @return {*} value
|
|
*/
|
|
Matrix.prototype.get = function get(index) {
|
|
if (!isArray(index)) {
|
|
throw new TypeError('Array expected');
|
|
}
|
|
if (index.length != this._size.length) {
|
|
throw new DimensionError(index.length, this._size.length);
|
|
}
|
|
|
|
var data = this._data;
|
|
for (var i = 0, ii = index.length; i < ii; i++) {
|
|
var index_i = index[i];
|
|
validateIndex(index_i, data.length);
|
|
data = data[index_i];
|
|
}
|
|
|
|
return object.clone(data);
|
|
};
|
|
|
|
/**
|
|
* Replace a single element in the matrix.
|
|
* @param {Number[]} index Zero-based index
|
|
* @param {*} value
|
|
* @param {*} [defaultValue] Default value, filled in on new entries when
|
|
* the matrix is resized. If not provided,
|
|
* new matrix elements will be left undefined.
|
|
* @return {Matrix} self
|
|
*/
|
|
Matrix.prototype.set = function set (index, value, defaultValue) {
|
|
var i, ii;
|
|
|
|
// validate input type and dimensions
|
|
if (!isArray(index)) {
|
|
throw new Error('Array expected');
|
|
}
|
|
if (index.length < this._size.length) {
|
|
throw new DimensionError(index.length, this._size.length, '<');
|
|
}
|
|
|
|
// enlarge matrix when needed
|
|
var size = index.map(function (i) {
|
|
return i + 1;
|
|
});
|
|
_fit(this, size, defaultValue);
|
|
|
|
// traverse over the dimensions
|
|
var data = this._data;
|
|
for (i = 0, ii = index.length - 1; i < ii; i++) {
|
|
var index_i = index[i];
|
|
validateIndex(index_i, data.length);
|
|
data = data[index_i];
|
|
}
|
|
|
|
// set new value
|
|
index_i = index[index.length - 1];
|
|
validateIndex(index_i, data.length);
|
|
data[index_i] = value;
|
|
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Get a submatrix of this matrix
|
|
* @param {Matrix} matrix
|
|
* @param {Index} index Zero-based index
|
|
* @private
|
|
*/
|
|
function _get (matrix, index) {
|
|
if (!(index instanceof Index)) {
|
|
throw new TypeError('Invalid index');
|
|
}
|
|
|
|
var isScalar = index.isScalar();
|
|
if (isScalar) {
|
|
// return a scalar
|
|
return matrix.get(index.min());
|
|
}
|
|
else {
|
|
// validate dimensions
|
|
var size = index.size();
|
|
if (size.length != matrix._size.length) {
|
|
throw new DimensionError(size.length, matrix._size.length);
|
|
}
|
|
|
|
// retrieve submatrix
|
|
var submatrix = new Matrix(_getSubmatrix(matrix._data, index, size.length, 0));
|
|
// TODO: more efficient when creating an empty matrix and setting _data and _size manually
|
|
|
|
// squeeze matrix output
|
|
while (isArray(submatrix._data) && submatrix._data.length == 1) {
|
|
submatrix._data = submatrix._data[0];
|
|
submatrix._size.shift();
|
|
}
|
|
|
|
return submatrix;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Recursively get a submatrix of a multi dimensional matrix.
|
|
* Index is not checked for correct number of dimensions.
|
|
* @param {Array} data
|
|
* @param {Index} index
|
|
* @param {number} dims Total number of dimensions
|
|
* @param {number} dim Current dimension
|
|
* @return {Array} submatrix
|
|
* @private
|
|
*/
|
|
function _getSubmatrix (data, index, dims, dim) {
|
|
var last = (dim == dims - 1);
|
|
var range = index.range(dim);
|
|
|
|
if (last) {
|
|
return range.map(function (i) {
|
|
validateIndex(i, data.length);
|
|
return data[i];
|
|
});
|
|
}
|
|
else {
|
|
return range.map(function (i) {
|
|
validateIndex(i, data.length);
|
|
var child = data[i];
|
|
return _getSubmatrix(child, index, dims, dim + 1);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Replace a submatrix in this matrix
|
|
* Indexes are zero-based.
|
|
* @param {Matrix} matrix
|
|
* @param {Index} index
|
|
* @param {Matrix | Array | *} submatrix
|
|
* @param {*} [defaultValue] Default value, filled in on new entries when
|
|
* the matrix is resized. If not provided,
|
|
* new matrix elements will be left undefined.
|
|
* @return {Matrix} matrix
|
|
* @private
|
|
*/
|
|
function _set (matrix, index, submatrix, defaultValue) {
|
|
if (!(index instanceof Index)) {
|
|
throw new TypeError('Invalid index');
|
|
}
|
|
|
|
// get index size and check whether the index contains a single value
|
|
var iSize = index.size(),
|
|
isScalar = index.isScalar();
|
|
|
|
// calculate the size of the submatrix, and convert it into an Array if needed
|
|
var sSize;
|
|
if (submatrix instanceof Matrix) {
|
|
sSize = submatrix.size();
|
|
submatrix = submatrix.valueOf();
|
|
}
|
|
else {
|
|
sSize = array.size(submatrix);
|
|
}
|
|
|
|
if (isScalar) {
|
|
// set a scalar
|
|
|
|
// check whether submatrix is a scalar
|
|
if (sSize.length != 0) {
|
|
throw new TypeError('Scalar expected');
|
|
}
|
|
|
|
matrix.set(index.min(), submatrix, defaultValue);
|
|
}
|
|
else {
|
|
// set a submatrix
|
|
|
|
// validate dimensions
|
|
if (iSize.length < matrix._size.length) {
|
|
throw new DimensionError(iSize.length, matrix._size.length, '<');
|
|
}
|
|
|
|
// unsqueeze the submatrix when needed
|
|
for (var i = 0, ii = iSize.length - sSize.length; i < ii; i++) {
|
|
submatrix = [submatrix];
|
|
sSize.unshift(1);
|
|
}
|
|
|
|
// check whether the size of the submatrix matches the index size
|
|
if (!object.deepEqual(iSize, sSize)) {
|
|
throw new DimensionError(iSize, sSize);
|
|
}
|
|
|
|
// enlarge matrix when needed
|
|
var size = index.max().map(function (i) {
|
|
return i + 1;
|
|
});
|
|
_fit(matrix, size, defaultValue);
|
|
|
|
// insert the sub matrix
|
|
var dims = iSize.length,
|
|
dim = 0;
|
|
_setSubmatrix (matrix._data, index, submatrix, dims, dim);
|
|
}
|
|
|
|
return matrix;
|
|
}
|
|
|
|
/**
|
|
* Replace a submatrix of a multi dimensional matrix.
|
|
* @param {Array} data
|
|
* @param {Index} index
|
|
* @param {Array} submatrix
|
|
* @param {number} dims Total number of dimensions
|
|
* @param {number} dim
|
|
* @private
|
|
*/
|
|
function _setSubmatrix (data, index, submatrix, dims, dim) {
|
|
var last = (dim == dims - 1),
|
|
range = index.range(dim);
|
|
|
|
if (last) {
|
|
range.forEach(function (dataIndex, subIndex) {
|
|
validateIndex(dataIndex);
|
|
data[dataIndex] = submatrix[subIndex];
|
|
});
|
|
}
|
|
else {
|
|
range.forEach(function (dataIndex, subIndex) {
|
|
validateIndex(dataIndex);
|
|
_setSubmatrix(data[dataIndex], index, submatrix[subIndex], dims, dim + 1);
|
|
});
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Resize the matrix
|
|
* @param {Number[]} size
|
|
* @param {*} [defaultValue] Default value, filled in on new entries.
|
|
* If not provided, the matrix elements will
|
|
* be left undefined.
|
|
* @return {Matrix} self The matrix itself is returned
|
|
*/
|
|
Matrix.prototype.resize = function resize(size, defaultValue) {
|
|
this._size = object.clone(size);
|
|
this._data = array.resize(this._data, this._size, defaultValue);
|
|
|
|
// return the matrix itself
|
|
return this;
|
|
};
|
|
|
|
/**
|
|
* Enlarge the matrix when it is smaller than given size.
|
|
* If the matrix is larger or equal sized, nothing is done.
|
|
* @param {Matrix} matrix The matrix to be resized
|
|
* @param {Number[]} size
|
|
* @param {*} [defaultValue] Default value, filled in on new entries.
|
|
* If not provided, the matrix elements will
|
|
* be left undefined.
|
|
* @private
|
|
*/
|
|
function _fit(matrix, size, defaultValue) {
|
|
var newSize = object.clone(matrix._size),
|
|
changed = false;
|
|
|
|
// add dimensions when needed
|
|
while (newSize.length < size.length) {
|
|
newSize.unshift(0);
|
|
changed = true;
|
|
}
|
|
|
|
// enlarge size when needed
|
|
for (var i = 0, ii = size.length; i < ii; i++) {
|
|
if (size[i] > newSize[i]) {
|
|
newSize[i] = size[i];
|
|
changed = true;
|
|
}
|
|
}
|
|
|
|
if (changed) {
|
|
// resize only when size is changed
|
|
matrix.resize(newSize, defaultValue);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a clone of the matrix
|
|
* @return {Matrix} clone
|
|
*/
|
|
Matrix.prototype.clone = function clone() {
|
|
var matrix = new Matrix();
|
|
matrix._data = object.clone(this._data);
|
|
matrix._size = object.clone(this._size);
|
|
return matrix;
|
|
};
|
|
|
|
/**
|
|
* Retrieve the size of the matrix.
|
|
* @returns {Number[]} size
|
|
*/
|
|
Matrix.prototype.size = function size() {
|
|
return this._size;
|
|
};
|
|
|
|
/**
|
|
* Create a new matrix with the results of the callback function executed on
|
|
* each entry of the matrix.
|
|
* @param {function} callback The callback function is invoked with three
|
|
* parameters: the value of the element, the index
|
|
* of the element, and the Matrix being traversed.
|
|
* @return {Matrix} matrix
|
|
*/
|
|
Matrix.prototype.map = function map(callback) {
|
|
var me = this;
|
|
var matrix = new Matrix();
|
|
var index = [];
|
|
var recurse = function (value, dim) {
|
|
if (isArray(value)) {
|
|
return value.map(function (child, i) {
|
|
index[dim] = i;
|
|
return recurse(child, dim + 1);
|
|
});
|
|
}
|
|
else {
|
|
return callback(value, index, me);
|
|
}
|
|
};
|
|
matrix._data = recurse(this._data, 0);
|
|
matrix._size = object.clone(this._size);
|
|
|
|
return matrix;
|
|
};
|
|
|
|
/**
|
|
* Execute a callback function on each entry of the matrix.
|
|
* @param {function} callback The callback function is invoked with three
|
|
* parameters: the value of the element, the index
|
|
* of the element, and the Matrix being traversed.
|
|
*/
|
|
Matrix.prototype.forEach = function forEach(callback) {
|
|
var me = this;
|
|
var index = [];
|
|
var recurse = function (value, dim) {
|
|
if (isArray(value)) {
|
|
value.forEach(function (child, i) {
|
|
index[dim] = i;
|
|
recurse(child, dim + 1);
|
|
});
|
|
}
|
|
else {
|
|
callback(value, index, me);
|
|
}
|
|
};
|
|
recurse(this._data, 0);
|
|
};
|
|
|
|
/**
|
|
* Create an Array with a copy of the data of the Matrix
|
|
* @returns {Array} array
|
|
*/
|
|
Matrix.prototype.toArray = function toArray() {
|
|
return object.clone(this._data);
|
|
};
|
|
|
|
/**
|
|
* Get the primitive value of the Matrix: a multidimensional array
|
|
* @returns {Array} array
|
|
*/
|
|
Matrix.prototype.valueOf = function valueOf() {
|
|
return this._data;
|
|
};
|
|
|
|
/**
|
|
* Get a string representation of the matrix, with optional formatting options.
|
|
* @param {Object | Number | Function} [options] Formatting options. See
|
|
* lib/util/number:format for a
|
|
* description of the available
|
|
* options.
|
|
* @returns {String} str
|
|
*/
|
|
Matrix.prototype.format = function format(options) {
|
|
return string.format(this._data, options);
|
|
};
|
|
|
|
/**
|
|
* Get a string representation of the matrix
|
|
* @returns {String} str
|
|
*/
|
|
Matrix.prototype.toString = function toString() {
|
|
return string.format(this._data);
|
|
};
|
|
|
|
/**
|
|
* Preprocess data, which can be an Array or Matrix with nested Arrays and
|
|
* Matrices. Replaces all nested Matrices with Arrays
|
|
* @param {Array} data
|
|
* @return {Array} data
|
|
*/
|
|
function preprocess(data) {
|
|
for (var i = 0, ii = data.length; i < ii; i++) {
|
|
var elem = data[i];
|
|
if (isArray(elem)) {
|
|
data[i] = preprocess(elem);
|
|
}
|
|
else if (elem instanceof Matrix) {
|
|
data[i] = preprocess(elem._data);
|
|
}
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
// exports
|
|
module.exports = Matrix;
|