UNPKG

nerdamer-ts

Version:

javascript light-weight symbolic math expression evaluator

647 lines 19.8 kB
"use strict"; 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