UNPKG

algebra.js

Version:

Build, display, and solve algebraic equations.

699 lines (554 loc) 20.8 kB
var Fraction = require('./fractions'); var isInt = require('./helper').isInt; var GREEK_LETTERS = require('./helper').GREEK_LETTERS; var Expression = function(variable) { this.constants = []; if(typeof(variable) === "string") { var v = new Variable(variable); var t = new Term(v); this.terms = [t]; } else if(isInt(variable)) { this.constants = [new Fraction(variable, 1)]; this.terms = []; } else if(variable instanceof Fraction) { this.constants = [variable]; this.terms = []; } else if(variable instanceof Term) { this.terms = [variable]; } else if(typeof(variable) === "undefined") { this.terms = []; }else{ throw new TypeError("Invalid Argument (" + variable.toString() + "): Argument must be of type String, Integer, Fraction or Term."); } }; Expression.prototype.constant = function() { return this.constants.reduce(function(p,c){return p.add(c);},new Fraction(0, 1)); }; Expression.prototype.simplify = function() { var copy = this.copy(); //simplify all terms copy.terms = copy.terms.map(function(t){return t.simplify();}); copy._sort(); copy._combineLikeTerms(); copy._moveTermsWithDegreeZeroToConstants(); copy._removeTermsWithCoefficientZero(); copy.constants = (copy.constant().valueOf() === 0 ? [] : [copy.constant()]); return copy; }; Expression.prototype.copy = function() { var copy = new Expression(); //copy all constants copy.constants = this.constants.map(function(c){return c.copy();}); //copy all terms copy.terms = this.terms.map(function(t){return t.copy();}); return copy; }; Expression.prototype.add = function(a, simplify) { var thisExp = this.copy(); if (typeof(a) === "string" || a instanceof Term || isInt(a) || a instanceof Fraction) { var exp = new Expression(a); return thisExp.add(exp, simplify); } else if (a instanceof Expression) { var keepTerms = a.copy().terms; thisExp.terms = thisExp.terms.concat(keepTerms); thisExp.constants = thisExp.constants.concat(a.constants); thisExp._sort(); } else { throw new TypeError("Invalid Argument (" + a.toString() + "): Summand must be of type String, Expression, Term, Fraction or Integer."); } return (simplify || simplify === undefined) ? thisExp.simplify() : thisExp; }; Expression.prototype.subtract = function(a, simplify) { var negative = (a instanceof Expression) ? a.multiply(-1) : new Expression(a).multiply(-1); return this.add(negative, simplify); }; Expression.prototype.multiply = function(a, simplify) { var thisExp = this.copy(); if (typeof(a) === "string" || a instanceof Term || isInt(a) || a instanceof Fraction) { var exp = new Expression(a); return thisExp.multiply(exp, simplify); } else if (a instanceof Expression) { var thatExp = a.copy(); var newTerms = []; for (var i = 0; i < thisExp.terms.length; i++) { var thisTerm = thisExp.terms[i]; for (var j = 0; j < thatExp.terms.length; j++) { var thatTerm = thatExp.terms[j]; newTerms.push(thisTerm.multiply(thatTerm, simplify)); } for (var j = 0; j < thatExp.constants.length; j++) { newTerms.push(thisTerm.multiply(thatExp.constants[j], simplify)); } } for (var i = 0; i < thatExp.terms.length; i++) { var thatTerm = thatExp.terms[i]; for (var j = 0; j < thisExp.constants.length; j++) { newTerms.push(thatTerm.multiply(thisExp.constants[j], simplify)); } } var newConstants = []; for (var i = 0; i < thisExp.constants.length; i++) { var thisConst = thisExp.constants[i]; for (var j = 0; j < thatExp.constants.length; j++) { var thatConst = thatExp.constants[j]; var t = new Term(); t = t.multiply(thatConst, false); t = t.multiply(thisConst, false); newTerms.push(t); } } thisExp.constants = newConstants; thisExp.terms = newTerms; thisExp._sort(); } else { throw new TypeError("Invalid Argument (" + a.toString() + "): Multiplicand must be of type String, Expression, Term, Fraction or Integer."); } return (simplify || simplify === undefined) ? thisExp.simplify() : thisExp; }; Expression.prototype.divide = function(a, simplify) { if (a instanceof Fraction || isInt(a)) { if (a.valueOf() === 0) { throw new EvalError("Divide By Zero"); } var copy = this.copy(); for (var i = 0; i < copy.terms.length; i++) { var thisTerm = copy.terms[i]; for (var j = 0; j < thisTerm.coefficients.length; j++) { thisTerm.coefficients[j] = thisTerm.coefficients[j].divide(a, simplify); } } //divide every constant by a copy.constants = copy.constants.map(function(c){return c.divide(a,simplify);}); return copy; } else { throw new TypeError("Invalid Argument (" + a.toString() + "): Divisor must be of type Fraction or Integer."); } }; Expression.prototype.pow = function(a, simplify) { if (isInt(a)) { var copy = this.copy(); if (a === 0) { return new Expression().add(1); } else { for (var i = 1; i < a; i++) { copy = copy.multiply(this, simplify); } copy._sort(); } return (simplify || simplify === undefined) ? copy.simplify() : copy; } else { throw new TypeError("Invalid Argument (" + a.toString() + "): Exponent must be of type Integer."); } }; Expression.prototype.eval = function(values, simplify) { var exp = new Expression(); exp.constants = (simplify ? [this.constant()] : this.constants.slice()); //add all evaluated terms of this to exp exp = this.terms.reduce(function(p,c){return p.add(c.eval(values,simplify),simplify);},exp); return exp; }; Expression.prototype.summation = function(variable, lower, upper, simplify) { var thisExpr = this.copy(); var newExpr = new Expression(); for(var i = lower; i < (upper + 1); i++) { var sub = {}; sub[variable] = i; newExpr = newExpr.add(thisExpr.eval(sub, simplify), simplify); } return newExpr; }; Expression.prototype.toString = function() { var str = ""; for (var i = 0; i < this.terms.length; i++) { var term = this.terms[i]; str += (term.coefficients[0].valueOf() < 0 ? " - " : " + ") + term.toString(); } for (var i = 0; i < this.constants.length; i++) { var constant = this.constants[i]; str += (constant.valueOf() < 0 ? " - " : " + ") + constant.abs().toString(); } if (str.substring(0, 3) === " - ") { return "-" + str.substring(3, str.length); } else if (str.substring(0, 3) === " + ") { return str.substring(3, str.length); } else { return "0"; } }; Expression.prototype.toTex = function(dict) { var str = ""; for (var i = 0; i < this.terms.length; i++) { var term = this.terms[i]; str += (term.coefficients[0].valueOf() < 0 ? " - " : " + ") + term.toTex(dict); } for (var i = 0; i < this.constants.length; i++) { var constant = this.constants[i]; str += (constant.valueOf() < 0 ? " - " : " + ") + constant.abs().toTex(); } if (str.substring(0, 3) === " - ") { return "-" + str.substring(3, str.length); } else if (str.substring(0, 3) === " + ") { return str.substring(3, str.length); } else { return "0"; } }; Expression.prototype._removeTermsWithCoefficientZero = function() { this.terms = this.terms.filter(function(t){return t.coefficient().reduce().numer !== 0;}); return this; }; Expression.prototype._combineLikeTerms = function() { function alreadyEncountered(term, encountered) { for (var i = 0; i < encountered.length; i++) { if (term.canBeCombinedWith(encountered[i])) { return true; } } return false; } var newTerms = []; var encountered = []; for (var i = 0; i < this.terms.length; i++) { var thisTerm = this.terms[i]; if (alreadyEncountered(thisTerm, encountered)) { continue; } else { for (var j = i + 1; j < this.terms.length; j++) { var thatTerm = this.terms[j]; if (thisTerm.canBeCombinedWith(thatTerm)) { thisTerm = thisTerm.add(thatTerm); } } newTerms.push(thisTerm); encountered.push(thisTerm); } } this.terms = newTerms; return this; }; Expression.prototype._moveTermsWithDegreeZeroToConstants = function() { var keepTerms = []; var constant = new Fraction(0, 1); for (var i = 0; i < this.terms.length; i++) { var thisTerm = this.terms[i]; if (thisTerm.variables.length === 0) { constant = constant.add(thisTerm.coefficient()); } else { keepTerms.push(thisTerm); } } this.constants.push(constant); this.terms = keepTerms; return this; }; Expression.prototype._sort = function() { function sortTerms(a, b) { var x = a.maxDegree(); var y = b.maxDegree(); if (x === y) { var m = a.variables.length; var n = b.variables.length; return n - m; } else { return y - x; } } this.terms = this.terms.sort(sortTerms); return this; }; Expression.prototype._hasVariable = function(variable) { for (var i = 0; i < this.terms.length; i++) { if (this.terms[i].hasVariable(variable)) { return true; } } return false; }; Expression.prototype._onlyHasVariable = function(variable) { for (var i = 0; i < this.terms.length; i++) { if (!this.terms[i].onlyHasVariable(variable)) { return false; } } return true; }; Expression.prototype._noCrossProductsWithVariable = function(variable) { for (var i = 0; i < this.terms.length; i++) { var term = this.terms[i]; if (term.hasVariable(variable) && !term.onlyHasVariable(variable)) { return false; } } return true; }; Expression.prototype._noCrossProducts = function() { for (var i = 0; i < this.terms.length; i++) { var term = this.terms[i]; if (term.variables.length > 1) { return false; } } return true; }; Expression.prototype._maxDegree = function() { return this.terms.reduce(function(p,c){return Math.max(p,c.maxDegree());},1); }; Expression.prototype._maxDegreeOfVariable = function(variable) { return this.terms.reduce(function(p,c){return Math.max(p,c.maxDegreeOfVariable(variable));},1); }; Expression.prototype._quadraticCoefficients = function() { // This function isn't used until everything has been moved to the LHS in Equation.solve. var a; var b = new Fraction(0, 1); for (var i = 0; i < this.terms.length; i++) { var thisTerm = this.terms[i]; a = (thisTerm.maxDegree() === 2) ? thisTerm.coefficient().copy() : a; b = (thisTerm.maxDegree() === 1) ? thisTerm.coefficient().copy() : b; } var c = this.constant(); return {a:a, b:b, c:c}; }; Expression.prototype._cubicCoefficients = function() { // This function isn't used until everything has been moved to the LHS in Equation.solve. var a; var b = new Fraction(0, 1); var c = new Fraction(0, 1); for (var i = 0; i < this.terms.length; i++) { var thisTerm = this.terms[i]; a = (thisTerm.maxDegree() === 3) ? thisTerm.coefficient().copy() : a; b = (thisTerm.maxDegree() === 2) ? thisTerm.coefficient().copy() : b; c = (thisTerm.maxDegree() === 1) ? thisTerm.coefficient().copy() : c; } var d = this.constant(); return {a:a, b:b, c:c, d:d}; }; Term = function(variable) { if (variable instanceof Variable) { this.variables = [variable.copy()]; } else if (typeof(variable) === "undefined") { this.variables = []; } else { throw new TypeError("Invalid Argument (" + variable.toString() + "): Term initializer must be of type Variable."); } this.coefficients = [new Fraction(1, 1)]; }; Term.prototype.coefficient = function() { //calculate the product of all coefficients return this.coefficients.reduce(function(p,c){return p.multiply(c);}, new Fraction(1,1)); }; Term.prototype.simplify = function() { var copy = this.copy(); copy.coefficients = [this.coefficient()]; copy.combineVars(); return copy.sort(); }; Term.prototype.combineVars = function() { var uniqueVars = {}; for (var i = 0; i < this.variables.length; i++) { var thisVar = this.variables[i]; if (thisVar.variable in uniqueVars) { uniqueVars[thisVar.variable] += thisVar.degree; } else { uniqueVars[thisVar.variable] = thisVar.degree; } } var newVars = []; for (var v in uniqueVars) { var newVar = new Variable(v); newVar.degree = uniqueVars[v]; newVars.push(newVar); } this.variables = newVars; return this; }; Term.prototype.copy = function() { var copy = new Term(); copy.coefficients = this.coefficients.map(function(c){return c.copy();}); copy.variables = this.variables.map(function(v){return v.copy();}); return copy; }; Term.prototype.add = function(term) { if(term instanceof Term && this.canBeCombinedWith(term)) { var copy = this.copy(); copy.coefficients = [copy.coefficient().add(term.coefficient())]; return copy; } else { throw new TypeError("Invalid Argument (" + term.toString() + "): Summand must be of type String, Expression, Term, Fraction or Integer."); } }; Term.prototype.subtract = function(term) { if (term instanceof Term && this.canBeCombinedWith(term)) { var copy = this.copy(); copy.coefficients = [copy.coefficient().subtract(term.coefficient())]; return copy; } else { throw new TypeError("Invalid Argument (" + term.toString() + "): Subtrahend must be of type String, Expression, Term, Fraction or Integer."); } }; Term.prototype.multiply = function(a, simplify) { var thisTerm = this.copy(); if (a instanceof Term) { thisTerm.variables = thisTerm.variables.concat(a.variables); thisTerm.coefficients = a.coefficients.concat(thisTerm.coefficients); } else if (isInt(a) || a instanceof Fraction) { var newCoef = (isInt(a) ? new Fraction(a, 1) : a); if (thisTerm.variables.length === 0) { thisTerm.coefficients.push(newCoef); } else { thisTerm.coefficients.unshift(newCoef); } } else { throw new TypeError("Invalid Argument (" + a.toString() + "): Multiplicand must be of type String, Expression, Term, Fraction or Integer."); } return (simplify || simplify === undefined) ? thisTerm.simplify() : thisTerm; }; Term.prototype.divide = function(a, simplify) { if(isInt(a) || a instanceof Fraction) { var thisTerm = this.copy(); thisTerm.coefficients = thisTerm.coefficients.map(function(c){return c.divide(a,simplify);}); return thisTerm; } else { throw new TypeError("Invalid Argument (" + a.toString() + "): Argument must be of type Fraction or Integer."); } }; Term.prototype.eval = function(values, simplify) { var copy = this.copy(); var keys = Object.keys(values); var exp = copy.coefficients.reduce(function(p,c){return p.multiply(c,simplify);}, new Expression(1)); for(var i = 0; i < copy.variables.length; i++) { var thisVar = copy.variables[i]; var ev; if (thisVar.variable in values) { var sub = values[thisVar.variable]; if(sub instanceof Fraction || sub instanceof Expression) { ev = sub.pow(thisVar.degree); } else if(isInt(sub)) { ev = Math.pow(sub, thisVar.degree); } else { throw new TypeError("Invalid Argument (" + sub + "): Can only evaluate Expressions or Fractions."); } } else { ev = new Expression(thisVar.variable).pow(thisVar.degree); } exp = exp.multiply(ev, simplify); } return exp; }; Term.prototype.hasVariable = function(variable) { for (var i = 0; i < this.variables.length; i++) { if (this.variables[i].variable === variable) { return true; } } return false; }; Term.prototype.maxDegree = function() { return this.variables.reduce(function(p,c){return Math.max(p,c.degree);},1); }; Term.prototype.maxDegreeOfVariable = function(variable) { return this.variables.reduce(function(p,c){return (c.variable === variable) ? Math.max(p,c.degree) : p;},1); }; Term.prototype.canBeCombinedWith = function(term) { var thisVars = this.variables; var thatVars = term.variables; if(thisVars.length != thatVars.length) { return false; } var matches = 0; for(var i = 0; i < thisVars.length; i++) { for(var j = 0; j < thatVars.length; j++) { if(thisVars[i].variable === thatVars[j].variable && thisVars[i].degree === thatVars[j].degree) { matches += 1; } } } return (matches === thisVars.length); }; Term.prototype.onlyHasVariable = function(variable) { for (var i = 0; i < this.variables.length; i++) { if (this.variables[i].variable != variable) { return false; } } return true; }; Term.prototype.sort = function() { function sortVars(a, b) { return b.degree - a.degree; } this.variables = this.variables.sort(sortVars); return this; }; Term.prototype.toString = function() { var str = ""; for (var i = 0; i < this.coefficients.length; i++) { var coef = this.coefficients[i]; if (coef.abs().numer !== 1 || coef.abs().denom !== 1) { str += " * " + coef.toString(); } } str = this.variables.reduce(function(p,c){return p.concat(c.toString());},str); str = (str.substring(0, 3) === " * " ? str.substring(3, str.length) : str); str = (str.substring(0, 1) === "-" ? str.substring(1, str.length) : str); return str; }; Term.prototype.toTex = function(dict) { var dict = (dict === undefined) ? {} : dict; dict.multiplication = !("multiplication" in dict) ? "cdot" : dict.multiplication; var op = " \\" + dict.multiplication + " "; var str = ""; for (var i = 0; i < this.coefficients.length; i++) { var coef = this.coefficients[i]; if (coef.abs().numer !== 1 || coef.abs().denom !== 1) { str += op + coef.toTex(); } } str = this.variables.reduce(function(p,c){return p.concat(c.toTex());},str); str = (str.substring(0, op.length) === op ? str.substring(op.length, str.length) : str); str = (str.substring(0, 1) === "-" ? str.substring(1, str.length) : str); str = (str.substring(0, 7) === "\\frac{-" ? "\\frac{" + str.substring(7, str.length) : str); return str; }; var Variable = function(variable) { if (typeof(variable) === "string") { this.variable = variable; this.degree = 1; } else { throw new TypeError("Invalid Argument (" + variable.toString() + "): Variable initalizer must be of type String."); } }; Variable.prototype.copy = function() { var copy = new Variable(this.variable); copy.degree = this.degree; return copy; }; Variable.prototype.toString = function() { var degree = this.degree; var variable = this.variable; if (degree === 0) { return ""; } else if (degree === 1) { return variable; } else { return variable + "^" + degree; } }; Variable.prototype.toTex = function() { var degree = this.degree; var variable = this.variable; if (GREEK_LETTERS.indexOf(variable) > -1) { variable = "\\" + variable; } if (degree === 0) { return ""; } else if (degree === 1) { return variable; } else { return variable + "^{" + degree + "}"; } }; module.exports = { Expression: Expression, Term: Term, Variable: Variable };