mathjs
Version:
Math.js is an extensive math library for JavaScript and Node.js. It features a flexible expression parser with support for symbolic computation, comes with a large set of built-in functions and constants, and offers an integrated solution to work with dif
278 lines (245 loc) • 7.75 kB
JavaScript
const clone = require('../../utils/object').clone
const isInteger = require('../../utils/number').isInteger
function factory (type) {
/**
* Create an index. An Index can store ranges and sets for multiple dimensions.
* Matrix.get, Matrix.set, and math.subset accept an Index as input.
*
* Usage:
* const index = new Index(range1, range2, matrix1, array1, ...)
*
* Where each parameter can be any of:
* A number
* A string (containing a name of an object property)
* An instance of Range
* An Array with the Set values
* A Matrix with the Set values
*
* The parameters start, end, and step must be integer numbers.
*
* @class Index
* @Constructor Index
* @param {...*} ranges
*/
function Index (ranges) {
if (!(this instanceof Index)) {
throw new SyntaxError('Constructor must be called with the new operator')
}
this._dimensions = []
this._isScalar = true
for (let i = 0, ii = arguments.length; i < ii; i++) {
const arg = arguments[i]
if (type.isRange(arg)) {
this._dimensions.push(arg)
this._isScalar = false
} else if (Array.isArray(arg) || type.isMatrix(arg)) {
// create matrix
const m = _createImmutableMatrix(arg.valueOf())
this._dimensions.push(m)
// size
const size = m.size()
// scalar
if (size.length !== 1 || size[0] !== 1) {
this._isScalar = false
}
} else if (typeof arg === 'number') {
this._dimensions.push(_createImmutableMatrix([arg]))
} else if (typeof arg === 'string') {
// object property (arguments.count should be 1)
this._dimensions.push(arg)
} else {
throw new TypeError('Dimension must be an Array, Matrix, number, string, or Range')
}
// TODO: implement support for wildcard '*'
}
}
/**
* Attach type information
*/
Index.prototype.type = 'Index'
Index.prototype.isIndex = true
function _createImmutableMatrix (arg) {
// loop array elements
for (let i = 0, l = arg.length; i < l; i++) {
if (typeof arg[i] !== 'number' || !isInteger(arg[i])) {
throw new TypeError('Index parameters must be positive integer numbers')
}
}
// create matrix
return new type.ImmutableDenseMatrix(arg)
}
/**
* Create a clone of the index
* @memberof Index
* @return {Index} clone
*/
Index.prototype.clone = function () {
const index = new Index()
index._dimensions = clone(this._dimensions)
index._isScalar = this._isScalar
return index
}
/**
* Create an index from an array with ranges/numbers
* @memberof Index
* @param {Array.<Array | number>} ranges
* @return {Index} index
* @private
*/
Index.create = function (ranges) {
const index = new Index()
Index.apply(index, ranges)
return index
}
/**
* Retrieve the size of the index, the number of elements for each dimension.
* @memberof Index
* @returns {number[]} size
*/
Index.prototype.size = function () {
const size = []
for (let i = 0, ii = this._dimensions.length; i < ii; i++) {
const d = this._dimensions[i]
size[i] = (typeof d === 'string') ? 1 : d.size()[0]
}
return size
}
/**
* Get the maximum value for each of the indexes ranges.
* @memberof Index
* @returns {number[]} max
*/
Index.prototype.max = function () {
const values = []
for (let i = 0, ii = this._dimensions.length; i < ii; i++) {
const range = this._dimensions[i]
values[i] = (typeof range === 'string') ? range : range.max()
}
return values
}
/**
* Get the minimum value for each of the indexes ranges.
* @memberof Index
* @returns {number[]} min
*/
Index.prototype.min = function () {
const values = []
for (let i = 0, ii = this._dimensions.length; i < ii; i++) {
const range = this._dimensions[i]
values[i] = (typeof range === 'string') ? range : range.min()
}
return values
}
/**
* Loop over each of the ranges of the index
* @memberof Index
* @param {Function} callback Called for each range with a Range as first
* argument, the dimension as second, and the
* index object as third.
*/
Index.prototype.forEach = function (callback) {
for (let i = 0, ii = this._dimensions.length; i < ii; i++) {
callback(this._dimensions[i], i, this)
}
}
/**
* Retrieve the dimension for the given index
* @memberof Index
* @param {Number} dim Number of the dimension
* @returns {Range | null} range
*/
Index.prototype.dimension = function (dim) {
return this._dimensions[dim] || null
}
/**
* Test whether this index contains an object property
* @returns {boolean} Returns true if the index is an object property
*/
Index.prototype.isObjectProperty = function () {
return this._dimensions.length === 1 && typeof this._dimensions[0] === 'string'
}
/**
* Returns the object property name when the Index holds a single object property,
* else returns null
* @returns {string | null}
*/
Index.prototype.getObjectProperty = function () {
return this.isObjectProperty() ? this._dimensions[0] : null
}
/**
* Test whether this index contains only a single value.
*
* This is the case when the index is created with only scalar values as ranges,
* not for ranges resolving into a single value.
* @memberof Index
* @return {boolean} isScalar
*/
Index.prototype.isScalar = function () {
return this._isScalar
}
/**
* Expand the Index into an array.
* For example new Index([0,3], [2,7]) returns [[0,1,2], [2,3,4,5,6]]
* @memberof Index
* @returns {Array} array
*/
Index.prototype.toArray = function () {
const array = []
for (let i = 0, ii = this._dimensions.length; i < ii; i++) {
const dimension = this._dimensions[i]
array.push((typeof dimension === 'string') ? dimension : dimension.toArray())
}
return array
}
/**
* Get the primitive value of the Index, a two dimensional array.
* Equivalent to Index.toArray().
* @memberof Index
* @returns {Array} array
*/
Index.prototype.valueOf = Index.prototype.toArray
/**
* Get the string representation of the index, for example '[2:6]' or '[0:2:10, 4:7, [1,2,3]]'
* @memberof Index
* @returns {String} str
*/
Index.prototype.toString = function () {
const strings = []
for (let i = 0, ii = this._dimensions.length; i < ii; i++) {
const dimension = this._dimensions[i]
if (typeof dimension === 'string') {
strings.push(JSON.stringify(dimension))
} else {
strings.push(dimension.toString())
}
}
return '[' + strings.join(', ') + ']'
}
/**
* Get a JSON representation of the Index
* @memberof Index
* @returns {Object} Returns a JSON object structured as:
* `{"mathjs": "Index", "ranges": [{"mathjs": "Range", start: 0, end: 10, step:1}, ...]}`
*/
Index.prototype.toJSON = function () {
return {
mathjs: 'Index',
dimensions: this._dimensions
}
}
/**
* Instantiate an Index from a JSON object
* @memberof Index
* @param {Object} json A JSON object structured as:
* `{"mathjs": "Index", "dimensions": [{"mathjs": "Range", start: 0, end: 10, step:1}, ...]}`
* @return {Index}
*/
Index.fromJSON = function (json) {
return Index.create(json.dimensions)
}
return Index
}
exports.name = 'Index'
exports.path = 'type'
exports.factory = factory