nerdamer-ts
Version:
javascript light-weight symbolic math expression evaluator
647 lines • 19.8 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.range = exports.isVector = exports.isVariableSymbol = exports.remove = exports.warn = exports.WARNINGS = exports.validateName = exports.nround = exports.knownVariable = exports.isNumber = exports.isPrime = exports.isInt = exports.isSymbol = exports.isExpression = exports.isMatrix = exports.isArray = exports.inBrackets = exports.generatePrimes = exports.format = exports.firstObject = exports.fillHoles = exports.evenFraction = exports.even = exports.evaluate = exports.each = exports.customType = exports.convertToVector = exports.compare = exports.comboSort = exports.build = exports.block = exports.arrayUnique = exports.arrayEqual = exports.arrayMin = exports.arrayMax = exports.arrayGetVariables = exports.arrayClone = exports.arrayAddSlices = exports.arguments2Array = exports.allNumeric = exports.allSame = exports.separate = exports.scientificToDecimal = exports.nroots = exports.isNumericSymbol = exports.isNegative = exports.isFraction = exports.getCoeffs = exports.arraySum = exports.decompose_fn = void 0;
exports.variables = exports.keys = exports.text = exports.stringReplace = exports.sameSign = exports.allConstants = exports.allNumbers = exports.pretty_print = void 0;
const Settings_1 = require("../Settings");
const Groups_1 = require("../Types/Groups");
const Symbol_1 = require("../Types/Symbol");
const Build_1 = require("../Parser/Build");
const Vector_1 = require("../Types/Vector");
const Utils_js_1 = require("./Utils-js");
Object.defineProperty(exports, "decompose_fn", { enumerable: true, get: function () { return Utils_js_1.decompose_fn; } });
Object.defineProperty(exports, "getCoeffs", { enumerable: true, get: function () { return Utils_js_1.getCoeffs; } });
Object.defineProperty(exports, "isFraction", { enumerable: true, get: function () { return Utils_js_1.isFraction; } });
Object.defineProperty(exports, "isNegative", { enumerable: true, get: function () { return Utils_js_1.isNegative; } });
Object.defineProperty(exports, "isNumericSymbol", { enumerable: true, get: function () { return Utils_js_1.isNumericSymbol; } });
Object.defineProperty(exports, "nroots", { enumerable: true, get: function () { return Utils_js_1.nroots; } });
Object.defineProperty(exports, "scientificToDecimal", { enumerable: true, get: function () { return Utils_js_1.scientificToDecimal; } });
Object.defineProperty(exports, "separate", { enumerable: true, get: function () { return Utils_js_1.separate; } });
const Utils_Symbol_1 = require("./Utils.Symbol");
Object.defineProperty(exports, "arraySum", { enumerable: true, get: function () { return Utils_Symbol_1.arraySum; } });
const Math_consts_1 = require("../Functions/Math.consts");
const Matrix_1 = require("../Types/Matrix");
const Expression_1 = require("../Parser/Expression");
const Errors_1 = require("./Errors");
const Text_1 = require("./Text");
const Parser_1 = require("../Parser/Parser");
/**
* Checks to see that all symbols in array are the same
* @param {{ equals() }[]} arr
* @returns {boolean}
*/
function allSame(arr) {
let last = arr[0];
for (let i = 1, l = arr.length; i < l; i++) {
if (!arr[i].equals(last)) {
return false;
}
}
return true;
}
exports.allSame = allSame;
/**
* Checks to see if an array contains only numeric values
* @param {Array} arr
*/
function allNumeric(arr) {
for (let i = 0; i < arr.length; i++) {
if (!isNumber(arr[i])) {
return false;
}
}
return true;
}
exports.allNumeric = allNumeric;
/**
* Converts function arguments to an array. Now used by gcd and lcm in Algebra.js :)
* @deprecated Just use rest syntax: ...args!
* @param {Array|object} obj
*/
function arguments2Array(obj) {
return [].slice.call(obj);
}
exports.arguments2Array = arguments2Array;
/**
* Fills numbers between array values
* @param {number[]} arr
* @param {Integer} slices
*/
function arrayAddSlices(arr, slices) {
slices = slices || 20;
let retval = [];
let c, delta, e;
retval.push(arr[0]); //push the beginning
for (let i = 0; i < arr.length - 1; i++) {
c = arr[i];
delta = arr[i + 1] - c; //get the difference
e = delta / slices; //chop it up in the desired number of slices
for (let j = 0; j < slices; j++) {
c += e; //add the mesh to the last slice
retval.push(c);
}
}
return retval;
}
exports.arrayAddSlices = arrayAddSlices;
/**
* Clones array with clonable items
* @param {Array} arr
* @returns {Array}
*/
function arrayClone(arr) {
let new_array = [], l = arr.length;
for (let i = 0; i < l; i++) {
new_array[i] = arr[i].clone();
}
return new_array;
}
exports.arrayClone = arrayClone;
/**
* Gets all the variables in an array of Symbols
* @param {Symbol[]} arr
*/
function arrayGetVariables(arr) {
let vars = [];
for (let i = 0; i < arr.length; i++) {
if (!isSymbol(arr[i])) {
continue;
}
vars = vars.concat(arr[i].variables());
}
//remove duplicates
vars = arrayUnique(vars).sort();
//done
return vars;
}
exports.arrayGetVariables = arrayGetVariables;
/**
* Returns the minimum number in an array
* @param {Array} arr
* @returns {Number}
*/
function arrayMax(arr) {
return Math.max(...arr);
}
exports.arrayMax = arrayMax;
/**
* Returns the maximum number in an array
* @param {Array} arr
* @returns {Number}
*/
function arrayMin(arr) {
return Math.min(...arr);
}
exports.arrayMin = arrayMin;
/**
* Checks to see if two arrays are equal
* @param {Array} arr1
* @param {Array} arr2
*/
function arrayEqual(arr1, arr2) {
arr1.sort();
arr2.sort();
// The must be of the same length
if (arr1.length === arr2.length) {
for (let i = 0; i < arr1.length; i++) {
// If any two items don't match we're done
if (arr1[i] !== arr2[i]) {
return false;
}
}
// Otherwise they're equal
return true;
}
return false;
}
exports.arrayEqual = arrayEqual;
/**
* Strips duplicates out of an array
* @param {Array} arr
*/
function arrayUnique(arr) {
const l = arr.length, a = [];
for (let i = 0; i < l; i++) {
let item = arr[i];
if (a.indexOf(item) === -1) {
a.push(item);
}
}
return a;
}
exports.arrayUnique = arrayUnique;
/**
* Creates a temporary block in which one of the global settings is temporarily modified while
* the function is called. For instance if you want to parse directly to a number rather than have a symbolic
* answer for a period you would set PARSE2NUMBER to true in the block.
* @example block('PARSE2NUMBER', function(){//symbol being parsed to number}, true);
* @param settingsName
* @param {Function} f
* @param {boolean} opt - The value of the setting in the block
* @param {string} obj - The obj of interest. Usually a Symbol but could be any object
*/
function block(settingsName, f, opt, obj) {
let current_setting = Settings_1.Settings[settingsName];
Settings_1.Settings[settingsName] = opt === undefined ? true : !!opt;
let retVal = f.call(obj);
Settings_1.Settings[settingsName] = current_setting;
return retVal;
}
exports.block = block;
function build(symbol, arg_array) {
return Build_1.Build.build(symbol, arg_array);
}
exports.build = build;
/**
* Sorts and array given 2 parameters
* @param {String} a
* @param {String} b
*/
function comboSort(a, b) {
const l = a.length;
const combined = []; //the linker
for (let i = 0; i < a.length; i++) {
combined.push([a[i], b[i]]); //create the map
}
combined.sort(function (x, y) {
return x[0] - y[0];
});
var na = [], nb = [];
for (let i = 0; i < l; i++) {
na.push(combined[i][0]);
nb.push(combined[i][1]);
}
return [na, nb];
}
exports.comboSort = comboSort;
/**
* Substitutes out variables for two symbols, parses them to a number and them compares them numerically
* @param {Symbol} sym1
* @param {Symbol} sym2
* @param {string[]} vars - an optional array of variables to use
* @returns {boolean}
*/
function compare(sym1, sym2, vars) {
const n = 5; //a random number between 1 and 5 is good enough
const scope = {}; // scope object with random numbers generated using vars
let comparison;
for (let i = 0; i < vars.length; i++) {
scope[vars[i]] = new Symbol_1.Symbol(Math.floor(Math.random() * n) + 1);
}
block('PARSE2NUMBER', function () {
comparison = (0, Parser_1.parse)(sym1, scope).equals((0, Parser_1.parse)(sym2, scope));
}, false);
return comparison;
}
exports.compare = compare;
function convertToVector(x) {
if (isArray(x)) {
const vector = new Vector_1.Vector([]);
for (let i = 0; i < x.length; i++) {
vector.elements.push(convertToVector(x[i]));
}
return vector;
}
//Ensure that a nerdamer ready object is returned
if (!isSymbol(x)) {
return (0, Parser_1.parse)(x);
}
return x;
}
exports.convertToVector = convertToVector;
/**
* the Parser uses this to check if it's allowed to convert the obj to type Symbol
* @param {Object} obj
* @returns {boolean}
*/
function customType(obj) {
return obj !== undefined && obj.custom;
}
exports.customType = customType;
/**
* Loops through each item in object and calls function with item as param
* @param {Object|Array} obj
* @param {Function} fn
*/
function each(obj, fn) {
if (isArray(obj)) {
let l = obj.length;
for (let i = 0; i < l; i++) {
fn.call(obj, i);
}
}
else {
for (let x in obj) {
if (obj.hasOwnProperty(x)) {
fn.call(obj, x);
}
}
}
}
exports.each = each;
/**
* As the name states. It forces evaluation of the expression
* @param {Symbol} symbol
* @param {Symbol} o
*/
function evaluate(symbol, o = undefined) {
return block('PARSE2NUMBER', function () {
return (0, Parser_1.parse)(symbol, o);
}, true);
}
exports.evaluate = evaluate;
/**
* Checks to see if a number is an even number
* @param {Number} num
* @returns {boolean}
*/
function even(num) {
return num % 2 === 0;
}
exports.even = even;
/**
* Checks to see if a fraction is divisible by 2
* @param {number} num
* @returns {boolean}
*/
function evenFraction(num) {
return 1 / (num % 1) % 2 === 0;
}
exports.evenFraction = evenFraction;
/**
* Fills holes in an array with zero symbol or generates one with n zeroes
* @param {Array} arr
* @param {Number} n
*/
function fillHoles(arr, n) {
n = n || arr.length;
for (let i = 0; i < n; i++) {
let sym = arr[i];
if (!sym) {
arr[i] = new Symbol_1.Symbol(0);
}
}
return arr;
}
exports.fillHoles = fillHoles;
/**
* Returns the first encountered item in an object. Items do not have a fixed order in objects
* so only use if you need any first random or if there's only one item in the object
* @param {object} obj
* @param {boolean} key Return this key as first object
* @param {boolean} both
* @returns {*}
*/
function firstObject(obj, key = false, both = false) {
for (let x in obj) {
if (key) {
return x;
}
if (both) {
return {
key: x,
obj: obj[x]
};
}
return obj[x];
}
return null;
}
exports.firstObject = firstObject;
/**
* A helper function to replace multiple occurences in a string. Takes multiple arguments
* @example format('{0} nice, {0} sweet', 'something')
* //returns 'something nice, something sweet'
*/
function format(str, ...args) {
return str.replace(/{(\d+)}/g, function (match, index) {
const arg = args[index];
return typeof arg === 'function' ? arg() : arg;
});
}
exports.format = format;
/**
* Generates prime numbers up to a specified number
* @param {Number} upto
*/
function generatePrimes(upto) {
//get the last prime in the array
let last_prime = Math_consts_1.PRIMES[Math_consts_1.PRIMES.length - 1] || 2;
//no need to check if we've already encountered the number. Just check the cache.
for (let i = last_prime; i < upto; i++) {
if (isPrime(i)) {
Math_consts_1.PRIMES.push(i);
}
}
}
exports.generatePrimes = generatePrimes;
/**
* @param {String} str
* @returns {String} - returns a formatted string surrounded by brackets
*/
function inBrackets(str) {
return '(' + str + ')';
}
exports.inBrackets = inBrackets;
/**
* Checks to see if the object provided is an Array
* @param {Object} arr
*/
function isArray(arr) {
return Array.isArray(arr);
}
exports.isArray = isArray;
/**
* Checks to see if the object provided is a Matrix
* @param {Object} obj
*/
function isMatrix(obj) {
return (obj instanceof Matrix_1.Matrix);
}
exports.isMatrix = isMatrix;
/**
* Checks to see if the object provided is an Expression
* @param {Object} obj
*/
function isExpression(obj) {
return (obj instanceof Expression_1.Expression);
}
exports.isExpression = isExpression;
/**
* Checks to see if the object provided is a Symbol
* @param {Object} obj
*/
function isSymbol(obj) {
return (obj instanceof Symbol_1.Symbol);
}
exports.isSymbol = isSymbol;
/**
* Checks to see if a number is an integer
* @param {number} value
*/
function isInt(value) {
return /^[-+]?\d+e?\+?\d*$/gim.test(value.toString());
}
exports.isInt = isInt;
/**
* Checks if number is a prime number
* @param {Number} n - the number to be checked
*/
function isPrime(n) {
let q = Math.floor(Math.sqrt(n));
for (let i = 2; i <= q; i++) {
if (n % i === 0)
return false;
}
return true;
}
exports.isPrime = isPrime;
/**
* Checks if n is a number
* @param {any} n
*/
function isNumber(n) {
return /^\d+\.?\d*$/.test(n);
}
exports.isNumber = isNumber;
/**
* Generates an object with known variable value for evaluation
* @param key
* @param {any} value Any stringifyable object
* @returns {Object}
*/
function knownVariable(key, value) {
let o = {};
o[key] = value;
return o;
}
exports.knownVariable = knownVariable;
/**
* Rounds a number up to x decimal places
* @param {number} x
* @param {number} s
*/
function nround(x, s) {
if (isInt(x)) {
let xn = Number(x);
if (xn >= Number.MAX_VALUE) {
return x.toString();
}
return Number(x);
}
s = s === undefined ? 14 : s;
return Math.round(Number(x) * Math.pow(10, s)) / Math.pow(10, s);
}
exports.nround = nround;
/**
* Enforces rule: "must start with a letter or underscore and
* can have any number of underscores, letters, and numbers thereafter."
* @param {string} name The name of the symbol being checked
* @param {string} typ - The type of symbols that's being validated
* @throws {InvalidVariableNameError} - Throws an exception on fail
*/
function validateName(name, typ = 'variable') {
if (Settings_1.Settings.ALLOW_CHARS.indexOf(name) !== -1)
return;
const regex = Settings_1.Settings.VALIDATION_REGEX;
if (!(regex.test(name))) {
throw new Errors_1.InvalidVariableNameError(name + ' is not a valid ' + typ + ' name');
}
}
exports.validateName = validateName;
/**
* Used to pass warnings or low severity errors about the library
* @param msg
*/
exports.WARNINGS = [];
function warn(msg) {
exports.WARNINGS.push(msg);
if (Settings_1.Settings.SHOW_WARNINGS && console && console.warn) {
console.warn(msg);
}
}
exports.warn = warn;
/**
* Removes an item from either an array or an object. If the object is an array, the index must be
* specified after the array. If it's an object then the key must be specified
* @param {Object|Array} obj
* @param {Integer} indexOrKey
*/
function remove(obj, indexOrKey) {
let result;
if (Array.isArray(obj) && typeof indexOrKey === 'number') {
result = obj.splice(indexOrKey, 1)[0];
}
else {
result = obj[indexOrKey];
delete obj[indexOrKey];
}
return result;
}
exports.remove = remove;
/**
* Checks to see if a symbol is a variable with no multiplier nor power
* @param {Symbol} symbol
*/
function isVariableSymbol(symbol) {
return symbol.group === Groups_1.Groups.S && symbol.multiplier.equals(1) && symbol.power.equals(1);
}
exports.isVariableSymbol = isVariableSymbol;
/**
*
* Checks to see if the object provided is a Vector
* @param {Object} obj
*/
function isVector(obj) {
return (obj instanceof Vector_1.Vector);
}
exports.isVector = isVector;
/**
* Generates an array with values within a range. Multiplies by a step if provided
* @param {Number} start
* @param {Number} end
* @param {Number} step
*/
function range(start, end, step = 1) {
const arr = [];
step = step || 1;
for (let i = start; i <= end; i++) {
arr.push(i * step);
}
return arr;
}
exports.range = range;
/*
* Debugging method used to better visualize vector and arrays
* @param {object} o
* @returns {String}
*/
function pretty_print(o) {
if (Array.isArray(o)) {
let s = o.map(x => pretty_print(x)).join(', ');
if (o.type === 'vector') {
return 'vector<' + s + '>';
}
return '(' + s + ')';
}
return o.toString();
}
exports.pretty_print = pretty_print;
/**
* Checks to see if all arguments are numbers
* @param {Symbol[]} args
*/
function allNumbers(args) {
for (let i = 0; i < args.length; i++) {
if (args[i].group !== Groups_1.Groups.N) {
return false;
}
}
return true;
}
exports.allNumbers = allNumbers;
/*
* Checks if all arguments aren't just all number but if they
* are constants as well e.g. pi, e.
* @param {object} args
*/
function allConstants(args) {
for (let i = 0; i < args.length; i++) {
if (args[i].isPi() || args[i].isE()) {
continue;
}
if (!args[i].isConstant(true)) {
return false;
}
}
return true;
}
exports.allConstants = allConstants;
/**
* Checks to see if numbers are both negative or are both positive
* @param {Number} a
* @param {Number} b
* @returns {boolean}
*/
function sameSign(a, b) {
return (a < 0) === (b < 0);
}
exports.sameSign = sameSign;
/**
* A helper function to replace parts of string
* @param {String} str - The original string
* @param {Integer} from - The starting index
* @param {Integer} to - The ending index
* @param {String} with_str - The replacement string
* @returns {String} - A formatted string
*/
function stringReplace(str, from, to, with_str) {
return str.substr(0, from) + with_str + str.substr(to, str.length);
}
exports.stringReplace = stringReplace;
function text(obj, option, useGroup, decp) {
return (0, Text_1.text)(obj, option, useGroup, decp);
}
exports.text = text;
/**
* Returns an array of all the keys in an array
* @param {Object} obj
* @returns {Array}
*/
exports.keys = Object.keys;
/**
* This method traverses the symbol structure and grabs all the variables in a symbol. The variable
* names are then returned in alphabetical order.
* @param {Symbol} obj
* @param {Boolean} poly
* @param {Object} vars - An object containing the variables. Do not pass this in as it generated
* automatically. In the future this will be a Collector object.
* @returns {String[]} - An array containing variable names
*/
function variables(obj, poly = false, vars) {
if (!isSymbol(obj)) {
return vars ? vars.c.sort() : [];
}
return obj.variables(poly, vars);
}
exports.variables = variables;
//# sourceMappingURL=Utils.js.map