Skip to content

Instantly share code, notes, and snippets.

@Rich-Harris
Last active August 29, 2015 14:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Rich-Harris/248ae85d35ae5ab54771 to your computer and use it in GitHub Desktop.
Save Rich-Harris/248ae85d35ae5ab54771 to your computer and use it in GitHub Desktop.
math.js ES6 example
import { format, isNumber, sign } from 'number';
import { isString } from 'string';
/**
* @constructor Range
* Create a range. A range has a start, step, and end, and contains functions
* to iterate over the range.
*
* A range can be constructed as:
* var range = new Range(start, end);
* var range = new Range(start, end, step);
*
* To get the result of the range:
* range.forEach(function (x) {
* console.log(x);
* });
* range.map(function (x) {
* return math.sin(x);
* });
* range.toArray();
*
* Example usage:
* var c = new Range(2, 6); // 2:1:5
* c.toArray(); // [2, 3, 4, 5]
* var d = new Range(2, -3, -1); // 2:-1:-2
* d.toArray(); // [2, 1, 0, -1, -2]
*
* @param {Number} start included lower bound
* @param {Number} end excluded upper bound
* @param {Number} [step] step size, default value is 1
*/
export default function Range(start, end, step) {
if (!(this instanceof Range)) {
throw new SyntaxError('Constructor must be called with the new operator');
}
if (start != null && !isNumber(start)) {
throw new TypeError('Parameter start must be a number');
}
if (end != null && !isNumber(end)) {
throw new TypeError('Parameter end must be a number');
}
if (step != null && !isNumber(step)) {
throw new TypeError('Parameter step must be a number');
}
this.start = (start != null) ? parseFloat(start) : 0;
this.end = (end != null) ? parseFloat(end) : 0;
this.step = (step != null) ? parseFloat(step) : 1;
}
/**
* Parse a string into a range,
* The string contains the start, optional step, and end, separated by a colon.
* If the string does not contain a valid range, null is returned.
* For example str='0:2:11'.
* @param {String} str
* @return {Range | null} range
*/
Range.parse = function (str) {
if (!isString(str)) {
return null;
}
var args = str.split(':');
var nums = args.map(function (arg) {
return parseFloat(arg);
});
var invalid = nums.some(function (num) {
return isNaN(num);
});
if(invalid) {
return null;
}
switch (nums.length) {
case 2: return new Range(nums[0], nums[1]);
case 3: return new Range(nums[0], nums[2], nums[1]);
default: return null;
}
};
/**
* Create a clone of the range
* @return {Range} clone
*/
Range.prototype.clone = function () {
return new Range(this.start, this.end, this.step);
};
/**
* Test whether an object is a Range
* @param {*} object
* @return {Boolean} isRange
*/
Range.isRange = function (object) {
return (object instanceof Range);
};
/**
* Retrieve the size of the range.
* Returns an array containing one number, the number of elements in the range.
* @returns {Number[]} size
*/
Range.prototype.size = function () {
var len = 0,
start = this.start,
step = this.step,
end = this.end,
diff = end - start;
if (sign(step) == sign(diff)) {
len = Math.ceil((diff) / step);
}
else if (diff == 0) {
len = 0;
}
if (isNaN(len)) {
len = 0;
}
return [len];
};
/**
* Calculate the minimum value in the range
* @return {Number | undefined} min
*/
Range.prototype.min = function () {
var size = this.size()[0];
if (size > 0) {
if (this.step > 0) {
// positive step
return this.start;
}
else {
// negative step
return this.start + (size - 1) * this.step;
}
}
else {
return undefined;
}
};
/**
* Calculate the maximum value in the range
* @return {Number | undefined} max
*/
Range.prototype.max = function () {
var size = this.size()[0];
if (size > 0) {
if (this.step > 0) {
// positive step
return this.start + (size - 1) * this.step;
}
else {
// negative step
return this.start;
}
}
else {
return undefined;
}
};
/**
* Execute a callback function for each value in the range.
* @param {function} callback The callback method is invoked with three
* parameters: the value of the element, the index
* of the element, and the Matrix being traversed.
*/
Range.prototype.forEach = function (callback) {
var x = this.start;
var step = this.step;
var end = this.end;
var i = 0;
if (step > 0) {
while (x < end) {
callback(x, i, this);
x += step;
i++;
}
}
else if (step < 0) {
while (x > end) {
callback(x, i, this);
x += step;
i++;
}
}
};
/**
* Execute a callback function for each value in the Range, and return the
* results as an array
* @param {function} callback The callback method is invoked with three
* parameters: the value of the element, the index
* of the element, and the Matrix being traversed.
* @returns {Array} array
*/
Range.prototype.map = function (callback) {
var array = [];
this.forEach(function (value, index, obj) {
array[index] = callback(value, index, obj);
});
return array;
};
/**
* Create an Array with a copy of the Ranges data
* @returns {Array} array
*/
Range.prototype.toArray = function () {
var array = [];
this.forEach(function (value, index) {
array[index] = value;
});
return array;
};
/**
* Get the primitive value of the Range, a one dimensional array
* @returns {Array} array
*/
Range.prototype.valueOf = function () {
// TODO: implement a caching mechanism for range.valueOf()
return this.toArray();
};
/**
* Get a string representation of the range, with optional formatting options.
* Output is formatted as 'start:step:end', for example '2:6' or '0:0.2:11'
* @param {Object | Number | Function} [options] Formatting options. See
* lib/util/number:format for a
* description of the available
* options.
* @returns {String} str
*/
Range.prototype.format = function (options) {
var str = format(this.start, options);
if (this.step != 1) {
str += ':' + format(this.step, options);
}
str += ':' + format(this.end, options);
return str;
};
/**
* Get a string representation of the range.
* @returns {String}
*/
Range.prototype.toString = function () {
return this.format();
};
/**
* Get a JSON representation of the range
* @returns {Object} Returns a JSON object structured as:
* `{"mathjs": "Range", "start": 2, "end": 4, "step": 1}`
*/
Range.prototype.toJSON = function () {
return {
mathjs: 'Range',
start: this.start,
end: this.end,
step: this.step
};
};
/**
* Instantiate a Range from a JSON object
* @param {Object} json A JSON object structured as:
* `{"mathjs": "Range", "start": 2, "end": 4, "step": 1}`
* @return {Range}
*/
Range.fromJSON = function (json) {
return new Range(json.start, json.end, json.step);
};
// no need for 'use strict' pragma (ES6 modules are always strict)
// and no need to export a factory function, as ES6 modules
// can be as cyclical as you like
import { Complex, Range, Index, Matrix, Unit, Help, ResultSet, BigNumber };
// Using objects like `math.type` makes tree-shaking impossible and
// minification less effective. If you were okay with a single-level API...
//
// import { Range } from 'math'
//
// ...rather than...
//
// const Range = require('math').type.Range;
//
// ...then you could avoid that. But since `reviver` needs to access
// constructors by name, you'd need to recreate `math.type` for
// anyone that imported this module
const type = {
Complex,
Range,
Index,
Matrix,
Unit,
Help,
ResultSet,
BigNumber
};
/**
* Instantiate mathjs data types from their JSON representation
* @param {string} key
* @param {*} value
* @returns {*} Returns the revived object
*/
export default function reviver(key, value) {
var name = value && value.mathjs;
var constructor = type[name];
if (constructor && constructor.fromJSON) {
return constructor.fromJSON(value);
}
return value;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment