From 91f21f3a56210ea1f5ca04695fc1dd798fd306b1 Mon Sep 17 00:00:00 2001 From: bebbe Date: Thu, 13 Apr 2017 09:35:42 +0200 Subject: [PATCH 01/40] Added support for multiplication of powers with integer as base, with corresponding tests. --- lib/TreeSearch.js | 4 +- lib/checks/canAddLikeTermPolynomialNodes.js | 1 - .../canMultiplyLikeTermPolynomialNodes.js | 1 - lib/checks/index.js | 2 + lib/node/Creator.js | 7 ++ lib/node/PolynomialTerm.js | 2 + lib/node/index.js | 2 + .../arithmeticSearch/index.js | 3 - .../multiplyLikeTerms.js | 109 +++++++++++------- lib/simplifyExpression/stepThrough.js | 5 +- ...canMultiplyLikeTermPolynomialNodes.test.js | 3 +- .../collectAndCombineSearch.test.js | 8 ++ test/simplifyExpression/simplify.test.js | 8 ++ 13 files changed, 102 insertions(+), 53 deletions(-) diff --git a/lib/TreeSearch.js b/lib/TreeSearch.js index ea5c7b18..2bb3d174 100644 --- a/lib/TreeSearch.js +++ b/lib/TreeSearch.js @@ -14,21 +14,19 @@ TreeSearch.preOrder = function(simplificationFunction) { // simplifcation function TreeSearch.postOrder = function(simplificationFunction) { return function (node) { - return search(simplificationFunction, node, false); + return search(simplificationFunction, node, false); }; }; // A helper function for performing a tree search with a function function search(simplificationFunction, node, preOrder) { let status; - if (preOrder) { status = simplificationFunction(node); if (status.hasChanged()) { return status; } } - if (Node.Type.isConstant(node) || Node.Type.isSymbol(node)) { return Node.Status.noChange(node); } diff --git a/lib/checks/canAddLikeTermPolynomialNodes.js b/lib/checks/canAddLikeTermPolynomialNodes.js index 6c9ac1ca..c5e87d0d 100644 --- a/lib/checks/canAddLikeTermPolynomialNodes.js +++ b/lib/checks/canAddLikeTermPolynomialNodes.js @@ -19,7 +19,6 @@ function canAddLikeTermPolynomialNodes(node) { const firstTerm = polynomialTermList[0]; const sharedSymbol = firstTerm.getSymbolName(); const sharedExponentNode = firstTerm.getExponentNode(true); - const restTerms = polynomialTermList.slice(1); return restTerms.every(term => { const haveSameSymbol = sharedSymbol === term.getSymbolName(); diff --git a/lib/checks/canMultiplyLikeTermPolynomialNodes.js b/lib/checks/canMultiplyLikeTermPolynomialNodes.js index 0654ae82..60c3ffda 100644 --- a/lib/checks/canMultiplyLikeTermPolynomialNodes.js +++ b/lib/checks/canMultiplyLikeTermPolynomialNodes.js @@ -13,7 +13,6 @@ function canMultiplyLikeTermPolynomialNodes(node) { if (args.length === 1) { return false; } - const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); if (!polynomialTermList.every(polyTerm => !polyTerm.hasCoeff())) { return false; diff --git a/lib/checks/index.js b/lib/checks/index.js index 5bb07252..ae55feb3 100644 --- a/lib/checks/index.js +++ b/lib/checks/index.js @@ -5,10 +5,12 @@ const canSimplifyPolynomialTerms = require('./canSimplifyPolynomialTerms'); const hasUnsupportedNodes = require('./hasUnsupportedNodes'); const isQuadratic = require('./isQuadratic'); const resolvesToConstant = require('./resolvesToConstant'); +const canMultiplyLikeTermConstantNodes = require('./canMultiplyLikeTermConstantNodes'); module.exports = { canAddLikeTermPolynomialNodes, canMultiplyLikeTermPolynomialNodes, + canMultiplyLikeTermConstantNodes, canRearrangeCoefficient, canSimplifyPolynomialTerms, hasUnsupportedNodes, diff --git a/lib/node/Creator.js b/lib/node/Creator.js index 165a6863..b5b603df 100644 --- a/lib/node/Creator.js +++ b/lib/node/Creator.js @@ -71,6 +71,13 @@ const NodeCreator = { nthRoot (radicandNode, rootNode) { const symbol = NodeCreator.symbol('nthRoot'); return new math.expression.node.FunctionNode(symbol, [radicandNode, rootNode]); + }, + constantTerm (base, exponent) { + let constTerm = base; + if (exponent) { + constTerm = this.operator('^', [constTerm, exponent]); + } + return constTerm; } }; diff --git a/lib/node/PolynomialTerm.js b/lib/node/PolynomialTerm.js index e5aa8919..4983bfcc 100644 --- a/lib/node/PolynomialTerm.js +++ b/lib/node/PolynomialTerm.js @@ -36,6 +36,7 @@ class PolynomialTerm { if (node.args.length !== 2) { throw Error('Expected two arguments to *'); } + const coeffNode = node.args[0]; if (!NodeType.isConstantOrConstantFraction(coeffNode)) { throw Error('Expected coefficient to be constant or fraction of ' + @@ -49,6 +50,7 @@ class PolynomialTerm { ' and ' + nonCoefficientTerm.getCoeffNode()); } this.symbol = nonCoefficientTerm.getSymbolNode(); + this.exponent = nonCoefficientTerm.getExponentNode(); } // this means there's a fraction coefficient diff --git a/lib/node/index.js b/lib/node/index.js index c40c0797..2ee809cd 100644 --- a/lib/node/index.js +++ b/lib/node/index.js @@ -2,10 +2,12 @@ const Creator = require('./Creator'); const PolynomialTerm = require('./PolynomialTerm'); const Status = require('./Status'); const Type = require('./Type'); +const ConstantTerms = require('./ConstantTerms'); module.exports = { Creator, PolynomialTerm, + ConstantTerms, Status, Type, }; diff --git a/lib/simplifyExpression/arithmeticSearch/index.js b/lib/simplifyExpression/arithmeticSearch/index.js index b84c7f8f..1339618f 100644 --- a/lib/simplifyExpression/arithmeticSearch/index.js +++ b/lib/simplifyExpression/arithmeticSearch/index.js @@ -2,7 +2,6 @@ const ChangeTypes = require('../../ChangeTypes'); const evaluate = require('../../util/evaluate'); const Node = require('../../node'); const TreeSearch = require('../../TreeSearch'); - // Searches through the tree, prioritizing deeper nodes, and evaluates // arithmetic (e.g. 2+2 or 3*5*2) on an operation node if possible. // Returns a Node.Status object. @@ -17,13 +16,11 @@ function arithmetic(node) { if (!node.args.every(child => Node.Type.isConstant(child, true))) { return Node.Status.noChange(node); } - // we want to eval each arg so unary minuses around constant nodes become // constant nodes with negative values node.args.forEach((arg, i) => { node.args[i] = Node.Creator.constant(evaluate(arg)); }); - // Only resolve division of integers if we get an integer result. // Note that a fraction of decimals will be divided out. if (Node.Type.isIntegerFraction(node)) { diff --git a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js index 079d3b17..97dcbb9b 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js @@ -5,23 +5,20 @@ const multiplyFractionsSearch = require('../multiplyFractionsSearch'); const ChangeTypes = require('../../ChangeTypes'); const Node = require('../../node'); - // Multiplies a list of nodes that are polynomial like terms. Returns a node. // The polynomial nodes should *not* have coefficients. (multiplying // coefficients is handled in collecting like terms for multiplication) -function multiplyLikeTerms(node, polynomialOnly=false) { +function multiplyLikeTerms(node, polynomialOnly = false) { if (!Node.Type.isOperator(node)) { return Node.Status.noChange(node); } let status; - - if (!polynomialOnly) { + if (!polynomialOnly && !checks.canMultiplyLikeTermConstantNodes(node)) { status = arithmeticSearch(node); if (status.hasChanged()) { status.changeType = ChangeTypes.MULTIPLY_COEFFICIENTS; return status; } - status = multiplyFractionsSearch(node); if (status.hasChanged()) { status.changeType = ChangeTypes.MULTIPLY_COEFFICIENTS; @@ -29,20 +26,17 @@ function multiplyLikeTerms(node, polynomialOnly=false) { } } - status = multiplyPolynomialTerms(node); + status = multiplyPolynomialTerms(node) if (status.hasChanged()) { status.changeType = ChangeTypes.MULTIPLY_COEFFICIENTS; return status; } - return Node.Status.noChange(node); } - function multiplyPolynomialTerms(node) { - if (!checks.canMultiplyLikeTermPolynomialNodes(node)) { + if (!checks.canMultiplyLikeTermPolynomialNodes(node) && !checks.canMultiplyLikeTermConstantNodes(node)) { return Node.Status.noChange(node); } - const substeps = []; let newNode = clone(node); @@ -93,51 +87,82 @@ function multiplyPolynomialTerms(node) { function addOneExponent(node) { const newNode = clone(node); let change = false; - let changeGroup = 1; - newNode.args.forEach((child, i) => { - const polyTerm = new Node.PolynomialTerm(child); - if (!polyTerm.getExponentNode()) { - newNode.args[i] = Node.Creator.polynomialTerm( - polyTerm.getSymbolNode(), - Node.Creator.constant(1), - polyTerm.getCoeffNode()); - - newNode.args[i].changeGroup = changeGroup; - node.args[i].changeGroup = changeGroup; // note that this is the "oldNode" - - change = true; - changeGroup++; - } - }); + if (checks.canMultiplyLikeTermConstantNodes(node)) { + newNode.args.forEach((child, i) => { + const constTerm = new Node.ConstantTerms(child); + if (!constTerm.getExponentNode()) { + newNode.args[i] = Node.Creator.constantTerm( + constTerm.getBaseNode(), + Node.Creator.constant(1), + constTerm.getCoeffNode()); + + newNode.args[i].changeGroup = changeGroup; + node.args[i].changeGroup = changeGroup; // note that this is the "oldNode" + + change = true; + changeGroup++; + } + }); + } + else { + newNode.args.forEach((child, i) => { + const polyTerm = new Node.PolynomialTerm(child); + if (!polyTerm.getExponentNode()) { + newNode.args[i] = Node.Creator.polynomialTerm( + polyTerm.getSymbolNode(), + Node.Creator.constant(1), + polyTerm.getCoeffNode()); + + newNode.args[i].changeGroup = changeGroup; + node.args[i].changeGroup = changeGroup; // note that this is the "oldNode" + + change = true; + changeGroup++; + } + }); + } if (change) { return Node.Status.nodeChanged( - ChangeTypes.ADD_EXPONENT_OF_ONE, node, newNode, false); + ChangeTypes.ADD_EXPONENT_OF_ONE, node, newNode, false); } else { return Node.Status.noChange(node); } } - // Given a product of polynomial terms, groups the exponents into a sum // e.g. x^2 * x^3 * x^1 -> x^(2 + 3 + 1) // Returns a Node.Status object. function collectExponents(node) { - const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); - - // If we're multiplying polynomial nodes together, they all share the same - // symbol. Get that from the first node. - const symbolNode = polynomialTermList[0].getSymbolNode(); - - // The new exponent will be a sum of exponents (an operation, wrapped in - // parens) e.g. x^(3+4+5) - const exponentNodeList = polynomialTermList.map(p => p.getExponentNode(true)); - const newExponent = Node.Creator.parenthesis( - Node.Creator.operator('+', exponentNodeList)); - const newNode = Node.Creator.polynomialTerm(symbolNode, newExponent, null); - return Node.Status.nodeChanged( - ChangeTypes.COLLECT_EXPONENTS, node, newNode); + if (checks.canMultiplyLikeTermConstantNodes(node)) { + const constantTermList = node.args.map(n => new Node.ConstantTerms(n)); + // If we're multiplying constant nodes together, they all share the same + // base. Get that from the first node. + const baseNode = constantTermList[0].getBaseNode(); + // The new exponent will be a sum of exponents (an operation, wrapped in + // parens) e.g. 10^(3+4+5) + const exponentNodeList = constantTermList.map(p => p.getExponentNode(true)); + const newExponent = Node.Creator.parenthesis( + Node.Creator.operator('+', exponentNodeList)); + const newNode = Node.Creator.constantTerm(baseNode, newExponent, null); + return Node.Status.nodeChanged( + ChangeTypes.COLLECT_EXPONENTS, node, newNode); + } + else { + const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); + // If we're multiplying polynomial nodes together, they all share the same + // symbol. Get that from the first node. + const symbolNode = polynomialTermList[0].getSymbolNode(); + // The new exponent will be a sum of exponents (an operation, wrapped in + // parens) e.g. x^(3+4+5) + const exponentNodeList = polynomialTermList.map(p => p.getExponentNode(true)); + const newExponent = Node.Creator.parenthesis( + Node.Creator.operator('+', exponentNodeList)); + const newNode = Node.Creator.polynomialTerm(symbolNode, newExponent, null); + return Node.Status.nodeChanged( + ChangeTypes.COLLECT_EXPONENTS, node, newNode); + } } module.exports = multiplyLikeTerms; diff --git a/lib/simplifyExpression/stepThrough.js b/lib/simplifyExpression/stepThrough.js index 9a5dad53..5f2e04c7 100644 --- a/lib/simplifyExpression/stepThrough.js +++ b/lib/simplifyExpression/stepThrough.js @@ -72,11 +72,12 @@ function step(node) { divisionSearch, // Adding fractions, cancelling out things in fractions fractionsSearch, - // e.g. 2 + 2 => 4 - arithmeticSearch, // e.g. addition: 2x + 4x^2 + x => 4x^2 + 3x // e.g. multiplication: 2x * x * x^2 => 2x^3 + // e.g multiplication: 10^3 * 10^2 => 10^5 collectAndCombineSearch, + // e.g. 2 + 2 => 4 + arithmeticSearch, // e.g. (2 + x) / 4 => 2/4 + x/4 breakUpNumeratorSearch, // e.g. 3/x * 2x/5 => (3 * 2x) / (x * 5) diff --git a/test/canMultiplyLikeTermPolynomialNodes.test.js b/test/canMultiplyLikeTermPolynomialNodes.test.js index d176dc4d..0b2037c2 100644 --- a/test/canMultiplyLikeTermPolynomialNodes.test.js +++ b/test/canMultiplyLikeTermPolynomialNodes.test.js @@ -10,7 +10,8 @@ describe('can multiply like term polynomials', () => { const tests = [ ['x^2 * x * x', true], ['x^2 * 3x * x', false], - ['y * y^3', true] + ['y * y^3', true], + ['x^3 * x^2', true] ]; tests.forEach(t => testCanBeMultiplied(t[0], t[1])); }); diff --git a/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js b/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js index b2335179..272e87c0 100644 --- a/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js +++ b/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js @@ -60,3 +60,11 @@ describe('collectAndCombineSearch with no substeps', function () { ]; tests.forEach(t => testSimpleCollectAndCombineSearch(t[0], t[1])); }); + +describe('collect and multiply like terms', function() { + const tests = [ + ['10^3 * 10^2', '10^5'], + ['2^4 * 2 * 2^4', '2^9'] + ]; + tests.forEach(t => testSimpleCollectAndCombineSearch(t[0], t[1])); +}); diff --git a/test/simplifyExpression/simplify.test.js b/test/simplifyExpression/simplify.test.js index 1cefba3b..736cf00e 100644 --- a/test/simplifyExpression/simplify.test.js +++ b/test/simplifyExpression/simplify.test.js @@ -163,6 +163,14 @@ describe('handles unnecessary parens at root level', function() { tests.forEach(t => testSimplify(t[0], t[1], t[2])); }); +describe('multiplication of powers support', function(){ + const tests = [ + ['10^3 * 10^2', '100000'], + ['2^3*2^4', '128'], + ]; + tests.forEach(t => testSimplify(t[0], t[1], t[2])); +}); + describe('keeping parens in important places, on printing', function() { testSimplify('2 / (2x^2) + 5', '2 / (2x^2) + 5'); }); From 14cf7592cfea8e9af5f7740e4abe2633972f20a8 Mon Sep 17 00:00:00 2001 From: bebbe Date: Thu, 13 Apr 2017 09:38:29 +0200 Subject: [PATCH 02/40] Added support for multiplication of powers with integer as base, with corresponding tests. --- .idea/codeStyleSettings.xml | 9 + .idea/inspectionProfiles/Project_Default.xml | 6 + .idea/mathsteps.iml | 12 + .idea/misc.xml | 6 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + .idea/watcherTasks.xml | 4 + .idea/workspace.xml | 816 ++++++++++++++++++ .npmignore | 4 + .../canMultiplyLikeTermConstantNodes.js | 30 + lib/node/ConstantTerms.js | 130 +++ test-mathsteps.js | 13 + test/canMultiplyLikeTermConstantNodes.test.js | 19 + 13 files changed, 1063 insertions(+) create mode 100644 .idea/codeStyleSettings.xml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/mathsteps.iml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 .idea/watcherTasks.xml create mode 100644 .idea/workspace.xml create mode 100644 .npmignore create mode 100644 lib/checks/canMultiplyLikeTermConstantNodes.js create mode 100644 lib/node/ConstantTerms.js create mode 100644 test-mathsteps.js create mode 100644 test/canMultiplyLikeTermConstantNodes.test.js diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml new file mode 100644 index 00000000..5555dd26 --- /dev/null +++ b/.idea/codeStyleSettings.xml @@ -0,0 +1,9 @@ + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..c6cc8c81 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/mathsteps.iml b/.idea/mathsteps.iml new file mode 100644 index 00000000..24643cc3 --- /dev/null +++ b/.idea/mathsteps.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..28a804d8 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..5976885e --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 00000000..94a25f7f --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/watcherTasks.xml b/.idea/watcherTasks.xml new file mode 100644 index 00000000..9338ba68 --- /dev/null +++ b/.idea/watcherTasks.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml new file mode 100644 index 00000000..d29187c1 --- /dev/null +++ b/.idea/workspace.xml @@ -0,0 +1,816 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + console.log + console. + math + math er + math error + math erro + re + return + returning + returning no + testArithmeticSearch + testSimplification + node.to + getBaseValue + isPolynomialTerm + every + hejhej + console + ar + arig + arith + arithmet + arithmeti + arithmeticA + arithmetic + arithmeticS + arithmeticSea + arithmeticSear + arithmeticSearc + arithmeticSearch + + + + + + + + + + + + + + true + DEFINITION_ORDER + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + project + + + + + + + + + + + + + + + + project + + + true + + + + DIRECTORY + + false + + + + + + + + + + + + + + + + + + + + + + + 1491376618838 + + + 1492068942886 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.npmignore b/.npmignore new file mode 100644 index 00000000..f58d10d0 --- /dev/null +++ b/.npmignore @@ -0,0 +1,4 @@ +*swp +node_modules +*.log +.DS_Store diff --git a/lib/checks/canMultiplyLikeTermConstantNodes.js b/lib/checks/canMultiplyLikeTermConstantNodes.js new file mode 100644 index 00000000..2a23ca47 --- /dev/null +++ b/lib/checks/canMultiplyLikeTermConstantNodes.js @@ -0,0 +1,30 @@ +const Node = require('../node'); + + +// Returns true if the nodes are constant terms with the same constant and has an exponent each + +function canMultiplyLikeTermConstantNodes(node) { + if (!Node.Type.isOperator(node) || node.op !== '*' || node.args[1].op === '/') { + return false; + } + const args = node.args; + if (args.some(n=> Node.PolynomialTerm.isPolynomialTerm(n))) { + return false; + } + if (!args.every(n => Node.ConstantTerms.isConstantTerm(n))) { + return false; + } + if (args.length === 1) { + return false; + } + const constantTermList = node.args.map(n => new Node.ConstantTerms(n)); + if (!constantTermList.every(constTerm => !constTerm.hasCoeff())) { + return false; + } + const firstTerm = constantTermList[0]; + const restTerms = constantTermList.slice(1); + // they're considered like terms if they have the same symbol name + return restTerms.every(term => firstTerm.getBaseValue() === term.getBaseValue()); +} + +module.exports = canMultiplyLikeTermConstantNodes; diff --git a/lib/node/ConstantTerms.js b/lib/node/ConstantTerms.js new file mode 100644 index 00000000..f7a3c6d3 --- /dev/null +++ b/lib/node/ConstantTerms.js @@ -0,0 +1,130 @@ +const NodeCreator = require('./Creator'); +const NodeType = require('./Type'); + +class ConstantTerms { + constructor(node, onlyImplicitMultiplication=false) { + if (NodeType.isOperator(node)) { + if (node.op === '^') { + const constantNode = node.args[0]; + if (!NodeType.isConstant(constantNode)) { + //throw Error('Expected constant term, got ' + constantNode); + return false; + } + this.base = constantNode; + this.exponent = node.args[1]; + } + else if (node.op === '*'){ + if (onlyImplicitMultiplication && !node.implicit) { + throw Error('Expected implicit multiplication'); + } + if (node.args.length !== 2) { + throw Error('Expected two arguments to *'); + } + const coeffNode = node.args[0]; + if (!NodeType.isConstantOrConstantFraction(coeffNode)) { + throw Error('Expected coefficient to be constant or fraction of ' + + 'constants term, got ' + coeffNode); + } + this.coeff = coeffNode; + const nonCoefficientTerm = new ConstantTerms( + node.args[1], onlyImplicitMultiplication); + if (nonCoefficientTerm.hasCoeff()) { + throw Error('Cannot have two coefficients ' + coeffNode + + ' and ' + nonCoefficientTerm.getCoeffNode()); + } + this.base = nonCoefficientTerm.getBaseNode(); + this.exponent = nonCoefficientTerm.getExponentNode(); + } + else if (node.op === '/') { + const denominatorNode = node.args[1]; + if (!NodeType.isConstant(denominatorNode)) { + //throw Error('denominator must be constant node, instead of ' + + //denominatorNode); + return false; + } + const numeratorNode = new ConstantTerms( + node.args[0]); + this.exponent = numeratorNode.getExponentNode(); + this.base = numeratorNode.getBaseNode(); + } + } + else if (NodeType.isUnaryMinus(node)) { + var arg = node.args[0]; + if (NodeType.isParenthesis(arg)) { + arg = arg.content; + } + const constNode = new ConstantTerms( + arg, onlyImplicitMultiplication); + this.exponent = constNode.getExponentNode(); + this.base = constNode.getBaseNode(); + if (!constNode.hasCoeff()) { + this.coeff = NodeCreator.constant(-1); + } + else { + this.coeff = negativeCoefficient(constNode.getCoeffNode()); + } + } + else if (NodeType.isConstant(node)) { + this.base = node; + } + else { + throw Error('Unsupported node type: ' + node.type); + } + } + + + /* GETTER FUNCTIONS */ + getBaseNode() { + return this.base; + } + getBaseValue() { + return this.base.value; + } + + getExponentNode(defaultOne = false) { + if (!this.exponent && defaultOne) { + return NodeCreator.constant(1); + } + else { + return this.exponent; + } + } + getCoeffNode(defaultOne=false) { + if (!this.coeff && defaultOne) { + return NodeCreator.constant(1); + } + else { + return this.coeff; + } + } + hasCoeff() { + return !!this.coeff; + } +} +// Returns if the node represents an expression that can be considered a term. +// e.g. 2^2, 10^4, 6^2 are all terms. 4, 2+x, 3*7, x-z are all not terms. +// See the tests for some more thorough examples of exactly what counts and +// what does not. +ConstantTerms.isConstantTerm = function (node, onlyImplicitMultiplication = false) { + try { + // will throw error if node isn't poly term + new ConstantTerms(node, onlyImplicitMultiplication); + return true; + } + catch (err) { + return false; + } +}; +function negativeCoefficient(node) { + if (NodeType.isConstant(node)) { + node = NodeCreator.constant(0 - parseFloat(node.value)); + } + else { + const numeratorValue = 0 - parseFloat(node.args[0].value); + node.args[0] = NodeCreator.constant(numeratorValue); + } + return node; +} + + +module.exports = ConstantTerms; \ No newline at end of file diff --git a/test-mathsteps.js b/test-mathsteps.js new file mode 100644 index 00000000..9d40fb35 --- /dev/null +++ b/test-mathsteps.js @@ -0,0 +1,13 @@ +/** + * Created by bebbe on 2017-04-05. + */ +const mathsteps = require('mathsteps'); + +const steps = mathsteps.simplifyExpression('2x + 2x + x + x'); + +steps.forEach(step => { + console.log('before change: ' + step.oldNode); // before change: 2 x + 2 x + x + x + console.log('change: ' + step.changeType); // change: ADD_POLYNOMIAL_TERMS + console.log('after change: ' + step.newNode); // after change: 6 x + console.log('# of substeps: ' + step.substeps.length); // # of substeps: 3 +}); \ No newline at end of file diff --git a/test/canMultiplyLikeTermConstantNodes.test.js b/test/canMultiplyLikeTermConstantNodes.test.js new file mode 100644 index 00000000..fc7e2d38 --- /dev/null +++ b/test/canMultiplyLikeTermConstantNodes.test.js @@ -0,0 +1,19 @@ +const canMultiplyLikeTermConstantNodes = require('../lib/checks/canMultiplyLikeTermConstantNodes'); +const multiplyLikeTerms = require('../lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms') + + +const TestUtil = require('./TestUtil'); + +function testCanBeMultipliedConstants(expr, multipliable) { + TestUtil.testBooleanFunction(canMultiplyLikeTermConstantNodes, expr, multipliable); +} + +describe('can multiply like term constants', () => { + const tests = [ + ['3^2 * 3^5', true], + ['2^3 * 3^2', false], + ['10^3 * 10^2', true], + ['10^2 * 10 * 10^4', true] + ]; + tests.forEach(t => testCanBeMultipliedConstants(t[0], t[1])); +}); From 5c1f306eff6db29d07a931410b67b1ffb385c21e Mon Sep 17 00:00:00 2001 From: Sebastian Liljevall Date: Thu, 13 Apr 2017 11:06:26 +0200 Subject: [PATCH 03/40] Delete test-mathsteps.js --- test-mathsteps.js | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 test-mathsteps.js diff --git a/test-mathsteps.js b/test-mathsteps.js deleted file mode 100644 index 9d40fb35..00000000 --- a/test-mathsteps.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Created by bebbe on 2017-04-05. - */ -const mathsteps = require('mathsteps'); - -const steps = mathsteps.simplifyExpression('2x + 2x + x + x'); - -steps.forEach(step => { - console.log('before change: ' + step.oldNode); // before change: 2 x + 2 x + x + x - console.log('change: ' + step.changeType); // change: ADD_POLYNOMIAL_TERMS - console.log('after change: ' + step.newNode); // after change: 6 x - console.log('# of substeps: ' + step.substeps.length); // # of substeps: 3 -}); \ No newline at end of file From 3e9b8a3e437ce3d2f1f63fff84e05aff221f6809 Mon Sep 17 00:00:00 2001 From: bebbe Date: Thu, 13 Apr 2017 11:13:28 +0200 Subject: [PATCH 04/40] Fixed errors from the CI --- .idea/workspace.xml | 282 ++++++++---------- lib/TreeSearch.js | 2 +- lib/checks/index.js | 5 +- lib/node/index.js | 5 +- test/canMultiplyLikeTermConstantNodes.test.js | 1 - 5 files changed, 139 insertions(+), 156 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index d29187c1..e24c11f2 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,20 +1,12 @@ - - - - - - - - - - - - - - + + + + + + @@ -32,24 +24,54 @@ - - + + - - + + - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -103,13 +125,19 @@ + + + @@ -143,10 +172,10 @@ - @@ -191,24 +220,6 @@ - + - + + @@ -441,7 +409,7 @@ - + 1492068942886 @@ -450,19 +418,27 @@ - - - + + - + - - + + @@ -494,20 +470,6 @@ - - - - - - - - - - - - - - @@ -635,13 +597,6 @@ - - - - - - - @@ -669,20 +624,6 @@ - - - - - - - - - - - - - - @@ -760,13 +701,6 @@ - - - - - - - @@ -788,18 +722,18 @@ - + - - + + - + - - + + @@ -812,5 +746,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/TreeSearch.js b/lib/TreeSearch.js index 2bb3d174..6f039502 100644 --- a/lib/TreeSearch.js +++ b/lib/TreeSearch.js @@ -14,7 +14,7 @@ TreeSearch.preOrder = function(simplificationFunction) { // simplifcation function TreeSearch.postOrder = function(simplificationFunction) { return function (node) { - return search(simplificationFunction, node, false); + return search(simplificationFunction, node, false); }; }; diff --git a/lib/checks/index.js b/lib/checks/index.js index ae55feb3..2808feab 100644 --- a/lib/checks/index.js +++ b/lib/checks/index.js @@ -1,16 +1,17 @@ const canAddLikeTermPolynomialNodes = require('./canAddLikeTermPolynomialNodes'); +const canMultiplyLikeTermConstantNodes = require('./canMultiplyLikeTermConstantNodes'); const canMultiplyLikeTermPolynomialNodes = require('./canMultiplyLikeTermPolynomialNodes'); const canRearrangeCoefficient = require('./canRearrangeCoefficient'); const canSimplifyPolynomialTerms = require('./canSimplifyPolynomialTerms'); const hasUnsupportedNodes = require('./hasUnsupportedNodes'); const isQuadratic = require('./isQuadratic'); const resolvesToConstant = require('./resolvesToConstant'); -const canMultiplyLikeTermConstantNodes = require('./canMultiplyLikeTermConstantNodes'); + module.exports = { canAddLikeTermPolynomialNodes, - canMultiplyLikeTermPolynomialNodes, canMultiplyLikeTermConstantNodes, + canMultiplyLikeTermPolynomialNodes, canRearrangeCoefficient, canSimplifyPolynomialTerms, hasUnsupportedNodes, diff --git a/lib/node/index.js b/lib/node/index.js index 2ee809cd..9bc29979 100644 --- a/lib/node/index.js +++ b/lib/node/index.js @@ -1,13 +1,14 @@ +const ConstantTerms = require('./ConstantTerms'); const Creator = require('./Creator'); const PolynomialTerm = require('./PolynomialTerm'); const Status = require('./Status'); const Type = require('./Type'); -const ConstantTerms = require('./ConstantTerms'); + module.exports = { + ConstantTerms, Creator, PolynomialTerm, - ConstantTerms, Status, Type, }; diff --git a/test/canMultiplyLikeTermConstantNodes.test.js b/test/canMultiplyLikeTermConstantNodes.test.js index fc7e2d38..7fd3c75e 100644 --- a/test/canMultiplyLikeTermConstantNodes.test.js +++ b/test/canMultiplyLikeTermConstantNodes.test.js @@ -1,5 +1,4 @@ const canMultiplyLikeTermConstantNodes = require('../lib/checks/canMultiplyLikeTermConstantNodes'); -const multiplyLikeTerms = require('../lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms') const TestUtil = require('./TestUtil'); From 008ca26d96ea540d74a3eadd8bb38bf1f738cefb Mon Sep 17 00:00:00 2001 From: Sebastian Liljevall Date: Thu, 13 Apr 2017 11:22:08 +0200 Subject: [PATCH 05/40] Update index.js --- lib/checks/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/checks/index.js b/lib/checks/index.js index ae55feb3..ddf93f61 100644 --- a/lib/checks/index.js +++ b/lib/checks/index.js @@ -1,16 +1,16 @@ const canAddLikeTermPolynomialNodes = require('./canAddLikeTermPolynomialNodes'); +const canMultiplyLikeTermConstantNodes = require('./canMultiplyLikeTermConstantNodes'); const canMultiplyLikeTermPolynomialNodes = require('./canMultiplyLikeTermPolynomialNodes'); const canRearrangeCoefficient = require('./canRearrangeCoefficient'); const canSimplifyPolynomialTerms = require('./canSimplifyPolynomialTerms'); const hasUnsupportedNodes = require('./hasUnsupportedNodes'); const isQuadratic = require('./isQuadratic'); const resolvesToConstant = require('./resolvesToConstant'); -const canMultiplyLikeTermConstantNodes = require('./canMultiplyLikeTermConstantNodes'); module.exports = { canAddLikeTermPolynomialNodes, - canMultiplyLikeTermPolynomialNodes, canMultiplyLikeTermConstantNodes, + canMultiplyLikeTermPolynomialNodes, canRearrangeCoefficient, canSimplifyPolynomialTerms, hasUnsupportedNodes, From 43f9c49cf6c5164890b860c69ed116e1797eae7d Mon Sep 17 00:00:00 2001 From: Sebastian Liljevall Date: Thu, 13 Apr 2017 11:22:49 +0200 Subject: [PATCH 06/40] Update index.js --- lib/node/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/node/index.js b/lib/node/index.js index 2ee809cd..10eed23f 100644 --- a/lib/node/index.js +++ b/lib/node/index.js @@ -1,13 +1,13 @@ +const ConstantTerms = require('./ConstantTerms'); const Creator = require('./Creator'); const PolynomialTerm = require('./PolynomialTerm'); const Status = require('./Status'); const Type = require('./Type'); -const ConstantTerms = require('./ConstantTerms'); module.exports = { + ConstantTerms, Creator, PolynomialTerm, - ConstantTerms, Status, Type, }; From c20657a935fd6b249c6c1c84b0f46ee03e4e0dfc Mon Sep 17 00:00:00 2001 From: Sebastian Liljevall Date: Thu, 13 Apr 2017 11:23:38 +0200 Subject: [PATCH 07/40] Update multiplyLikeTerms.js --- .../collectAndCombineSearch/multiplyLikeTerms.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js index 97dcbb9b..03c23da1 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js @@ -26,7 +26,7 @@ function multiplyLikeTerms(node, polynomialOnly = false) { } } - status = multiplyPolynomialTerms(node) + status = multiplyPolynomialTerms(node); if (status.hasChanged()) { status.changeType = ChangeTypes.MULTIPLY_COEFFICIENTS; return status; From 48ff96169ac61636de6eff90448e8e6c560fe9ac Mon Sep 17 00:00:00 2001 From: Sebastian Liljevall Date: Thu, 13 Apr 2017 11:24:12 +0200 Subject: [PATCH 08/40] Update TreeSearch.js --- lib/TreeSearch.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/TreeSearch.js b/lib/TreeSearch.js index 2bb3d174..6f039502 100644 --- a/lib/TreeSearch.js +++ b/lib/TreeSearch.js @@ -14,7 +14,7 @@ TreeSearch.preOrder = function(simplificationFunction) { // simplifcation function TreeSearch.postOrder = function(simplificationFunction) { return function (node) { - return search(simplificationFunction, node, false); + return search(simplificationFunction, node, false); }; }; From 9c86ab86a585b8786242cfcd208b24392144eab4 Mon Sep 17 00:00:00 2001 From: Sebastian Liljevall Date: Thu, 13 Apr 2017 11:24:50 +0200 Subject: [PATCH 09/40] Update canMultiplyLikeTermConstantNodes.test.js --- test/canMultiplyLikeTermConstantNodes.test.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/canMultiplyLikeTermConstantNodes.test.js b/test/canMultiplyLikeTermConstantNodes.test.js index fc7e2d38..7fd3c75e 100644 --- a/test/canMultiplyLikeTermConstantNodes.test.js +++ b/test/canMultiplyLikeTermConstantNodes.test.js @@ -1,5 +1,4 @@ const canMultiplyLikeTermConstantNodes = require('../lib/checks/canMultiplyLikeTermConstantNodes'); -const multiplyLikeTerms = require('../lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms') const TestUtil = require('./TestUtil'); From 34df2cd9016001d313720dc43487009e28046386 Mon Sep 17 00:00:00 2001 From: bebbe Date: Thu, 13 Apr 2017 12:43:33 +0200 Subject: [PATCH 10/40] Added support for division with powers too, for better pedagogical approach --- .idea/workspace.xml | 420 +++++++++++------- lib/checks/canDivideLikeTermConstantNodes.js | 30 ++ .../canDivideLikeTermPolynomialNodes.js | 26 ++ lib/checks/index.js | 4 + .../divideLikeTerms.js | 161 +++++++ .../collectAndCombineSearch/index.js | 5 + test/canDivideLikeTermConstantNodes.test.js | 18 + test/canDivideLikeTermPolynomialNodes.test.js | 17 + .../collectAndCombineSearch.test.js | 8 + test/simplifyExpression/simplify.test.js | 10 +- 10 files changed, 549 insertions(+), 150 deletions(-) create mode 100644 lib/checks/canDivideLikeTermConstantNodes.js create mode 100644 lib/checks/canDivideLikeTermPolynomialNodes.js create mode 100644 lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js create mode 100644 test/canDivideLikeTermConstantNodes.test.js create mode 100644 test/canDivideLikeTermPolynomialNodes.test.js diff --git a/.idea/workspace.xml b/.idea/workspace.xml index e24c11f2..a69f0396 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -1,12 +1,17 @@ - + + + + + + - - - + + + @@ -21,61 +26,71 @@ - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + + + + + + + + + + + @@ -92,9 +107,6 @@ - console.log - console. - math math er math error math erro @@ -122,6 +134,9 @@ arithmeticSear arithmeticSearc arithmeticSearch + canMu + simplify_arithme + collectExponents @@ -129,7 +144,7 @@ @@ -150,13 +165,20 @@ @@ -172,10 +194,10 @@ - @@ -220,6 +242,46 @@ - + @@ -409,7 +511,8 @@ - + + 1492068942886 @@ -425,20 +528,26 @@ - - - - + - + - - + + @@ -463,62 +572,14 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -598,7 +659,6 @@ - @@ -610,13 +670,6 @@ - - - - - - - @@ -638,13 +691,6 @@ - - - - - - - @@ -659,13 +705,6 @@ - - - - - - - @@ -680,20 +719,6 @@ - - - - - - - - - - - - - - @@ -726,7 +751,6 @@ - @@ -734,7 +758,6 @@ - @@ -742,46 +765,113 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + + + + + + + + - - + + - + - - + + - - + + - + - - + + - + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -794,5 +884,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/checks/canDivideLikeTermConstantNodes.js b/lib/checks/canDivideLikeTermConstantNodes.js new file mode 100644 index 00000000..403639ad --- /dev/null +++ b/lib/checks/canDivideLikeTermConstantNodes.js @@ -0,0 +1,30 @@ +const Node = require('../node'); + + +// Returns true if the nodes are constant terms with the same constant and has an exponent each + +function canDivideLikeTermConstantNodes(node) { + if (!Node.Type.isOperator(node) || node.op !== '/') { + return false; + } + const args = node.args; + if (args.some(n=> Node.PolynomialTerm.isPolynomialTerm(n))) { + return false; + } + if (!args.every(n => Node.ConstantTerms.isConstantTerm(n))) { + return false; + } + if (args.length === 1) { + return false; + } + const constantTermList = node.args.map(n => new Node.ConstantTerms(n)); + if (!constantTermList.every(constTerm => !constTerm.hasCoeff())) { + return false; + } + const firstTerm = constantTermList[0]; + const restTerms = constantTermList.slice(1); + // they're considered like terms if they have the same symbol name + return restTerms.every(term => firstTerm.getBaseValue() === term.getBaseValue()); +} + +module.exports = canDivideLikeTermConstantNodes; diff --git a/lib/checks/canDivideLikeTermPolynomialNodes.js b/lib/checks/canDivideLikeTermPolynomialNodes.js new file mode 100644 index 00000000..5614c1b6 --- /dev/null +++ b/lib/checks/canDivideLikeTermPolynomialNodes.js @@ -0,0 +1,26 @@ +const Node = require('../node'); + +// Returns true if the nodes are symbolic terms with the same symbol and no +// coefficients. +function canDivideLikeTermPolynomialNodes(node) { + if (!Node.Type.isOperator(node) || node.op !== '/') { + return false; + } + const args = node.args; + if (!args.every(n => Node.PolynomialTerm.isPolynomialTerm(n))) { + return false; + } + if (args.length === 1) { + return false; + } + const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); + if (!polynomialTermList.every(polyTerm => !polyTerm.hasCoeff())) { + return false; + } + const firstTerm = polynomialTermList[0]; + const restTerms = polynomialTermList.slice(1); + // they're considered like terms if they have the same symbol name + return restTerms.every(term => firstTerm.getSymbolName() === term.getSymbolName()); +} + +module.exports = canDivideLikeTermPolynomialNodes; diff --git a/lib/checks/index.js b/lib/checks/index.js index 2808feab..1ddb08a2 100644 --- a/lib/checks/index.js +++ b/lib/checks/index.js @@ -1,4 +1,6 @@ const canAddLikeTermPolynomialNodes = require('./canAddLikeTermPolynomialNodes'); +const canDivideLikeTermConstantNodes = require('./canDivideLikeTermConstantNodes'); +const canDivideLikeTermPolynomialNodes = require('./canDivideLikeTermPolynomialNodes'); const canMultiplyLikeTermConstantNodes = require('./canMultiplyLikeTermConstantNodes'); const canMultiplyLikeTermPolynomialNodes = require('./canMultiplyLikeTermPolynomialNodes'); const canRearrangeCoefficient = require('./canRearrangeCoefficient'); @@ -10,6 +12,8 @@ const resolvesToConstant = require('./resolvesToConstant'); module.exports = { canAddLikeTermPolynomialNodes, + canDivideLikeTermConstantNodes, + canDivideLikeTermPolynomialNodes, canMultiplyLikeTermConstantNodes, canMultiplyLikeTermPolynomialNodes, canRearrangeCoefficient, diff --git a/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js new file mode 100644 index 00000000..3ed1aff7 --- /dev/null +++ b/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js @@ -0,0 +1,161 @@ +const arithmeticSearch = require('../arithmeticSearch'); +const checks = require('../../checks'); +const clone = require('../../util/clone'); + +const ChangeTypes = require('../../ChangeTypes'); +const Node = require('../../node'); +// Divides a list of nodes that are polynomial like terms or constants with same base. Returns a node. +// The nodes should *not* have coefficients. +function divideLikeTerms(node, polynomialOnly = false) { + if (!Node.Type.isOperator(node)) { + return Node.Status.noChange(node); + } + let status; + if (!polynomialOnly && !checks.canDivideLikeTermConstantNodes(node)) { + status = arithmeticSearch(node); + if (status.hasChanged()) { + status.changeType = ChangeTypes.SIMPLIFY_FRACTION; + return status; + } + } + + status = dividePolynomialTerms(node); + if (status.hasChanged()) { + status.changeType = ChangeTypes.SIMPLIFY_FRACTION; + return status; + } + return Node.Status.noChange(node); +} +function dividePolynomialTerms(node) { + if (!checks.canDivideLikeTermPolynomialNodes(node) && !checks.canDivideLikeTermConstantNodes(node)) { + return Node.Status.noChange(node); + } + const substeps = []; + let newNode = clone(node); + + // STEP 1: If any term has no exponent, make it have exponent 1 + // e.g. x -> x^1 (this is for pedagogy reasons) + // (this step only happens under certain conditions and later steps might + // happen even if step 1 does not) + let status = addOneExponent(newNode); + if (status.hasChanged()) { + substeps.push(status); + newNode = Node.Status.resetChangeGroups(status.newNode); + } + + // STEP 2: collect exponents to a single exponent sum + // e.g. x^1 / x^3 -> x^(1 + -3) + status = collectExponents(newNode); + substeps.push(status); + newNode = Node.Status.resetChangeGroups(status.newNode); + + // STEP 3: add exponents together. + // NOTE: This might not be a step if the exponents aren't all constants, + // but this case isn't that common and can be caught in other steps. + // e.g. x^(2+4+z) + // TODO: handle fractions, combining and collecting like terms, etc, here + const exponentSum = newNode.args[1].content; + const sumStatus = arithmeticSearch(exponentSum); + if (sumStatus.hasChanged()) { + status = Node.Status.childChanged(newNode, sumStatus, 1); + substeps.push(status); + newNode = Node.Status.resetChangeGroups(status.newNode); + } + + if (substeps.length === 1) { // possible if only step 2 happens + return substeps[0]; + } + else { + return Node.Status.nodeChanged( + ChangeTypes.MULTIPLY_POLYNOMIAL_TERMS, + node, newNode, true, substeps); + } +} + +// Given a product of polynomial terms, changes any term with no exponent +// into a term with an explicit exponent of 1. This is for pedagogy, and +// makes the adding coefficients step clearer. +// e.g. x^2 / x -> x^2 / x^1 +// Returns a Node.Status object. +function addOneExponent(node) { + const newNode = clone(node); + let change = false; + let changeGroup = 1; + if (checks.canDivideLikeTermConstantNodes(node)) { + newNode.args.forEach((child, i) => { + const constTerm = new Node.ConstantTerms(child); + if (!constTerm.getExponentNode()) { + newNode.args[i] = Node.Creator.constantTerm( + constTerm.getBaseNode(), + Node.Creator.constant(1), + constTerm.getCoeffNode()); + + newNode.args[i].changeGroup = changeGroup; + node.args[i].changeGroup = changeGroup; // note that this is the "oldNode" + + change = true; + changeGroup++; + } + }); + } + else { + newNode.args.forEach((child, i) => { + const polyTerm = new Node.PolynomialTerm(child); + if (!polyTerm.getExponentNode()) { + newNode.args[i] = Node.Creator.polynomialTerm( + polyTerm.getSymbolNode(), + Node.Creator.constant(1), + polyTerm.getCoeffNode()); + + newNode.args[i].changeGroup = changeGroup; + node.args[i].changeGroup = changeGroup; // note that this is the "oldNode" + + change = true; + changeGroup++; + } + }); + } + + if (change) { + return Node.Status.nodeChanged( + ChangeTypes.ADD_EXPONENT_OF_ONE, node, newNode, false); + } + else { + return Node.Status.noChange(node); + } +} +// Given a product of polynomial terms, groups the exponents into a sum +// e.g. x^2 * x^3 * x^1 -> x^(2 + 3 + 1) +// Returns a Node.Status object. +function collectExponents(node) { + if (checks.canDivideLikeTermConstantNodes(node)) { + const constantTermList = node.args.map(n => new Node.ConstantTerms(n)); + // If we're multiplying constant nodes together, they all share the same + // base. Get that from the first node. + const baseNode = constantTermList[0].getBaseNode(); + // The new exponent will be a sum of negative exponents (an operation, wrapped in + // parens) e.g. 10^(3+-4+-5) + const exponentNodeList = constantTermList.map(p => p.getExponentNode(true)); + const newExponent = Node.Creator.parenthesis( + Node.Creator.operator('-', exponentNodeList)); + const newNode = Node.Creator.constantTerm(baseNode, newExponent, null); + return Node.Status.nodeChanged( + ChangeTypes.COLLECT_EXPONENTS, node, newNode); + } + else { + const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); + // If we're multiplying polynomial nodes together, they all share the same + // symbol. Get that from the first node. + const symbolNode = polynomialTermList[0].getSymbolNode(); + // The new exponent will be a sum of exponents (an operation, wrapped in + // parens) e.g. x^(3+4+5) + const exponentNodeList = polynomialTermList.map(p => p.getExponentNode(true)); + const newExponent = Node.Creator.parenthesis( + Node.Creator.operator('-', exponentNodeList)); + const newNode = Node.Creator.polynomialTerm(symbolNode, newExponent, null); + return Node.Status.nodeChanged( + ChangeTypes.COLLECT_EXPONENTS, node, newNode); + } +} + +module.exports = divideLikeTerms; diff --git a/lib/simplifyExpression/collectAndCombineSearch/index.js b/lib/simplifyExpression/collectAndCombineSearch/index.js index 33ddb342..3f2eb145 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/index.js +++ b/lib/simplifyExpression/collectAndCombineSearch/index.js @@ -2,6 +2,7 @@ const addLikeTerms = require('./addLikeTerms'); const clone = require('../../util/clone'); +const divideLikeTerms = require('./divideLikeTerms') const multiplyLikeTerms = require('./multiplyLikeTerms'); const ChangeTypes = require('../../ChangeTypes'); @@ -11,6 +12,7 @@ const TreeSearch = require('../../TreeSearch'); const termCollectorFunctions = { '+': addLikeTerms, + '/': divideLikeTerms, '*': multiplyLikeTerms }; @@ -44,6 +46,9 @@ function collectAndCombineLikeTerms(node) { // e.g. x * x^2 * x => ... => x^4 return multiplyLikeTerms(node, true); } + else if (node.op === '/') { + return divideLikeTerms(node, true); + } else { return Node.Status.noChange(node); } diff --git a/test/canDivideLikeTermConstantNodes.test.js b/test/canDivideLikeTermConstantNodes.test.js new file mode 100644 index 00000000..0a593214 --- /dev/null +++ b/test/canDivideLikeTermConstantNodes.test.js @@ -0,0 +1,18 @@ +const canDivideLikeTermConstantNodes = require('../lib/checks/canDivideLikeTermConstantNodes'); + + +const TestUtil = require('./TestUtil'); + +function testCanBeDividedConstants(expr, multipliable) { + TestUtil.testBooleanFunction(canDivideLikeTermConstantNodes, expr, multipliable); +} + +describe('can multiply like term constants', () => { + const tests = [ + ['3^2 / 3^5', true], + ['2^3 / 3^2', false], + ['10^3 / 10^2', true], + ['10^6 / 10^4', true] + ]; + tests.forEach(t => testCanBeDividedConstants(t[0], t[1])); +}); diff --git a/test/canDivideLikeTermPolynomialNodes.test.js b/test/canDivideLikeTermPolynomialNodes.test.js new file mode 100644 index 00000000..33e31f66 --- /dev/null +++ b/test/canDivideLikeTermPolynomialNodes.test.js @@ -0,0 +1,17 @@ +const canDivideLikeTermPolynomialNodes = require('../lib/checks/canDivideLikeTermPolynomialNodes'); + +const TestUtil = require('./TestUtil'); + +function testCanBeDivided(expr, multipliable) { + TestUtil.testBooleanFunction(canDivideLikeTermPolynomialNodes, expr, multipliable); +} + +describe('can divide like term polynomials', () => { + const tests = [ + ['x^2 / x', true], + ['3x^2 / x ', false], + ['y^3 / y^2', true], + ['x^8 / x^5', true] + ]; + tests.forEach(t => testCanBeDivided(t[0], t[1])); +}); diff --git a/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js b/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js index 272e87c0..2b792ecb 100644 --- a/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js +++ b/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js @@ -68,3 +68,11 @@ describe('collect and multiply like terms', function() { ]; tests.forEach(t => testSimpleCollectAndCombineSearch(t[0], t[1])); }); + +describe('collect and divide multiply like terms', function() { + const tests = [ + ['10^5 / 10^2', '10^3'], + ['2^4 / 2^2', '2^2'] + ]; + tests.forEach(t => testSimpleCollectAndCombineSearch(t[0], t[1])); +}); diff --git a/test/simplifyExpression/simplify.test.js b/test/simplifyExpression/simplify.test.js index 736cf00e..71fe51b1 100644 --- a/test/simplifyExpression/simplify.test.js +++ b/test/simplifyExpression/simplify.test.js @@ -163,7 +163,7 @@ describe('handles unnecessary parens at root level', function() { tests.forEach(t => testSimplify(t[0], t[1], t[2])); }); -describe('multiplication of powers support', function(){ +describe('multiplication of powers support', function() { const tests = [ ['10^3 * 10^2', '100000'], ['2^3*2^4', '128'], @@ -171,6 +171,14 @@ describe('multiplication of powers support', function(){ tests.forEach(t => testSimplify(t[0], t[1], t[2])); }); +describe('division of powers support', function() { + const tests = [ + ['10^3 / 10^2', '10'], + ['2^5/2^4', '2'], + ]; + tests.forEach(t => testSimplify(t[0], t[1], t[2])); +}) + describe('keeping parens in important places, on printing', function() { testSimplify('2 / (2x^2) + 5', '2 / (2x^2) + 5'); }); From b9b10df0406cf71e9b2411cbf76262afc5f80891 Mon Sep 17 00:00:00 2001 From: Sebastian Liljevall Date: Thu, 13 Apr 2017 13:54:00 +0200 Subject: [PATCH 11/40] Delete test-mathsteps.js --- test-mathsteps.js | 13 ------------- 1 file changed, 13 deletions(-) delete mode 100644 test-mathsteps.js diff --git a/test-mathsteps.js b/test-mathsteps.js deleted file mode 100644 index 9d40fb35..00000000 --- a/test-mathsteps.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Created by bebbe on 2017-04-05. - */ -const mathsteps = require('mathsteps'); - -const steps = mathsteps.simplifyExpression('2x + 2x + x + x'); - -steps.forEach(step => { - console.log('before change: ' + step.oldNode); // before change: 2 x + 2 x + x + x - console.log('change: ' + step.changeType); // change: ADD_POLYNOMIAL_TERMS - console.log('after change: ' + step.newNode); // after change: 6 x - console.log('# of substeps: ' + step.substeps.length); // # of substeps: 3 -}); \ No newline at end of file From 1ae215887a66f4c35c9c77c566d3ea5ef07cda25 Mon Sep 17 00:00:00 2001 From: bebbe Date: Thu, 13 Apr 2017 13:55:57 +0200 Subject: [PATCH 12/40] Fixed errors and added semicolon --- .idea/workspace.xml | 135 ++++++++---------- .../collectAndCombineSearch/index.js | 2 +- test/simplifyExpression/simplify.test.js | 2 +- 3 files changed, 63 insertions(+), 76 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index a69f0396..fbcda4f9 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,15 +2,8 @@ - - - - - - - @@ -26,6 +19,16 @@ + + + + + + + + + + @@ -36,7 +39,7 @@ - + @@ -46,6 +49,16 @@ + + + + + + + + + + @@ -171,14 +184,14 @@ @@ -194,10 +207,10 @@ - @@ -336,49 +349,13 @@ - + @@ -512,7 +489,7 @@ - + 1492068942886 @@ -535,19 +512,27 @@ - - - + + - + - - + + @@ -573,7 +558,8 @@ - @@ -789,21 +775,6 @@ - - - - - - - - - - - - - - - @@ -916,5 +887,21 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/simplifyExpression/collectAndCombineSearch/index.js b/lib/simplifyExpression/collectAndCombineSearch/index.js index 3f2eb145..5221b984 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/index.js +++ b/lib/simplifyExpression/collectAndCombineSearch/index.js @@ -2,7 +2,7 @@ const addLikeTerms = require('./addLikeTerms'); const clone = require('../../util/clone'); -const divideLikeTerms = require('./divideLikeTerms') +const divideLikeTerms = require('./divideLikeTerms'); const multiplyLikeTerms = require('./multiplyLikeTerms'); const ChangeTypes = require('../../ChangeTypes'); diff --git a/test/simplifyExpression/simplify.test.js b/test/simplifyExpression/simplify.test.js index 71fe51b1..175a557f 100644 --- a/test/simplifyExpression/simplify.test.js +++ b/test/simplifyExpression/simplify.test.js @@ -177,7 +177,7 @@ describe('division of powers support', function() { ['2^5/2^4', '2'], ]; tests.forEach(t => testSimplify(t[0], t[1], t[2])); -}) +}); describe('keeping parens in important places, on printing', function() { testSimplify('2 / (2x^2) + 5', '2 / (2x^2) + 5'); From 8016ed94fced623ce40021d12b8f948ba463415a Mon Sep 17 00:00:00 2001 From: bebbe Date: Thu, 13 Apr 2017 13:58:25 +0200 Subject: [PATCH 13/40] Fixed errors and added semicolon --- .idea/workspace.xml | 229 +++++------------- .../multiplyLikeTerms.js | 2 +- 2 files changed, 68 insertions(+), 163 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index fbcda4f9..38135930 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -3,8 +3,7 @@ - - + @@ -19,91 +18,11 @@ - - - - - - - - - - - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + @@ -178,7 +97,6 @@ @@ -207,10 +126,10 @@ - @@ -273,28 +192,6 @@ - - + - + - - + + @@ -559,7 +463,8 @@ - @@ -783,14 +688,6 @@ - - - - - - - - @@ -799,14 +696,6 @@ - - - - - - - - @@ -815,23 +704,23 @@ - + - - + + - + - - + + - + @@ -839,34 +728,34 @@ - + - + - + - - + + - + - - + + - + - - + + @@ -879,26 +768,42 @@ - + - - + + - + - + - + - - + + + + + + + + + + + + + + + + + + diff --git a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js index 97dcbb9b..03c23da1 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js @@ -26,7 +26,7 @@ function multiplyLikeTerms(node, polynomialOnly = false) { } } - status = multiplyPolynomialTerms(node) + status = multiplyPolynomialTerms(node); if (status.hasChanged()) { status.changeType = ChangeTypes.MULTIPLY_COEFFICIENTS; return status; From a98c170221cf973369cf8cf472e6d86372bc4515 Mon Sep 17 00:00:00 2001 From: bebbe Date: Tue, 18 Apr 2017 13:01:08 +0200 Subject: [PATCH 14/40] Fixed some stuff according to the comments from the pull request. --- lib/ChangeTypes.js | 7 +- lib/TreeSearch.js | 1 + .../canMultiplyLikeTermConstantNodes.js | 12 ++-- lib/node/ConstantTerms.js | 67 ++----------------- lib/node/PolynomialTerm.js | 1 - .../multiplyLikeTerms.js | 19 +++--- lib/simplifyExpression/stepThrough.js | 6 +- 7 files changed, 28 insertions(+), 85 deletions(-) diff --git a/lib/ChangeTypes.js b/lib/ChangeTypes.js index a02ccca1..7e980c6f 100644 --- a/lib/ChangeTypes.js +++ b/lib/ChangeTypes.js @@ -42,6 +42,11 @@ module.exports = { // e.g. x + 2 + x^2 + x + 4 -> x^2 + (x + x) + (4 + 2) COLLECT_LIKE_TERMS: 'COLLECT_LIKE_TERMS', + // MULTIPLYING CONSTANT POWERS + + // e.g. 10^2 * 10^3 -> 10^(2+3) + COLLECT_CONSTANT_EXPONENTS: 'COLLECT_CONSTANT_EXPONENTS', + // ADDING POLYNOMIALS // e.g. 2x + x -> 2x + 1x @@ -58,7 +63,7 @@ module.exports = { // e.g. x^2 * x -> x^2 * x^1 ADD_EXPONENT_OF_ONE: 'ADD_EXPONENT_OF_ONE', // e.g. x^2 * x^3 * x^1 -> x^(2 + 3 + 1) - COLLECT_EXPONENTS: 'COLLECT_EXPONENTS', + COLLECT_POLYNOMIAL_EXPONENTS: 'COLLECT_POLYNOMIAL_EXPONENTS', // e.g. 2x * 3x -> (2 * 3)(x * x) MULTIPLY_COEFFICIENTS: 'MULTIPLY_COEFFICIENTS', // e.g. 2x * x -> 2x ^ 2 diff --git a/lib/TreeSearch.js b/lib/TreeSearch.js index 6f039502..17a07e6f 100644 --- a/lib/TreeSearch.js +++ b/lib/TreeSearch.js @@ -21,6 +21,7 @@ TreeSearch.postOrder = function(simplificationFunction) { // A helper function for performing a tree search with a function function search(simplificationFunction, node, preOrder) { let status; + if (preOrder) { status = simplificationFunction(node); if (status.hasChanged()) { diff --git a/lib/checks/canMultiplyLikeTermConstantNodes.js b/lib/checks/canMultiplyLikeTermConstantNodes.js index 2a23ca47..5f84d90a 100644 --- a/lib/checks/canMultiplyLikeTermConstantNodes.js +++ b/lib/checks/canMultiplyLikeTermConstantNodes.js @@ -1,14 +1,14 @@ const Node = require('../node'); - -// Returns true if the nodes are constant terms with the same constant and has an exponent each +// Returns true if node is a multiplication of constant power nodes +// where you can combine their exponents, e.g. 10^2 * 10^4 * 10 can become 10^7 function canMultiplyLikeTermConstantNodes(node) { if (!Node.Type.isOperator(node) || node.op !== '*' || node.args[1].op === '/') { return false; } const args = node.args; - if (args.some(n=> Node.PolynomialTerm.isPolynomialTerm(n))) { + if (args.some(n => Node.PolynomialTerm.isPolynomialTerm(n))) { return false; } if (!args.every(n => Node.ConstantTerms.isConstantTerm(n))) { @@ -18,13 +18,9 @@ function canMultiplyLikeTermConstantNodes(node) { return false; } const constantTermList = node.args.map(n => new Node.ConstantTerms(n)); - if (!constantTermList.every(constTerm => !constTerm.hasCoeff())) { - return false; - } const firstTerm = constantTermList[0]; const restTerms = constantTermList.slice(1); - // they're considered like terms if they have the same symbol name + // they're considered like terms if they have the same base value return restTerms.every(term => firstTerm.getBaseValue() === term.getBaseValue()); } - module.exports = canMultiplyLikeTermConstantNodes; diff --git a/lib/node/ConstantTerms.js b/lib/node/ConstantTerms.js index f7a3c6d3..281f6b5e 100644 --- a/lib/node/ConstantTerms.js +++ b/lib/node/ConstantTerms.js @@ -2,7 +2,7 @@ const NodeCreator = require('./Creator'); const NodeType = require('./Type'); class ConstantTerms { - constructor(node, onlyImplicitMultiplication=false) { + constructor(node) { if (NodeType.isOperator(node)) { if (node.op === '^') { const constantNode = node.args[0]; @@ -13,28 +13,6 @@ class ConstantTerms { this.base = constantNode; this.exponent = node.args[1]; } - else if (node.op === '*'){ - if (onlyImplicitMultiplication && !node.implicit) { - throw Error('Expected implicit multiplication'); - } - if (node.args.length !== 2) { - throw Error('Expected two arguments to *'); - } - const coeffNode = node.args[0]; - if (!NodeType.isConstantOrConstantFraction(coeffNode)) { - throw Error('Expected coefficient to be constant or fraction of ' + - 'constants term, got ' + coeffNode); - } - this.coeff = coeffNode; - const nonCoefficientTerm = new ConstantTerms( - node.args[1], onlyImplicitMultiplication); - if (nonCoefficientTerm.hasCoeff()) { - throw Error('Cannot have two coefficients ' + coeffNode + - ' and ' + nonCoefficientTerm.getCoeffNode()); - } - this.base = nonCoefficientTerm.getBaseNode(); - this.exponent = nonCoefficientTerm.getExponentNode(); - } else if (node.op === '/') { const denominatorNode = node.args[1]; if (!NodeType.isConstant(denominatorNode)) { @@ -48,22 +26,6 @@ class ConstantTerms { this.base = numeratorNode.getBaseNode(); } } - else if (NodeType.isUnaryMinus(node)) { - var arg = node.args[0]; - if (NodeType.isParenthesis(arg)) { - arg = arg.content; - } - const constNode = new ConstantTerms( - arg, onlyImplicitMultiplication); - this.exponent = constNode.getExponentNode(); - this.base = constNode.getBaseNode(); - if (!constNode.hasCoeff()) { - this.coeff = NodeCreator.constant(-1); - } - else { - this.coeff = negativeCoefficient(constNode.getCoeffNode()); - } - } else if (NodeType.isConstant(node)) { this.base = node; } @@ -89,42 +51,21 @@ class ConstantTerms { return this.exponent; } } - getCoeffNode(defaultOne=false) { - if (!this.coeff && defaultOne) { - return NodeCreator.constant(1); - } - else { - return this.coeff; - } - } - hasCoeff() { - return !!this.coeff; - } } // Returns if the node represents an expression that can be considered a term. // e.g. 2^2, 10^4, 6^2 are all terms. 4, 2+x, 3*7, x-z are all not terms. // See the tests for some more thorough examples of exactly what counts and // what does not. -ConstantTerms.isConstantTerm = function (node, onlyImplicitMultiplication = false) { +ConstantTerms.isConstantTerm = function (node) { try { - // will throw error if node isn't poly term - new ConstantTerms(node, onlyImplicitMultiplication); + // will throw error if node isn't constant power term + new ConstantTerms(node); return true; } catch (err) { return false; } }; -function negativeCoefficient(node) { - if (NodeType.isConstant(node)) { - node = NodeCreator.constant(0 - parseFloat(node.value)); - } - else { - const numeratorValue = 0 - parseFloat(node.args[0].value); - node.args[0] = NodeCreator.constant(numeratorValue); - } - return node; -} module.exports = ConstantTerms; \ No newline at end of file diff --git a/lib/node/PolynomialTerm.js b/lib/node/PolynomialTerm.js index 4983bfcc..36713f68 100644 --- a/lib/node/PolynomialTerm.js +++ b/lib/node/PolynomialTerm.js @@ -36,7 +36,6 @@ class PolynomialTerm { if (node.args.length !== 2) { throw Error('Expected two arguments to *'); } - const coeffNode = node.args[0]; if (!NodeType.isConstantOrConstantFraction(coeffNode)) { throw Error('Expected coefficient to be constant or fraction of ' + diff --git a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js index 97dcbb9b..977d21e7 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js @@ -8,7 +8,7 @@ const Node = require('../../node'); // Multiplies a list of nodes that are polynomial like terms. Returns a node. // The polynomial nodes should *not* have coefficients. (multiplying // coefficients is handled in collecting like terms for multiplication) -function multiplyLikeTerms(node, polynomialOnly = false) { +function multiplyLikeTerms(node, polynomialOnly=false) { if (!Node.Type.isOperator(node)) { return Node.Status.noChange(node); } @@ -26,7 +26,7 @@ function multiplyLikeTerms(node, polynomialOnly = false) { } } - status = multiplyPolynomialTerms(node) + status = multiplyPolynomialTerms(node); if (status.hasChanged()) { status.changeType = ChangeTypes.MULTIPLY_COEFFICIENTS; return status; @@ -79,10 +79,11 @@ function multiplyPolynomialTerms(node) { } } -// Given a product of polynomial terms, changes any term with no exponent +// Given a product of polynomial or constant terms, changes any term with no exponent // into a term with an explicit exponent of 1. This is for pedagogy, and -// makes the adding coefficients step clearer. +// makes the adding exponents step clearer. // e.g. x^2 * x -> x^2 * x^1 +// e.g. 10^2 * 10 -> 10^2 * 10^1 // Returns a Node.Status object. function addOneExponent(node) { const newNode = clone(node); @@ -94,8 +95,7 @@ function addOneExponent(node) { if (!constTerm.getExponentNode()) { newNode.args[i] = Node.Creator.constantTerm( constTerm.getBaseNode(), - Node.Creator.constant(1), - constTerm.getCoeffNode()); + Node.Creator.constant(1)); newNode.args[i].changeGroup = changeGroup; node.args[i].changeGroup = changeGroup; // note that this is the "oldNode" @@ -131,8 +131,9 @@ function addOneExponent(node) { return Node.Status.noChange(node); } } -// Given a product of polynomial terms, groups the exponents into a sum +// Given a product of polynomial or constant terms, groups the exponents into a sum // e.g. x^2 * x^3 * x^1 -> x^(2 + 3 + 1) +// e.g. 10^2 * 10^3 -> 10^(2+3) // Returns a Node.Status object. function collectExponents(node) { if (checks.canMultiplyLikeTermConstantNodes(node)) { @@ -147,7 +148,7 @@ function collectExponents(node) { Node.Creator.operator('+', exponentNodeList)); const newNode = Node.Creator.constantTerm(baseNode, newExponent, null); return Node.Status.nodeChanged( - ChangeTypes.COLLECT_EXPONENTS, node, newNode); + ChangeTypes.COLLECT_CONSTANT_EXPONENTS, node, newNode); } else { const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); @@ -161,7 +162,7 @@ function collectExponents(node) { Node.Creator.operator('+', exponentNodeList)); const newNode = Node.Creator.polynomialTerm(symbolNode, newExponent, null); return Node.Status.nodeChanged( - ChangeTypes.COLLECT_EXPONENTS, node, newNode); + ChangeTypes.COLLECT_POLYNOMIAL_EXPONENTS, node, newNode); } } diff --git a/lib/simplifyExpression/stepThrough.js b/lib/simplifyExpression/stepThrough.js index 5f2e04c7..818977ac 100644 --- a/lib/simplifyExpression/stepThrough.js +++ b/lib/simplifyExpression/stepThrough.js @@ -72,9 +72,9 @@ function step(node) { divisionSearch, // Adding fractions, cancelling out things in fractions fractionsSearch, - // e.g. addition: 2x + 4x^2 + x => 4x^2 + 3x - // e.g. multiplication: 2x * x * x^2 => 2x^3 - // e.g multiplication: 10^3 * 10^2 => 10^5 + // e.g. addition of polynomial terms: 2x + 4x^2 + x => 4x^2 + 3x + // e.g. multiplication of polynomial terms: 2x * x * x^2 => 2x^3 + // e.g. multiplication of constants: 10^3 * 10^2 => 10^5 collectAndCombineSearch, // e.g. 2 + 2 => 4 arithmeticSearch, From a9d2a9bc3914ce243aa7edf081f363917c956c4a Mon Sep 17 00:00:00 2001 From: bebbe Date: Tue, 18 Apr 2017 13:14:39 +0200 Subject: [PATCH 15/40] Added new ChangeTypes for collecting exponents --- lib/ChangeTypes.js | 8 +++++++- lib/checks/canDivideLikeTermConstantNodes.js | 5 +---- .../collectAndCombineSearch/divideLikeTerms.js | 4 ++-- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/ChangeTypes.js b/lib/ChangeTypes.js index a02ccca1..4b48ab2f 100644 --- a/lib/ChangeTypes.js +++ b/lib/ChangeTypes.js @@ -42,6 +42,12 @@ module.exports = { // e.g. x + 2 + x^2 + x + 4 -> x^2 + (x + x) + (4 + 2) COLLECT_LIKE_TERMS: 'COLLECT_LIKE_TERMS', + // MULTYPLYING/DIVIDING POWERS + + // e.g. 10^2 * 10^3 -> 10^(3+2) + // e.g. 10^4 / 10^2 -> 10^(4-2) + COLLECT_CONSTANT_EXPONENTS: 'COLLECT_CONSTANT_EXPONENTS', + // ADDING POLYNOMIALS // e.g. 2x + x -> 2x + 1x @@ -58,7 +64,7 @@ module.exports = { // e.g. x^2 * x -> x^2 * x^1 ADD_EXPONENT_OF_ONE: 'ADD_EXPONENT_OF_ONE', // e.g. x^2 * x^3 * x^1 -> x^(2 + 3 + 1) - COLLECT_EXPONENTS: 'COLLECT_EXPONENTS', + COLLECT_POLYNOMIAL_EXPONENTS: 'COLLECT_POLYNOMIAL_EXPONENTS', // e.g. 2x * 3x -> (2 * 3)(x * x) MULTIPLY_COEFFICIENTS: 'MULTIPLY_COEFFICIENTS', // e.g. 2x * x -> 2x ^ 2 diff --git a/lib/checks/canDivideLikeTermConstantNodes.js b/lib/checks/canDivideLikeTermConstantNodes.js index 403639ad..7364c0c3 100644 --- a/lib/checks/canDivideLikeTermConstantNodes.js +++ b/lib/checks/canDivideLikeTermConstantNodes.js @@ -18,12 +18,9 @@ function canDivideLikeTermConstantNodes(node) { return false; } const constantTermList = node.args.map(n => new Node.ConstantTerms(n)); - if (!constantTermList.every(constTerm => !constTerm.hasCoeff())) { - return false; - } const firstTerm = constantTermList[0]; const restTerms = constantTermList.slice(1); - // they're considered like terms if they have the same symbol name + // they're considered like terms if they have the same base value return restTerms.every(term => firstTerm.getBaseValue() === term.getBaseValue()); } diff --git a/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js index 3ed1aff7..ab9108c3 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js @@ -140,7 +140,7 @@ function collectExponents(node) { Node.Creator.operator('-', exponentNodeList)); const newNode = Node.Creator.constantTerm(baseNode, newExponent, null); return Node.Status.nodeChanged( - ChangeTypes.COLLECT_EXPONENTS, node, newNode); + ChangeTypes.COLLECT_CONSTANT_EXPONENTS, node, newNode); } else { const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); @@ -154,7 +154,7 @@ function collectExponents(node) { Node.Creator.operator('-', exponentNodeList)); const newNode = Node.Creator.polynomialTerm(symbolNode, newExponent, null); return Node.Status.nodeChanged( - ChangeTypes.COLLECT_EXPONENTS, node, newNode); + ChangeTypes.COLLECT_POLYNOMIAL_EXPONENTS, node, newNode); } } From 94c55104019369219b9f391255e5650141143fcd Mon Sep 17 00:00:00 2001 From: bebbe Date: Tue, 18 Apr 2017 13:34:12 +0200 Subject: [PATCH 16/40] Fixed some things and added new ChangeTypes. --- .../canMultiplyLikeTermConstantNodes.js | 3 - lib/node/ConstantTerms.js | 61 +------------------ .../divideLikeTerms.js | 3 +- .../multiplyLikeTerms.js | 7 +-- 4 files changed, 5 insertions(+), 69 deletions(-) diff --git a/lib/checks/canMultiplyLikeTermConstantNodes.js b/lib/checks/canMultiplyLikeTermConstantNodes.js index 2a23ca47..d92d1327 100644 --- a/lib/checks/canMultiplyLikeTermConstantNodes.js +++ b/lib/checks/canMultiplyLikeTermConstantNodes.js @@ -18,9 +18,6 @@ function canMultiplyLikeTermConstantNodes(node) { return false; } const constantTermList = node.args.map(n => new Node.ConstantTerms(n)); - if (!constantTermList.every(constTerm => !constTerm.hasCoeff())) { - return false; - } const firstTerm = constantTermList[0]; const restTerms = constantTermList.slice(1); // they're considered like terms if they have the same symbol name diff --git a/lib/node/ConstantTerms.js b/lib/node/ConstantTerms.js index f7a3c6d3..caa45647 100644 --- a/lib/node/ConstantTerms.js +++ b/lib/node/ConstantTerms.js @@ -2,7 +2,7 @@ const NodeCreator = require('./Creator'); const NodeType = require('./Type'); class ConstantTerms { - constructor(node, onlyImplicitMultiplication=false) { + constructor(node) { if (NodeType.isOperator(node)) { if (node.op === '^') { const constantNode = node.args[0]; @@ -13,28 +13,6 @@ class ConstantTerms { this.base = constantNode; this.exponent = node.args[1]; } - else if (node.op === '*'){ - if (onlyImplicitMultiplication && !node.implicit) { - throw Error('Expected implicit multiplication'); - } - if (node.args.length !== 2) { - throw Error('Expected two arguments to *'); - } - const coeffNode = node.args[0]; - if (!NodeType.isConstantOrConstantFraction(coeffNode)) { - throw Error('Expected coefficient to be constant or fraction of ' + - 'constants term, got ' + coeffNode); - } - this.coeff = coeffNode; - const nonCoefficientTerm = new ConstantTerms( - node.args[1], onlyImplicitMultiplication); - if (nonCoefficientTerm.hasCoeff()) { - throw Error('Cannot have two coefficients ' + coeffNode + - ' and ' + nonCoefficientTerm.getCoeffNode()); - } - this.base = nonCoefficientTerm.getBaseNode(); - this.exponent = nonCoefficientTerm.getExponentNode(); - } else if (node.op === '/') { const denominatorNode = node.args[1]; if (!NodeType.isConstant(denominatorNode)) { @@ -48,22 +26,6 @@ class ConstantTerms { this.base = numeratorNode.getBaseNode(); } } - else if (NodeType.isUnaryMinus(node)) { - var arg = node.args[0]; - if (NodeType.isParenthesis(arg)) { - arg = arg.content; - } - const constNode = new ConstantTerms( - arg, onlyImplicitMultiplication); - this.exponent = constNode.getExponentNode(); - this.base = constNode.getBaseNode(); - if (!constNode.hasCoeff()) { - this.coeff = NodeCreator.constant(-1); - } - else { - this.coeff = negativeCoefficient(constNode.getCoeffNode()); - } - } else if (NodeType.isConstant(node)) { this.base = node; } @@ -89,17 +51,6 @@ class ConstantTerms { return this.exponent; } } - getCoeffNode(defaultOne=false) { - if (!this.coeff && defaultOne) { - return NodeCreator.constant(1); - } - else { - return this.coeff; - } - } - hasCoeff() { - return !!this.coeff; - } } // Returns if the node represents an expression that can be considered a term. // e.g. 2^2, 10^4, 6^2 are all terms. 4, 2+x, 3*7, x-z are all not terms. @@ -115,16 +66,6 @@ ConstantTerms.isConstantTerm = function (node, onlyImplicitMultiplication = fals return false; } }; -function negativeCoefficient(node) { - if (NodeType.isConstant(node)) { - node = NodeCreator.constant(0 - parseFloat(node.value)); - } - else { - const numeratorValue = 0 - parseFloat(node.args[0].value); - node.args[0] = NodeCreator.constant(numeratorValue); - } - return node; -} module.exports = ConstantTerms; \ No newline at end of file diff --git a/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js index ab9108c3..69835fde 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js @@ -87,8 +87,7 @@ function addOneExponent(node) { if (!constTerm.getExponentNode()) { newNode.args[i] = Node.Creator.constantTerm( constTerm.getBaseNode(), - Node.Creator.constant(1), - constTerm.getCoeffNode()); + Node.Creator.constant(1)); newNode.args[i].changeGroup = changeGroup; node.args[i].changeGroup = changeGroup; // note that this is the "oldNode" diff --git a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js index 03c23da1..176d6e8a 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js @@ -94,8 +94,7 @@ function addOneExponent(node) { if (!constTerm.getExponentNode()) { newNode.args[i] = Node.Creator.constantTerm( constTerm.getBaseNode(), - Node.Creator.constant(1), - constTerm.getCoeffNode()); + Node.Creator.constant(1)); newNode.args[i].changeGroup = changeGroup; node.args[i].changeGroup = changeGroup; // note that this is the "oldNode" @@ -147,7 +146,7 @@ function collectExponents(node) { Node.Creator.operator('+', exponentNodeList)); const newNode = Node.Creator.constantTerm(baseNode, newExponent, null); return Node.Status.nodeChanged( - ChangeTypes.COLLECT_EXPONENTS, node, newNode); + ChangeTypes.COLLECT_CONSTANT_EXPONENTS, node, newNode); } else { const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); @@ -161,7 +160,7 @@ function collectExponents(node) { Node.Creator.operator('+', exponentNodeList)); const newNode = Node.Creator.polynomialTerm(symbolNode, newExponent, null); return Node.Status.nodeChanged( - ChangeTypes.COLLECT_EXPONENTS, node, newNode); + ChangeTypes.COLLECT_POLYNOMIAL_EXPONENTS, node, newNode); } } From b74568890834d57592b63b46b31f0777666d3598 Mon Sep 17 00:00:00 2001 From: bebbe Date: Tue, 18 Apr 2017 13:36:13 +0200 Subject: [PATCH 17/40] Better comment for the new ChangeType --- lib/ChangeTypes.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/ChangeTypes.js b/lib/ChangeTypes.js index 7e980c6f..2f7261c4 100644 --- a/lib/ChangeTypes.js +++ b/lib/ChangeTypes.js @@ -42,9 +42,10 @@ module.exports = { // e.g. x + 2 + x^2 + x + 4 -> x^2 + (x + x) + (4 + 2) COLLECT_LIKE_TERMS: 'COLLECT_LIKE_TERMS', - // MULTIPLYING CONSTANT POWERS + // MULTIPLYING/DIVIDING CONSTANT POWERS // e.g. 10^2 * 10^3 -> 10^(2+3) + // e.g. 10^4 / 10^2 -> 10^(4-2) COLLECT_CONSTANT_EXPONENTS: 'COLLECT_CONSTANT_EXPONENTS', // ADDING POLYNOMIALS From 2768d3cb4346ec75e61d10b436b120a7740c9f05 Mon Sep 17 00:00:00 2001 From: bebbe Date: Thu, 20 Apr 2017 11:29:11 +0200 Subject: [PATCH 18/40] Deleted the ConstantTerms class and added functions inside "CanMultiplyLikeTermConstantNodes" instead to create the power node with exponent and base and also check if all the arguments are of this form and have the same base. Also added some more tests. --- lib/ChangeTypes.js | 3 +- .../canMultiplyLikeTermConstantNodes.js | 59 ++++++++++--- lib/node/ConstantTerms.js | 71 ---------------- lib/node/Creator.js | 8 +- lib/node/index.js | 2 - .../multiplyLikeTerms.js | 84 ++++++++++--------- test/canMultiplyLikeTermConstantNodes.test.js | 2 +- .../collectAndCombineSearch.test.js | 15 ++++ 8 files changed, 114 insertions(+), 130 deletions(-) delete mode 100644 lib/node/ConstantTerms.js diff --git a/lib/ChangeTypes.js b/lib/ChangeTypes.js index 2f7261c4..7e980c6f 100644 --- a/lib/ChangeTypes.js +++ b/lib/ChangeTypes.js @@ -42,10 +42,9 @@ module.exports = { // e.g. x + 2 + x^2 + x + 4 -> x^2 + (x + x) + (4 + 2) COLLECT_LIKE_TERMS: 'COLLECT_LIKE_TERMS', - // MULTIPLYING/DIVIDING CONSTANT POWERS + // MULTIPLYING CONSTANT POWERS // e.g. 10^2 * 10^3 -> 10^(2+3) - // e.g. 10^4 / 10^2 -> 10^(4-2) COLLECT_CONSTANT_EXPONENTS: 'COLLECT_CONSTANT_EXPONENTS', // ADDING POLYNOMIALS diff --git a/lib/checks/canMultiplyLikeTermConstantNodes.js b/lib/checks/canMultiplyLikeTermConstantNodes.js index 5f84d90a..a69d524b 100644 --- a/lib/checks/canMultiplyLikeTermConstantNodes.js +++ b/lib/checks/canMultiplyLikeTermConstantNodes.js @@ -1,26 +1,63 @@ const Node = require('../node'); +const NodeType = require('../node/Type'); // Returns true if node is a multiplication of constant power nodes // where you can combine their exponents, e.g. 10^2 * 10^4 * 10 can become 10^7 function canMultiplyLikeTermConstantNodes(node) { - if (!Node.Type.isOperator(node) || node.op !== '*' || node.args[1].op === '/') { + if (!Node.Type.isOperator(node) || node.op !== '*') { return false; } const args = node.args; - if (args.some(n => Node.PolynomialTerm.isPolynomialTerm(n))) { + if (!args.every(n => isPowerTerm(n))) { return false; } - if (!args.every(n => Node.ConstantTerms.isConstantTerm(n))) { - return false; - } - if (args.length === 1) { - return false; - } - const constantTermList = node.args.map(n => new Node.ConstantTerms(n)); + const constantTermList = node.args.map(n => createPowerNode(n)); const firstTerm = constantTermList[0]; const restTerms = constantTermList.slice(1); // they're considered like terms if they have the same base value - return restTerms.every(term => firstTerm.getBaseValue() === term.getBaseValue()); + return restTerms.every(term => getBaseValue(firstTerm) === getBaseValue(term)); +} +function createPowerNode(node) { + if (NodeType.isOperator(node)) { + if (node.op === '^') { + const constantNode = node.args[0]; + if (!NodeType.isConstant(constantNode)) { + throw Error('Expected constant term, got ' + constantNode); + } + node.base = constantNode; + node.exponent = node.args[1]; + return node; + } + else { + throw Error('Expected power sign, got ' + node.op); + } + } + else if (NodeType.isConstant(node)) { + node.base = node; + return node; + } + else { + throw Error('Unsupported node type: ' + node.type); + } } -module.exports = canMultiplyLikeTermConstantNodes; + +function getBaseValue(node) { + return node.base.value; +} + +function isPowerTerm(node) { + try { + // will throw error if node isn't constant power term + new createPowerNode(node); + return true; + } + catch (err) { + return false; + } +} +module.exports = { + canMultiplyLikeTermConstantNodes, + createPowerNode +}; + diff --git a/lib/node/ConstantTerms.js b/lib/node/ConstantTerms.js deleted file mode 100644 index 281f6b5e..00000000 --- a/lib/node/ConstantTerms.js +++ /dev/null @@ -1,71 +0,0 @@ -const NodeCreator = require('./Creator'); -const NodeType = require('./Type'); - -class ConstantTerms { - constructor(node) { - if (NodeType.isOperator(node)) { - if (node.op === '^') { - const constantNode = node.args[0]; - if (!NodeType.isConstant(constantNode)) { - //throw Error('Expected constant term, got ' + constantNode); - return false; - } - this.base = constantNode; - this.exponent = node.args[1]; - } - else if (node.op === '/') { - const denominatorNode = node.args[1]; - if (!NodeType.isConstant(denominatorNode)) { - //throw Error('denominator must be constant node, instead of ' + - //denominatorNode); - return false; - } - const numeratorNode = new ConstantTerms( - node.args[0]); - this.exponent = numeratorNode.getExponentNode(); - this.base = numeratorNode.getBaseNode(); - } - } - else if (NodeType.isConstant(node)) { - this.base = node; - } - else { - throw Error('Unsupported node type: ' + node.type); - } - } - - - /* GETTER FUNCTIONS */ - getBaseNode() { - return this.base; - } - getBaseValue() { - return this.base.value; - } - - getExponentNode(defaultOne = false) { - if (!this.exponent && defaultOne) { - return NodeCreator.constant(1); - } - else { - return this.exponent; - } - } -} -// Returns if the node represents an expression that can be considered a term. -// e.g. 2^2, 10^4, 6^2 are all terms. 4, 2+x, 3*7, x-z are all not terms. -// See the tests for some more thorough examples of exactly what counts and -// what does not. -ConstantTerms.isConstantTerm = function (node) { - try { - // will throw error if node isn't constant power term - new ConstantTerms(node); - return true; - } - catch (err) { - return false; - } -}; - - -module.exports = ConstantTerms; \ No newline at end of file diff --git a/lib/node/Creator.js b/lib/node/Creator.js index b5b603df..c172fd76 100644 --- a/lib/node/Creator.js +++ b/lib/node/Creator.js @@ -72,12 +72,12 @@ const NodeCreator = { const symbol = NodeCreator.symbol('nthRoot'); return new math.expression.node.FunctionNode(symbol, [radicandNode, rootNode]); }, - constantTerm (base, exponent) { - let constTerm = base; + powerTerm (base, exponent) { + let powerTerm = base; if (exponent) { - constTerm = this.operator('^', [constTerm, exponent]); + powerTerm = this.operator('^', [powerTerm, exponent]); } - return constTerm; + return powerTerm; } }; diff --git a/lib/node/index.js b/lib/node/index.js index 9bc29979..c8370723 100644 --- a/lib/node/index.js +++ b/lib/node/index.js @@ -1,4 +1,3 @@ -const ConstantTerms = require('./ConstantTerms'); const Creator = require('./Creator'); const PolynomialTerm = require('./PolynomialTerm'); const Status = require('./Status'); @@ -6,7 +5,6 @@ const Type = require('./Type'); module.exports = { - ConstantTerms, Creator, PolynomialTerm, Status, diff --git a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js index 977d21e7..99b6096f 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js @@ -13,7 +13,7 @@ function multiplyLikeTerms(node, polynomialOnly=false) { return Node.Status.noChange(node); } let status; - if (!polynomialOnly && !checks.canMultiplyLikeTermConstantNodes(node)) { + if (!polynomialOnly && !checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { status = arithmeticSearch(node); if (status.hasChanged()) { status.changeType = ChangeTypes.MULTIPLY_COEFFICIENTS; @@ -34,7 +34,7 @@ function multiplyLikeTerms(node, polynomialOnly=false) { return Node.Status.noChange(node); } function multiplyPolynomialTerms(node) { - if (!checks.canMultiplyLikeTermPolynomialNodes(node) && !checks.canMultiplyLikeTermConstantNodes(node)) { + if (!checks.canMultiplyLikeTermPolynomialNodes(node) && !checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { return Node.Status.noChange(node); } const substeps = []; @@ -52,7 +52,13 @@ function multiplyPolynomialTerms(node) { // STEP 2: collect exponents to a single exponent sum // e.g. x^1 * x^3 -> x^(1+3) - status = collectExponents(newNode); + // e.g. 10^2 * 10^3 -> 10^(2+3) + if (checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { + status = collectConstantExponents(newNode); + } + else { + status = collectPolynomialExponents(newNode); + } substeps.push(status); newNode = Node.Status.resetChangeGroups(status.newNode); @@ -89,12 +95,12 @@ function addOneExponent(node) { const newNode = clone(node); let change = false; let changeGroup = 1; - if (checks.canMultiplyLikeTermConstantNodes(node)) { + if (checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { newNode.args.forEach((child, i) => { - const constTerm = new Node.ConstantTerms(child); - if (!constTerm.getExponentNode()) { - newNode.args[i] = Node.Creator.constantTerm( - constTerm.getBaseNode(), + const constPowerTerm = new checks.canMultiplyLikeTermConstantNodes.createPowerNode(child); + if (!constPowerTerm.exponent) { + newNode.args[i] = Node.Creator.powerTerm( + constPowerTerm.base, Node.Creator.constant(1)); newNode.args[i].changeGroup = changeGroup; @@ -131,39 +137,39 @@ function addOneExponent(node) { return Node.Status.noChange(node); } } -// Given a product of polynomial or constant terms, groups the exponents into a sum -// e.g. x^2 * x^3 * x^1 -> x^(2 + 3 + 1) +// Given a product of constant terms, groups the exponents into a sum // e.g. 10^2 * 10^3 -> 10^(2+3) // Returns a Node.Status object. -function collectExponents(node) { - if (checks.canMultiplyLikeTermConstantNodes(node)) { - const constantTermList = node.args.map(n => new Node.ConstantTerms(n)); - // If we're multiplying constant nodes together, they all share the same - // base. Get that from the first node. - const baseNode = constantTermList[0].getBaseNode(); - // The new exponent will be a sum of exponents (an operation, wrapped in - // parens) e.g. 10^(3+4+5) - const exponentNodeList = constantTermList.map(p => p.getExponentNode(true)); - const newExponent = Node.Creator.parenthesis( - Node.Creator.operator('+', exponentNodeList)); - const newNode = Node.Creator.constantTerm(baseNode, newExponent, null); - return Node.Status.nodeChanged( - ChangeTypes.COLLECT_CONSTANT_EXPONENTS, node, newNode); - } - else { - const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); - // If we're multiplying polynomial nodes together, they all share the same - // symbol. Get that from the first node. - const symbolNode = polynomialTermList[0].getSymbolNode(); - // The new exponent will be a sum of exponents (an operation, wrapped in - // parens) e.g. x^(3+4+5) - const exponentNodeList = polynomialTermList.map(p => p.getExponentNode(true)); - const newExponent = Node.Creator.parenthesis( - Node.Creator.operator('+', exponentNodeList)); - const newNode = Node.Creator.polynomialTerm(symbolNode, newExponent, null); - return Node.Status.nodeChanged( - ChangeTypes.COLLECT_POLYNOMIAL_EXPONENTS, node, newNode); - } +function collectConstantExponents(node) { + const constantTermList = node.args.map(n => new checks.canMultiplyLikeTermConstantNodes.createPowerNode(n)); + // If we're multiplying constant nodes together, they all share the same + // base. Get that from the first node. + const baseNode = constantTermList[0].base; + // The new exponent will be a sum of exponents (an operation, wrapped in + // parens) e.g. 10^(3+4+5) + const exponentNodeList = constantTermList.map(p => p.exponent); + const newExponent = Node.Creator.parenthesis( + Node.Creator.operator('+', exponentNodeList)); + const newNode = Node.Creator.powerTerm(baseNode, newExponent, null); + return Node.Status.nodeChanged( + ChangeTypes.COLLECT_CONSTANT_EXPONENTS, node, newNode); +} +// Given a product of polynomial terms, groups the exponents into a sum +// e.g. x^2 * x^3 * x^1 -> x^(2 + 3 + 1) +// Returns a Node.Status object. +function collectPolynomialExponents(node) { + const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); + // If we're multiplying polynomial nodes together, they all share the same + // symbol. Get that from the first node. + const symbolNode = polynomialTermList[0].getSymbolNode(); + // The new exponent will be a sum of exponents (an operation, wrapped in + // parens) e.g. x^(3+4+5) + const exponentNodeList = polynomialTermList.map(p => p.getExponentNode(true)); + const newExponent = Node.Creator.parenthesis( + Node.Creator.operator('+', exponentNodeList)); + const newNode = Node.Creator.polynomialTerm(symbolNode, newExponent, null); + return Node.Status.nodeChanged( + ChangeTypes.COLLECT_POLYNOMIAL_EXPONENTS, node, newNode); } module.exports = multiplyLikeTerms; diff --git a/test/canMultiplyLikeTermConstantNodes.test.js b/test/canMultiplyLikeTermConstantNodes.test.js index 7fd3c75e..30f12088 100644 --- a/test/canMultiplyLikeTermConstantNodes.test.js +++ b/test/canMultiplyLikeTermConstantNodes.test.js @@ -4,7 +4,7 @@ const canMultiplyLikeTermConstantNodes = require('../lib/checks/canMultiplyLikeT const TestUtil = require('./TestUtil'); function testCanBeMultipliedConstants(expr, multipliable) { - TestUtil.testBooleanFunction(canMultiplyLikeTermConstantNodes, expr, multipliable); + TestUtil.testBooleanFunction(canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes, expr, multipliable); } describe('can multiply like term constants', () => { diff --git a/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js b/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js index 272e87c0..c7116b29 100644 --- a/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js +++ b/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js @@ -52,6 +52,21 @@ describe('combinePolynomialTerms addition', function() { ]; tests.forEach(t => testCollectAndCombineSubsteps(t[0], t[1])); }); +describe('combineConstantPowerTerms multiplication', function() { + const tests = [ + ['10^2 * 10', + ['10^2 * 10^1', + '10^(2 + 1)', + '10^3'], + ], + ['2 * 2^3', + ['2^1 * 2^3', + '2^(1 + 3)', + '2^4'], + ], + ]; + tests.forEach(t => testCollectAndCombineSubsteps(t[0], t[1], t[2])); +}); describe('collectAndCombineSearch with no substeps', function () { const tests = [ From 154a2e6445eb949b8e4a83adb47f94de472622a4 Mon Sep 17 00:00:00 2001 From: bebbe Date: Fri, 21 Apr 2017 09:59:44 +0200 Subject: [PATCH 19/40] Merged some changes from multiply power branch --- lib/ChangeTypes.js | 2 +- lib/checks/canDivideLikeTermConstantNodes.js | 60 ++++++++++--- .../canMultiplyLikeTermConstantNodes.js | 62 ++++++++++--- lib/node/ConstantTerms.js | 71 --------------- lib/node/Creator.js | 8 +- lib/node/index.js | 2 - .../divideLikeTerms.js | 22 ++--- .../multiplyLikeTerms.js | 88 ++++++++++--------- test/canDivideLikeTermConstantNodes.test.js | 4 +- test/canMultiplyLikeTermConstantNodes.test.js | 2 +- 10 files changed, 164 insertions(+), 157 deletions(-) delete mode 100644 lib/node/ConstantTerms.js diff --git a/lib/ChangeTypes.js b/lib/ChangeTypes.js index 4b48ab2f..475655d6 100644 --- a/lib/ChangeTypes.js +++ b/lib/ChangeTypes.js @@ -42,7 +42,7 @@ module.exports = { // e.g. x + 2 + x^2 + x + 4 -> x^2 + (x + x) + (4 + 2) COLLECT_LIKE_TERMS: 'COLLECT_LIKE_TERMS', - // MULTYPLYING/DIVIDING POWERS + // MULTIPLYING CONSTANT POWERS // e.g. 10^2 * 10^3 -> 10^(3+2) // e.g. 10^4 / 10^2 -> 10^(4-2) diff --git a/lib/checks/canDivideLikeTermConstantNodes.js b/lib/checks/canDivideLikeTermConstantNodes.js index 7364c0c3..df9c50c5 100644 --- a/lib/checks/canDivideLikeTermConstantNodes.js +++ b/lib/checks/canDivideLikeTermConstantNodes.js @@ -1,27 +1,63 @@ const Node = require('../node'); +const NodeType = require('../node/Type'); - -// Returns true if the nodes are constant terms with the same constant and has an exponent each +// Returns true if node is a division of constant power nodes +// where you can combine their exponents, e.g. 10^4 / 10^2 can become 10^2 function canDivideLikeTermConstantNodes(node) { if (!Node.Type.isOperator(node) || node.op !== '/') { return false; } const args = node.args; - if (args.some(n=> Node.PolynomialTerm.isPolynomialTerm(n))) { - return false; - } - if (!args.every(n => Node.ConstantTerms.isConstantTerm(n))) { - return false; - } - if (args.length === 1) { + if (!args.every(n => isPowerTerm(n))) { return false; } - const constantTermList = node.args.map(n => new Node.ConstantTerms(n)); + const constantTermList = node.args.map(n => createPowerNode(n)); const firstTerm = constantTermList[0]; const restTerms = constantTermList.slice(1); // they're considered like terms if they have the same base value - return restTerms.every(term => firstTerm.getBaseValue() === term.getBaseValue()); + return restTerms.every(term => getBaseValue(firstTerm) === getBaseValue(term)); +} +function createPowerNode(node) { + if (NodeType.isOperator(node)) { + if (node.op === '^') { + const constantNode = node.args[0]; + if (!NodeType.isConstant(constantNode)) { + throw Error('Expected constant term, got ' + constantNode); + } + node.base = constantNode; + node.exponent = node.args[1]; + return node; + } + else { + throw Error('Expected power sign, got ' + node.op); + } + } + else if (NodeType.isConstant(node)) { + node.base = node; + return node; + } + else { + throw Error('Unsupported node type: ' + node.type); + } +} + +function getBaseValue(node) { + return node.base.value; +} + +function isPowerTerm(node) { + try { + // will throw error if node isn't constant power term + new createPowerNode(node); + return true; + } + catch (err) { + return false; + } } +module.exports = { + canDivideLikeTermConstantNodes, + createPowerNode +}; -module.exports = canDivideLikeTermConstantNodes; diff --git a/lib/checks/canMultiplyLikeTermConstantNodes.js b/lib/checks/canMultiplyLikeTermConstantNodes.js index d92d1327..a69d524b 100644 --- a/lib/checks/canMultiplyLikeTermConstantNodes.js +++ b/lib/checks/canMultiplyLikeTermConstantNodes.js @@ -1,27 +1,63 @@ const Node = require('../node'); +const NodeType = require('../node/Type'); - -// Returns true if the nodes are constant terms with the same constant and has an exponent each +// Returns true if node is a multiplication of constant power nodes +// where you can combine their exponents, e.g. 10^2 * 10^4 * 10 can become 10^7 function canMultiplyLikeTermConstantNodes(node) { - if (!Node.Type.isOperator(node) || node.op !== '*' || node.args[1].op === '/') { + if (!Node.Type.isOperator(node) || node.op !== '*') { return false; } const args = node.args; - if (args.some(n=> Node.PolynomialTerm.isPolynomialTerm(n))) { + if (!args.every(n => isPowerTerm(n))) { return false; } - if (!args.every(n => Node.ConstantTerms.isConstantTerm(n))) { - return false; + const constantTermList = node.args.map(n => createPowerNode(n)); + const firstTerm = constantTermList[0]; + const restTerms = constantTermList.slice(1); + // they're considered like terms if they have the same base value + return restTerms.every(term => getBaseValue(firstTerm) === getBaseValue(term)); +} +function createPowerNode(node) { + if (NodeType.isOperator(node)) { + if (node.op === '^') { + const constantNode = node.args[0]; + if (!NodeType.isConstant(constantNode)) { + throw Error('Expected constant term, got ' + constantNode); + } + node.base = constantNode; + node.exponent = node.args[1]; + return node; + } + else { + throw Error('Expected power sign, got ' + node.op); + } + } + else if (NodeType.isConstant(node)) { + node.base = node; + return node; + } + else { + throw Error('Unsupported node type: ' + node.type); } - if (args.length === 1) { +} + +function getBaseValue(node) { + return node.base.value; +} + +function isPowerTerm(node) { + try { + // will throw error if node isn't constant power term + new createPowerNode(node); + return true; + } + catch (err) { return false; } - const constantTermList = node.args.map(n => new Node.ConstantTerms(n)); - const firstTerm = constantTermList[0]; - const restTerms = constantTermList.slice(1); - // they're considered like terms if they have the same symbol name - return restTerms.every(term => firstTerm.getBaseValue() === term.getBaseValue()); } +module.exports = { + canMultiplyLikeTermConstantNodes, + createPowerNode +}; -module.exports = canMultiplyLikeTermConstantNodes; diff --git a/lib/node/ConstantTerms.js b/lib/node/ConstantTerms.js deleted file mode 100644 index caa45647..00000000 --- a/lib/node/ConstantTerms.js +++ /dev/null @@ -1,71 +0,0 @@ -const NodeCreator = require('./Creator'); -const NodeType = require('./Type'); - -class ConstantTerms { - constructor(node) { - if (NodeType.isOperator(node)) { - if (node.op === '^') { - const constantNode = node.args[0]; - if (!NodeType.isConstant(constantNode)) { - //throw Error('Expected constant term, got ' + constantNode); - return false; - } - this.base = constantNode; - this.exponent = node.args[1]; - } - else if (node.op === '/') { - const denominatorNode = node.args[1]; - if (!NodeType.isConstant(denominatorNode)) { - //throw Error('denominator must be constant node, instead of ' + - //denominatorNode); - return false; - } - const numeratorNode = new ConstantTerms( - node.args[0]); - this.exponent = numeratorNode.getExponentNode(); - this.base = numeratorNode.getBaseNode(); - } - } - else if (NodeType.isConstant(node)) { - this.base = node; - } - else { - throw Error('Unsupported node type: ' + node.type); - } - } - - - /* GETTER FUNCTIONS */ - getBaseNode() { - return this.base; - } - getBaseValue() { - return this.base.value; - } - - getExponentNode(defaultOne = false) { - if (!this.exponent && defaultOne) { - return NodeCreator.constant(1); - } - else { - return this.exponent; - } - } -} -// Returns if the node represents an expression that can be considered a term. -// e.g. 2^2, 10^4, 6^2 are all terms. 4, 2+x, 3*7, x-z are all not terms. -// See the tests for some more thorough examples of exactly what counts and -// what does not. -ConstantTerms.isConstantTerm = function (node, onlyImplicitMultiplication = false) { - try { - // will throw error if node isn't poly term - new ConstantTerms(node, onlyImplicitMultiplication); - return true; - } - catch (err) { - return false; - } -}; - - -module.exports = ConstantTerms; \ No newline at end of file diff --git a/lib/node/Creator.js b/lib/node/Creator.js index b5b603df..c172fd76 100644 --- a/lib/node/Creator.js +++ b/lib/node/Creator.js @@ -72,12 +72,12 @@ const NodeCreator = { const symbol = NodeCreator.symbol('nthRoot'); return new math.expression.node.FunctionNode(symbol, [radicandNode, rootNode]); }, - constantTerm (base, exponent) { - let constTerm = base; + powerTerm (base, exponent) { + let powerTerm = base; if (exponent) { - constTerm = this.operator('^', [constTerm, exponent]); + powerTerm = this.operator('^', [powerTerm, exponent]); } - return constTerm; + return powerTerm; } }; diff --git a/lib/node/index.js b/lib/node/index.js index 9bc29979..c8370723 100644 --- a/lib/node/index.js +++ b/lib/node/index.js @@ -1,4 +1,3 @@ -const ConstantTerms = require('./ConstantTerms'); const Creator = require('./Creator'); const PolynomialTerm = require('./PolynomialTerm'); const Status = require('./Status'); @@ -6,7 +5,6 @@ const Type = require('./Type'); module.exports = { - ConstantTerms, Creator, PolynomialTerm, Status, diff --git a/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js index 69835fde..305d7567 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js @@ -11,7 +11,7 @@ function divideLikeTerms(node, polynomialOnly = false) { return Node.Status.noChange(node); } let status; - if (!polynomialOnly && !checks.canDivideLikeTermConstantNodes(node)) { + if (!polynomialOnly && !checks.canDivideLikeTermConstantNodes.canDivideLikeTermConstantNodes(node)) { status = arithmeticSearch(node); if (status.hasChanged()) { status.changeType = ChangeTypes.SIMPLIFY_FRACTION; @@ -27,7 +27,7 @@ function divideLikeTerms(node, polynomialOnly = false) { return Node.Status.noChange(node); } function dividePolynomialTerms(node) { - if (!checks.canDivideLikeTermPolynomialNodes(node) && !checks.canDivideLikeTermConstantNodes(node)) { + if (!checks.canDivideLikeTermPolynomialNodes(node) && !checks.canDivideLikeTermConstantNodes.canDivideLikeTermConstantNodes(node)) { return Node.Status.noChange(node); } const substeps = []; @@ -81,12 +81,12 @@ function addOneExponent(node) { const newNode = clone(node); let change = false; let changeGroup = 1; - if (checks.canDivideLikeTermConstantNodes(node)) { + if (checks.canDivideLikeTermConstantNodes.canDivideLikeTermConstantNodes(node)) { newNode.args.forEach((child, i) => { - const constTerm = new Node.ConstantTerms(child); - if (!constTerm.getExponentNode()) { + const constTerm = new checks.canDivideLikeTermConstantNodes.createPowerNode(child); + if (!constTerm.exponent) { newNode.args[i] = Node.Creator.constantTerm( - constTerm.getBaseNode(), + constTerm.base, Node.Creator.constant(1)); newNode.args[i].changeGroup = changeGroup; @@ -127,17 +127,17 @@ function addOneExponent(node) { // e.g. x^2 * x^3 * x^1 -> x^(2 + 3 + 1) // Returns a Node.Status object. function collectExponents(node) { - if (checks.canDivideLikeTermConstantNodes(node)) { - const constantTermList = node.args.map(n => new Node.ConstantTerms(n)); + if (checks.canDivideLikeTermConstantNodes.canDivideLikeTermConstantNodes(node)) { + const constantTermList = node.args.map(n => new checks.canDivideLikeTermConstantNodes.createPowerNode(n)); // If we're multiplying constant nodes together, they all share the same // base. Get that from the first node. - const baseNode = constantTermList[0].getBaseNode(); + const baseNode = constantTermList[0].base; // The new exponent will be a sum of negative exponents (an operation, wrapped in // parens) e.g. 10^(3+-4+-5) - const exponentNodeList = constantTermList.map(p => p.getExponentNode(true)); + const exponentNodeList = constantTermList.map(p => p.exponent); const newExponent = Node.Creator.parenthesis( Node.Creator.operator('-', exponentNodeList)); - const newNode = Node.Creator.constantTerm(baseNode, newExponent, null); + const newNode = Node.Creator.powerTerm(baseNode, newExponent, null); return Node.Status.nodeChanged( ChangeTypes.COLLECT_CONSTANT_EXPONENTS, node, newNode); } diff --git a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js index 176d6e8a..99b6096f 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js @@ -8,12 +8,12 @@ const Node = require('../../node'); // Multiplies a list of nodes that are polynomial like terms. Returns a node. // The polynomial nodes should *not* have coefficients. (multiplying // coefficients is handled in collecting like terms for multiplication) -function multiplyLikeTerms(node, polynomialOnly = false) { +function multiplyLikeTerms(node, polynomialOnly=false) { if (!Node.Type.isOperator(node)) { return Node.Status.noChange(node); } let status; - if (!polynomialOnly && !checks.canMultiplyLikeTermConstantNodes(node)) { + if (!polynomialOnly && !checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { status = arithmeticSearch(node); if (status.hasChanged()) { status.changeType = ChangeTypes.MULTIPLY_COEFFICIENTS; @@ -34,7 +34,7 @@ function multiplyLikeTerms(node, polynomialOnly = false) { return Node.Status.noChange(node); } function multiplyPolynomialTerms(node) { - if (!checks.canMultiplyLikeTermPolynomialNodes(node) && !checks.canMultiplyLikeTermConstantNodes(node)) { + if (!checks.canMultiplyLikeTermPolynomialNodes(node) && !checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { return Node.Status.noChange(node); } const substeps = []; @@ -52,7 +52,13 @@ function multiplyPolynomialTerms(node) { // STEP 2: collect exponents to a single exponent sum // e.g. x^1 * x^3 -> x^(1+3) - status = collectExponents(newNode); + // e.g. 10^2 * 10^3 -> 10^(2+3) + if (checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { + status = collectConstantExponents(newNode); + } + else { + status = collectPolynomialExponents(newNode); + } substeps.push(status); newNode = Node.Status.resetChangeGroups(status.newNode); @@ -79,21 +85,22 @@ function multiplyPolynomialTerms(node) { } } -// Given a product of polynomial terms, changes any term with no exponent +// Given a product of polynomial or constant terms, changes any term with no exponent // into a term with an explicit exponent of 1. This is for pedagogy, and -// makes the adding coefficients step clearer. +// makes the adding exponents step clearer. // e.g. x^2 * x -> x^2 * x^1 +// e.g. 10^2 * 10 -> 10^2 * 10^1 // Returns a Node.Status object. function addOneExponent(node) { const newNode = clone(node); let change = false; let changeGroup = 1; - if (checks.canMultiplyLikeTermConstantNodes(node)) { + if (checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { newNode.args.forEach((child, i) => { - const constTerm = new Node.ConstantTerms(child); - if (!constTerm.getExponentNode()) { - newNode.args[i] = Node.Creator.constantTerm( - constTerm.getBaseNode(), + const constPowerTerm = new checks.canMultiplyLikeTermConstantNodes.createPowerNode(child); + if (!constPowerTerm.exponent) { + newNode.args[i] = Node.Creator.powerTerm( + constPowerTerm.base, Node.Creator.constant(1)); newNode.args[i].changeGroup = changeGroup; @@ -130,38 +137,39 @@ function addOneExponent(node) { return Node.Status.noChange(node); } } +// Given a product of constant terms, groups the exponents into a sum +// e.g. 10^2 * 10^3 -> 10^(2+3) +// Returns a Node.Status object. +function collectConstantExponents(node) { + const constantTermList = node.args.map(n => new checks.canMultiplyLikeTermConstantNodes.createPowerNode(n)); + // If we're multiplying constant nodes together, they all share the same + // base. Get that from the first node. + const baseNode = constantTermList[0].base; + // The new exponent will be a sum of exponents (an operation, wrapped in + // parens) e.g. 10^(3+4+5) + const exponentNodeList = constantTermList.map(p => p.exponent); + const newExponent = Node.Creator.parenthesis( + Node.Creator.operator('+', exponentNodeList)); + const newNode = Node.Creator.powerTerm(baseNode, newExponent, null); + return Node.Status.nodeChanged( + ChangeTypes.COLLECT_CONSTANT_EXPONENTS, node, newNode); +} // Given a product of polynomial terms, groups the exponents into a sum // e.g. x^2 * x^3 * x^1 -> x^(2 + 3 + 1) // Returns a Node.Status object. -function collectExponents(node) { - if (checks.canMultiplyLikeTermConstantNodes(node)) { - const constantTermList = node.args.map(n => new Node.ConstantTerms(n)); - // If we're multiplying constant nodes together, they all share the same - // base. Get that from the first node. - const baseNode = constantTermList[0].getBaseNode(); - // The new exponent will be a sum of exponents (an operation, wrapped in - // parens) e.g. 10^(3+4+5) - const exponentNodeList = constantTermList.map(p => p.getExponentNode(true)); - const newExponent = Node.Creator.parenthesis( - Node.Creator.operator('+', exponentNodeList)); - const newNode = Node.Creator.constantTerm(baseNode, newExponent, null); - return Node.Status.nodeChanged( - ChangeTypes.COLLECT_CONSTANT_EXPONENTS, node, newNode); - } - else { - const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); - // If we're multiplying polynomial nodes together, they all share the same - // symbol. Get that from the first node. - const symbolNode = polynomialTermList[0].getSymbolNode(); - // The new exponent will be a sum of exponents (an operation, wrapped in - // parens) e.g. x^(3+4+5) - const exponentNodeList = polynomialTermList.map(p => p.getExponentNode(true)); - const newExponent = Node.Creator.parenthesis( - Node.Creator.operator('+', exponentNodeList)); - const newNode = Node.Creator.polynomialTerm(symbolNode, newExponent, null); - return Node.Status.nodeChanged( - ChangeTypes.COLLECT_POLYNOMIAL_EXPONENTS, node, newNode); - } +function collectPolynomialExponents(node) { + const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); + // If we're multiplying polynomial nodes together, they all share the same + // symbol. Get that from the first node. + const symbolNode = polynomialTermList[0].getSymbolNode(); + // The new exponent will be a sum of exponents (an operation, wrapped in + // parens) e.g. x^(3+4+5) + const exponentNodeList = polynomialTermList.map(p => p.getExponentNode(true)); + const newExponent = Node.Creator.parenthesis( + Node.Creator.operator('+', exponentNodeList)); + const newNode = Node.Creator.polynomialTerm(symbolNode, newExponent, null); + return Node.Status.nodeChanged( + ChangeTypes.COLLECT_POLYNOMIAL_EXPONENTS, node, newNode); } module.exports = multiplyLikeTerms; diff --git a/test/canDivideLikeTermConstantNodes.test.js b/test/canDivideLikeTermConstantNodes.test.js index 0a593214..2b3b657d 100644 --- a/test/canDivideLikeTermConstantNodes.test.js +++ b/test/canDivideLikeTermConstantNodes.test.js @@ -4,10 +4,10 @@ const canDivideLikeTermConstantNodes = require('../lib/checks/canDivideLikeTermC const TestUtil = require('./TestUtil'); function testCanBeDividedConstants(expr, multipliable) { - TestUtil.testBooleanFunction(canDivideLikeTermConstantNodes, expr, multipliable); + TestUtil.testBooleanFunction(canDivideLikeTermConstantNodes.canDivideLikeTermConstantNodes, expr, multipliable); } -describe('can multiply like term constants', () => { +describe('can divide like term constants', () => { const tests = [ ['3^2 / 3^5', true], ['2^3 / 3^2', false], diff --git a/test/canMultiplyLikeTermConstantNodes.test.js b/test/canMultiplyLikeTermConstantNodes.test.js index 7fd3c75e..30f12088 100644 --- a/test/canMultiplyLikeTermConstantNodes.test.js +++ b/test/canMultiplyLikeTermConstantNodes.test.js @@ -4,7 +4,7 @@ const canMultiplyLikeTermConstantNodes = require('../lib/checks/canMultiplyLikeT const TestUtil = require('./TestUtil'); function testCanBeMultipliedConstants(expr, multipliable) { - TestUtil.testBooleanFunction(canMultiplyLikeTermConstantNodes, expr, multipliable); + TestUtil.testBooleanFunction(canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes, expr, multipliable); } describe('can multiply like term constants', () => { From 0496a72949ad8c865f6c084bab848c34e274fb54 Mon Sep 17 00:00:00 2001 From: bebbe Date: Mon, 24 Apr 2017 12:05:19 +0200 Subject: [PATCH 20/40] Changed so that it doesn't create power node but returns exponent and base instead. Also fixed a bug where it couldnt simplify e.g '10^3 * 10 * 10' because of collectLikeTerms --- lib/ChangeTypes.js | 1 - lib/TreeSearch.js | 5 +- lib/checks/canAddLikeTermPolynomialNodes.js | 1 + .../canMultiplyLikeTermConstantNodes.js | 69 +++++++++---------- .../collectAndCombineSearch/index.js | 4 ++ .../multiplyLikeTerms.js | 20 ++++-- .../collectAndCombineSearch.test.js | 7 +- 7 files changed, 60 insertions(+), 47 deletions(-) diff --git a/lib/ChangeTypes.js b/lib/ChangeTypes.js index 7e980c6f..e0ceaf34 100644 --- a/lib/ChangeTypes.js +++ b/lib/ChangeTypes.js @@ -43,7 +43,6 @@ module.exports = { COLLECT_LIKE_TERMS: 'COLLECT_LIKE_TERMS', // MULTIPLYING CONSTANT POWERS - // e.g. 10^2 * 10^3 -> 10^(2+3) COLLECT_CONSTANT_EXPONENTS: 'COLLECT_CONSTANT_EXPONENTS', diff --git a/lib/TreeSearch.js b/lib/TreeSearch.js index 17a07e6f..ad2423a6 100644 --- a/lib/TreeSearch.js +++ b/lib/TreeSearch.js @@ -3,7 +3,7 @@ const Node = require('./node'); const TreeSearch = {}; // Returns a function that performs a preorder search on the tree for the given -// simplifcation function +// simplification function TreeSearch.preOrder = function(simplificationFunction) { return function (node) { return search(simplificationFunction, node, true); @@ -11,7 +11,7 @@ TreeSearch.preOrder = function(simplificationFunction) { }; // Returns a function that performs a postorder search on the tree for the given -// simplifcation function +// simplification function TreeSearch.postOrder = function(simplificationFunction) { return function (node) { return search(simplificationFunction, node, false); @@ -28,6 +28,7 @@ function search(simplificationFunction, node, preOrder) { return status; } } + if (Node.Type.isConstant(node) || Node.Type.isSymbol(node)) { return Node.Status.noChange(node); } diff --git a/lib/checks/canAddLikeTermPolynomialNodes.js b/lib/checks/canAddLikeTermPolynomialNodes.js index c5e87d0d..6c9ac1ca 100644 --- a/lib/checks/canAddLikeTermPolynomialNodes.js +++ b/lib/checks/canAddLikeTermPolynomialNodes.js @@ -19,6 +19,7 @@ function canAddLikeTermPolynomialNodes(node) { const firstTerm = polynomialTermList[0]; const sharedSymbol = firstTerm.getSymbolName(); const sharedExponentNode = firstTerm.getExponentNode(true); + const restTerms = polynomialTermList.slice(1); return restTerms.every(term => { const haveSameSymbol = sharedSymbol === term.getSymbolName(); diff --git a/lib/checks/canMultiplyLikeTermConstantNodes.js b/lib/checks/canMultiplyLikeTermConstantNodes.js index a69d524b..7bd8f165 100644 --- a/lib/checks/canMultiplyLikeTermConstantNodes.js +++ b/lib/checks/canMultiplyLikeTermConstantNodes.js @@ -2,62 +2,59 @@ const Node = require('../node'); const NodeType = require('../node/Type'); // Returns true if node is a multiplication of constant power nodes -// where you can combine their exponents, e.g. 10^2 * 10^4 * 10 can become 10^7 - +// where you can combine their exponents, e.g. 10^2 * 10^4 * 10 can become 10^7. +// The node can either be on form c^n or c, as long as c is the same for all. function canMultiplyLikeTermConstantNodes(node) { if (!Node.Type.isOperator(node) || node.op !== '*') { return false; } const args = node.args; - if (!args.every(n => isPowerTerm(n))) { + if (!args.every(n => isConstantOrConstantPower(n))) { return false; } - const constantTermList = node.args.map(n => createPowerNode(n)); - const firstTerm = constantTermList[0]; - const restTerms = constantTermList.slice(1); + const constantTermList = node.map(n => getBaseNode(n)); + const firstTerm = constantTermList.args[0]; + const restTerms = constantTermList.args.slice(1); // they're considered like terms if they have the same base value return restTerms.every(term => getBaseValue(firstTerm) === getBaseValue(term)); } -function createPowerNode(node) { - if (NodeType.isOperator(node)) { - if (node.op === '^') { - const constantNode = node.args[0]; - if (!NodeType.isConstant(constantNode)) { - throw Error('Expected constant term, got ' + constantNode); - } - node.base = constantNode; - node.exponent = node.args[1]; - return node; - } - else { - throw Error('Expected power sign, got ' + node.op); - } - } - else if (NodeType.isConstant(node)) { - node.base = node; - return node; +// GETTER FUNCTIONS +// Returns the base node if the node is on power form, else returns the node as it is constant. +function getBaseNode(node) { + if (node.args) { + return node.args[0]; } else { - throw Error('Unsupported node type: ' + node.type); + return node; } } - +// Returns the value of the base for comparison function getBaseValue(node) { - return node.base.value; + return node.value; } - -function isPowerTerm(node) { - try { - // will throw error if node isn't constant power term - new createPowerNode(node); +// First if returns false so that addOneExponent can take care of the rest, by adding +// one to the exponent if there are none. +function getExponentNode(node) { + if (node.args === undefined || !node.args[1]) { + return false; + } + else { + return node.args[1]; + } +} +// Checks if the node is an constant or a power with constant base. +function isConstantOrConstantPower(node) { + if ((NodeType.isOperator(node, '^') && NodeType.isConstant(node.args[0])) || + NodeType.isConstant(node)) { return true; } - catch (err) { + else { return false; } } + module.exports = { canMultiplyLikeTermConstantNodes, - createPowerNode -}; - + getBaseNode, + getExponentNode +}; \ No newline at end of file diff --git a/lib/simplifyExpression/collectAndCombineSearch/index.js b/lib/simplifyExpression/collectAndCombineSearch/index.js index 33ddb342..c46365f5 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/index.js +++ b/lib/simplifyExpression/collectAndCombineSearch/index.js @@ -1,6 +1,7 @@ // Collects and combines like terms const addLikeTerms = require('./addLikeTerms'); +const checks = require('../../checks'); const clone = require('../../util/clone'); const multiplyLikeTerms = require('./multiplyLikeTerms'); @@ -34,6 +35,9 @@ function collectAndCombineLikeTerms(node) { else if (node.op === '*') { // collect and combine involves there being coefficients pulled the front // e.g. 2x * x^2 * 5x => (2*5) * (x * x^2 * x) => ... => 10 x^4 + if (checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { + return multiplyLikeTerms(node, true); + } const status = collectAndCombineOperation(node); if (status.hasChanged()) { // make sure there's no * between the coefficient and the symbol part diff --git a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js index 99b6096f..c3056d2e 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js @@ -5,7 +5,8 @@ const multiplyFractionsSearch = require('../multiplyFractionsSearch'); const ChangeTypes = require('../../ChangeTypes'); const Node = require('../../node'); -// Multiplies a list of nodes that are polynomial like terms. Returns a node. + +// Multiplies a list of nodes that are polynomial or constant power like terms. Returns a node. // The polynomial nodes should *not* have coefficients. (multiplying // coefficients is handled in collecting like terms for multiplication) function multiplyLikeTerms(node, polynomialOnly=false) { @@ -13,12 +14,14 @@ function multiplyLikeTerms(node, polynomialOnly=false) { return Node.Status.noChange(node); } let status; + if (!polynomialOnly && !checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { status = arithmeticSearch(node); if (status.hasChanged()) { status.changeType = ChangeTypes.MULTIPLY_COEFFICIENTS; return status; } + status = multiplyFractionsSearch(node); if (status.hasChanged()) { status.changeType = ChangeTypes.MULTIPLY_COEFFICIENTS; @@ -31,8 +34,10 @@ function multiplyLikeTerms(node, polynomialOnly=false) { status.changeType = ChangeTypes.MULTIPLY_COEFFICIENTS; return status; } + return Node.Status.noChange(node); } + function multiplyPolynomialTerms(node) { if (!checks.canMultiplyLikeTermPolynomialNodes(node) && !checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { return Node.Status.noChange(node); @@ -94,13 +99,13 @@ function multiplyPolynomialTerms(node) { function addOneExponent(node) { const newNode = clone(node); let change = false; + let changeGroup = 1; if (checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { newNode.args.forEach((child, i) => { - const constPowerTerm = new checks.canMultiplyLikeTermConstantNodes.createPowerNode(child); - if (!constPowerTerm.exponent) { + if (!checks.canMultiplyLikeTermConstantNodes.getExponentNode(child)) { newNode.args[i] = Node.Creator.powerTerm( - constPowerTerm.base, + checks.canMultiplyLikeTermConstantNodes.getBaseNode(child), Node.Creator.constant(1)); newNode.args[i].changeGroup = changeGroup; @@ -141,13 +146,12 @@ function addOneExponent(node) { // e.g. 10^2 * 10^3 -> 10^(2+3) // Returns a Node.Status object. function collectConstantExponents(node) { - const constantTermList = node.args.map(n => new checks.canMultiplyLikeTermConstantNodes.createPowerNode(n)); // If we're multiplying constant nodes together, they all share the same // base. Get that from the first node. - const baseNode = constantTermList[0].base; + const baseNode = checks.canMultiplyLikeTermConstantNodes.getBaseNode(node.args[0]); // The new exponent will be a sum of exponents (an operation, wrapped in // parens) e.g. 10^(3+4+5) - const exponentNodeList = constantTermList.map(p => p.exponent); + const exponentNodeList = node.args.map(p => checks.canMultiplyLikeTermConstantNodes.getExponentNode(p)); const newExponent = Node.Creator.parenthesis( Node.Creator.operator('+', exponentNodeList)); const newNode = Node.Creator.powerTerm(baseNode, newExponent, null); @@ -159,9 +163,11 @@ function collectConstantExponents(node) { // Returns a Node.Status object. function collectPolynomialExponents(node) { const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); + // If we're multiplying polynomial nodes together, they all share the same // symbol. Get that from the first node. const symbolNode = polynomialTermList[0].getSymbolNode(); + // The new exponent will be a sum of exponents (an operation, wrapped in // parens) e.g. x^(3+4+5) const exponentNodeList = polynomialTermList.map(p => p.getExponentNode(true)); diff --git a/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js b/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js index c7116b29..df79cb48 100644 --- a/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js +++ b/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js @@ -64,6 +64,11 @@ describe('combineConstantPowerTerms multiplication', function() { '2^(1 + 3)', '2^4'], ], + ['3^3 * 3 * 3', + ['3^3 * 3^1 * 3^1', + '3^(3 + 1 + 1)', + '3^5'], + ], ]; tests.forEach(t => testCollectAndCombineSubsteps(t[0], t[1], t[2])); }); @@ -79,7 +84,7 @@ describe('collectAndCombineSearch with no substeps', function () { describe('collect and multiply like terms', function() { const tests = [ ['10^3 * 10^2', '10^5'], - ['2^4 * 2 * 2^4', '2^9'] + ['2^4 * 2 * 2^4 * 2', '2^10'] ]; tests.forEach(t => testSimpleCollectAndCombineSearch(t[0], t[1])); }); From 922f77ee70cfc9c05263a6011a087364cff67e6d Mon Sep 17 00:00:00 2001 From: bebbe Date: Wed, 26 Apr 2017 09:30:54 +0200 Subject: [PATCH 21/40] Changed to work the same as in other branch --- .../canMultiplyLikeTermConstantNodes.js | 69 +++++++++---------- .../collectAndCombineSearch/index.js | 3 + 2 files changed, 36 insertions(+), 36 deletions(-) diff --git a/lib/checks/canMultiplyLikeTermConstantNodes.js b/lib/checks/canMultiplyLikeTermConstantNodes.js index a69d524b..7bd8f165 100644 --- a/lib/checks/canMultiplyLikeTermConstantNodes.js +++ b/lib/checks/canMultiplyLikeTermConstantNodes.js @@ -2,62 +2,59 @@ const Node = require('../node'); const NodeType = require('../node/Type'); // Returns true if node is a multiplication of constant power nodes -// where you can combine their exponents, e.g. 10^2 * 10^4 * 10 can become 10^7 - +// where you can combine their exponents, e.g. 10^2 * 10^4 * 10 can become 10^7. +// The node can either be on form c^n or c, as long as c is the same for all. function canMultiplyLikeTermConstantNodes(node) { if (!Node.Type.isOperator(node) || node.op !== '*') { return false; } const args = node.args; - if (!args.every(n => isPowerTerm(n))) { + if (!args.every(n => isConstantOrConstantPower(n))) { return false; } - const constantTermList = node.args.map(n => createPowerNode(n)); - const firstTerm = constantTermList[0]; - const restTerms = constantTermList.slice(1); + const constantTermList = node.map(n => getBaseNode(n)); + const firstTerm = constantTermList.args[0]; + const restTerms = constantTermList.args.slice(1); // they're considered like terms if they have the same base value return restTerms.every(term => getBaseValue(firstTerm) === getBaseValue(term)); } -function createPowerNode(node) { - if (NodeType.isOperator(node)) { - if (node.op === '^') { - const constantNode = node.args[0]; - if (!NodeType.isConstant(constantNode)) { - throw Error('Expected constant term, got ' + constantNode); - } - node.base = constantNode; - node.exponent = node.args[1]; - return node; - } - else { - throw Error('Expected power sign, got ' + node.op); - } - } - else if (NodeType.isConstant(node)) { - node.base = node; - return node; +// GETTER FUNCTIONS +// Returns the base node if the node is on power form, else returns the node as it is constant. +function getBaseNode(node) { + if (node.args) { + return node.args[0]; } else { - throw Error('Unsupported node type: ' + node.type); + return node; } } - +// Returns the value of the base for comparison function getBaseValue(node) { - return node.base.value; + return node.value; } - -function isPowerTerm(node) { - try { - // will throw error if node isn't constant power term - new createPowerNode(node); +// First if returns false so that addOneExponent can take care of the rest, by adding +// one to the exponent if there are none. +function getExponentNode(node) { + if (node.args === undefined || !node.args[1]) { + return false; + } + else { + return node.args[1]; + } +} +// Checks if the node is an constant or a power with constant base. +function isConstantOrConstantPower(node) { + if ((NodeType.isOperator(node, '^') && NodeType.isConstant(node.args[0])) || + NodeType.isConstant(node)) { return true; } - catch (err) { + else { return false; } } + module.exports = { canMultiplyLikeTermConstantNodes, - createPowerNode -}; - + getBaseNode, + getExponentNode +}; \ No newline at end of file diff --git a/lib/simplifyExpression/collectAndCombineSearch/index.js b/lib/simplifyExpression/collectAndCombineSearch/index.js index d16ee496..31d08a46 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/index.js +++ b/lib/simplifyExpression/collectAndCombineSearch/index.js @@ -51,6 +51,9 @@ function collectAndCombineLikeTerms(node) { return multiplyLikeTerms(node, true); } else if (node.op === '/') { + // we might also be able to just combine like terms + // e.g 10^6 / 10^3 => ... => 10^3 + // e.g x^5 / x^2 => ... => x^3 return divideLikeTerms(node, true); } else { From 9781053dae7cd0c951170c068662702ad3083bed Mon Sep 17 00:00:00 2001 From: bebbe Date: Wed, 26 Apr 2017 09:51:27 +0200 Subject: [PATCH 22/40] Changed structure when dividing like terms. Pretty much does the same thing as "multiplyLikeTerms" but returns exponent with "-" instead of "+" --- lib/ChangeTypes.js | 2 + lib/checks/canDivideLikeTermConstantNodes.js | 62 ++++++------ .../divideLikeTerms.js | 95 ++++++++++--------- 3 files changed, 83 insertions(+), 76 deletions(-) diff --git a/lib/ChangeTypes.js b/lib/ChangeTypes.js index ccc1d8cd..6a48efb0 100644 --- a/lib/ChangeTypes.js +++ b/lib/ChangeTypes.js @@ -69,6 +69,8 @@ module.exports = { MULTIPLY_COEFFICIENTS: 'MULTIPLY_COEFFICIENTS', // e.g. 2x * x -> 2x ^ 2 MULTIPLY_POLYNOMIAL_TERMS: 'MULTIPLY_POLYNOMIAL_TERMS', + // e.g x^5 / x^3 -> x^2 + DIVIDE_POLYNOMIAL_TERMS: 'DIVIDE_POLYNOMIAL_TERMS', // FRACTIONS diff --git a/lib/checks/canDivideLikeTermConstantNodes.js b/lib/checks/canDivideLikeTermConstantNodes.js index df9c50c5..75e3a916 100644 --- a/lib/checks/canDivideLikeTermConstantNodes.js +++ b/lib/checks/canDivideLikeTermConstantNodes.js @@ -9,55 +9,53 @@ function canDivideLikeTermConstantNodes(node) { return false; } const args = node.args; - if (!args.every(n => isPowerTerm(n))) { + if (!args.every(n => isConstantOrConstantPower(n))) { return false; } - const constantTermList = node.args.map(n => createPowerNode(n)); - const firstTerm = constantTermList[0]; - const restTerms = constantTermList.slice(1); + const constantTermList = node.map(n => getBaseNode(n)); + const firstTerm = constantTermList.args[0]; + const restTerms = constantTermList.args.slice(1); // they're considered like terms if they have the same base value return restTerms.every(term => getBaseValue(firstTerm) === getBaseValue(term)); } -function createPowerNode(node) { - if (NodeType.isOperator(node)) { - if (node.op === '^') { - const constantNode = node.args[0]; - if (!NodeType.isConstant(constantNode)) { - throw Error('Expected constant term, got ' + constantNode); - } - node.base = constantNode; - node.exponent = node.args[1]; - return node; - } - else { - throw Error('Expected power sign, got ' + node.op); - } - } - else if (NodeType.isConstant(node)) { - node.base = node; - return node; +// GETTER FUNCTIONS +// Returns the base node if the node is on power form, else returns the node as it is constant. +function getBaseNode(node) { + if (node.args) { + return node.args[0]; } else { - throw Error('Unsupported node type: ' + node.type); + return node; } } - +// Returns the value of the base for comparison function getBaseValue(node) { - return node.base.value; + return node.value; } - -function isPowerTerm(node) { - try { - // will throw error if node isn't constant power term - new createPowerNode(node); +// First if returns false so that addOneExponent can take care of the rest, by adding +// one to the exponent if there are none. +function getExponentNode(node) { + if (node.args === undefined || !node.args[1]) { + return false; + } + else { + return node.args[1]; + } +} +// Checks if the node is an constant or a power with constant base. +function isConstantOrConstantPower(node) { + if ((NodeType.isOperator(node, '^') && NodeType.isConstant(node.args[0])) || + NodeType.isConstant(node)) { return true; } - catch (err) { + else { return false; } } + module.exports = { canDivideLikeTermConstantNodes, - createPowerNode + getBaseNode, + getExponentNode }; diff --git a/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js index 305d7567..8e8338e3 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js @@ -43,21 +43,26 @@ function dividePolynomialTerms(node) { newNode = Node.Status.resetChangeGroups(status.newNode); } - // STEP 2: collect exponents to a single exponent sum + // STEP 2: collect exponents to a single exponent difference // e.g. x^1 / x^3 -> x^(1 + -3) - status = collectExponents(newNode); + if (checks.canDivideLikeTermConstantNodes.canDivideLikeTermConstantNodes(node)) { + status = collectConstantExponents(newNode); + } + else { + status = collectPolynomialExponents(newNode); + } substeps.push(status); newNode = Node.Status.resetChangeGroups(status.newNode); - // STEP 3: add exponents together. + // STEP 3: calculate difference of exponents. // NOTE: This might not be a step if the exponents aren't all constants, // but this case isn't that common and can be caught in other steps. - // e.g. x^(2+4+z) + // e.g. x^(2-4-z) // TODO: handle fractions, combining and collecting like terms, etc, here - const exponentSum = newNode.args[1].content; - const sumStatus = arithmeticSearch(exponentSum); - if (sumStatus.hasChanged()) { - status = Node.Status.childChanged(newNode, sumStatus, 1); + const exponentDiff = newNode.args[1].content; + const diffStatus = arithmeticSearch(exponentDiff); + if (diffStatus.hasChanged()) { + status = Node.Status.childChanged(newNode, diffStatus, 1); substeps.push(status); newNode = Node.Status.resetChangeGroups(status.newNode); } @@ -67,7 +72,7 @@ function dividePolynomialTerms(node) { } else { return Node.Status.nodeChanged( - ChangeTypes.MULTIPLY_POLYNOMIAL_TERMS, + ChangeTypes.DIVIDE_POLYNOMIAL_TERMS, node, newNode, true, substeps); } } @@ -80,13 +85,13 @@ function dividePolynomialTerms(node) { function addOneExponent(node) { const newNode = clone(node); let change = false; + let changeGroup = 1; if (checks.canDivideLikeTermConstantNodes.canDivideLikeTermConstantNodes(node)) { newNode.args.forEach((child, i) => { - const constTerm = new checks.canDivideLikeTermConstantNodes.createPowerNode(child); - if (!constTerm.exponent) { - newNode.args[i] = Node.Creator.constantTerm( - constTerm.base, + if (!checks.canDivideLikeTermConstantNodes.getExponentNode(child)) { + newNode.args[i] = Node.Creator.powerTerm( + checks.canDivideLikeTermConstantNodes.getBaseNode(child), Node.Creator.constant(1)); newNode.args[i].changeGroup = changeGroup; @@ -123,38 +128,40 @@ function addOneExponent(node) { return Node.Status.noChange(node); } } -// Given a product of polynomial terms, groups the exponents into a sum -// e.g. x^2 * x^3 * x^1 -> x^(2 + 3 + 1) +// Given a division of constant terms, groups the exponents into a difference +// e.g. 10^5 / 10^3 -> 10^(5 - 3) // Returns a Node.Status object. -function collectExponents(node) { - if (checks.canDivideLikeTermConstantNodes.canDivideLikeTermConstantNodes(node)) { - const constantTermList = node.args.map(n => new checks.canDivideLikeTermConstantNodes.createPowerNode(n)); - // If we're multiplying constant nodes together, they all share the same - // base. Get that from the first node. - const baseNode = constantTermList[0].base; - // The new exponent will be a sum of negative exponents (an operation, wrapped in - // parens) e.g. 10^(3+-4+-5) - const exponentNodeList = constantTermList.map(p => p.exponent); - const newExponent = Node.Creator.parenthesis( - Node.Creator.operator('-', exponentNodeList)); - const newNode = Node.Creator.powerTerm(baseNode, newExponent, null); - return Node.Status.nodeChanged( - ChangeTypes.COLLECT_CONSTANT_EXPONENTS, node, newNode); - } - else { - const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); - // If we're multiplying polynomial nodes together, they all share the same - // symbol. Get that from the first node. - const symbolNode = polynomialTermList[0].getSymbolNode(); - // The new exponent will be a sum of exponents (an operation, wrapped in - // parens) e.g. x^(3+4+5) - const exponentNodeList = polynomialTermList.map(p => p.getExponentNode(true)); - const newExponent = Node.Creator.parenthesis( - Node.Creator.operator('-', exponentNodeList)); - const newNode = Node.Creator.polynomialTerm(symbolNode, newExponent, null); - return Node.Status.nodeChanged( - ChangeTypes.COLLECT_POLYNOMIAL_EXPONENTS, node, newNode); - } +function collectConstantExponents(node) { + // If we're dividing constant nodes together, they all share the same + // base. Get that from the first node. + const baseNode = checks.canDivideLikeTermConstantNodes.getBaseNode(node.args[0]); + // The new exponent will be a difference of exponents (an operation, wrapped in + // parens) e.g. 10^(5-3) + const exponentNodeList = node.args.map(p => checks.canDivideLikeTermConstantNodes.getExponentNode(p)); + const newExponent = Node.Creator.parenthesis( + Node.Creator.operator('-', exponentNodeList)); + const newNode = Node.Creator.powerTerm(baseNode, newExponent, null); + return Node.Status.nodeChanged( + ChangeTypes.COLLECT_CONSTANT_EXPONENTS, node, newNode); +} +// Given a division of polynomial terms, groups the exponents into a difference +// e.g. x^5 / x^3 -> x^(5 - 3) +// Returns a Node.Status object. +function collectPolynomialExponents(node) { + const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); + + // If we're dividing polynomial nodes together, they all share the same + // symbol. Get that from the first node. + const symbolNode = polynomialTermList[0].getSymbolNode(); + + // The new exponent will be a difference of exponents (an operation, wrapped in + // parens) e.g. x^(5-3) + const exponentNodeList = polynomialTermList.map(p => p.getExponentNode(true)); + const newExponent = Node.Creator.parenthesis( + Node.Creator.operator('-', exponentNodeList)); + const newNode = Node.Creator.polynomialTerm(symbolNode, newExponent, null); + return Node.Status.nodeChanged( + ChangeTypes.COLLECT_POLYNOMIAL_EXPONENTS, node, newNode); } module.exports = divideLikeTerms; From 00ca6093633f3da90aad4896d8363cd83f5699d7 Mon Sep 17 00:00:00 2001 From: bebbe Date: Wed, 26 Apr 2017 11:49:43 +0200 Subject: [PATCH 23/40] Added more tests --- .../collectAndCombineSearch.test.js | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js b/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js index 44d4fbc4..ea06eb90 100644 --- a/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js +++ b/test/simplifyExpression/collectAndCombineSearch/collectAndCombineSearch.test.js @@ -32,6 +32,22 @@ describe('combinePolynomialTerms multiplication', function() { tests.forEach(t => testCollectAndCombineSubsteps(t[0], t[1], t[2])); }); +describe('combinePolynomialPowerTerms division', function() { + const tests = [ + ['x^2 / x', + ['x^2 / (x^1)', + 'x^(2 - 1)', + 'x^1'], + ], + ['y / y^3', + ['y^1 / (y^3)', + 'y^(1 - 3)', + 'y^-2'], + ], + ]; + tests.forEach(t => testCollectAndCombineSubsteps(t[0], t[1], t[2])); +}); + describe('combinePolynomialTerms addition', function() { const tests = [ ['x+x', @@ -73,6 +89,22 @@ describe('combineConstantPowerTerms multiplication', function() { tests.forEach(t => testCollectAndCombineSubsteps(t[0], t[1], t[2])); }); +describe('combineConstantPowerTerms division', function() { + const tests = [ + ['10^2 / 10', + ['10^2 / (10^1)', + '10^(2 - 1)', + '10^1'], + ], + ['2 / 2^3', + ['2^1 / (2^3)', + '2^(1 - 3)', + '2^-2'], + ], + ]; + tests.forEach(t => testCollectAndCombineSubsteps(t[0], t[1], t[2])); +}); + describe('collectAndCombineSearch with no substeps', function () { const tests = [ ['2x + 4x + x', '7x'], @@ -92,7 +124,11 @@ describe('collect and multiply like terms', function() { describe('collect and divide multiply like terms', function() { const tests = [ ['10^5 / 10^2', '10^3'], - ['2^4 / 2^2', '2^2'] + ['2^4 / 2^2', '2^2'], + ['2^3 / 2^4', '2^-1'], + ['x^3 / x^4', 'x^-1'], + ['y^5 / y^2', 'y^3'], + ['z^4 / z^2', 'z^2'], ]; tests.forEach(t => testSimpleCollectAndCombineSearch(t[0], t[1])); }); From fbe40a0de8ff98d9f4a6ce6e2a97d755fc90e279 Mon Sep 17 00:00:00 2001 From: bebbe Date: Thu, 27 Apr 2017 09:12:25 +0200 Subject: [PATCH 24/40] Changed stuff according to the comments. Also added module with helper functions to make other things look neater. --- .../canMultiplyLikeTermConstantNodes.js | 52 +++---------------- .../ConstantOrPowerTerm.js | 45 ++++++++++++++++ .../collectAndCombineSearch/index.js | 2 +- .../multiplyLikeTerms.js | 20 +++---- test/canMultiplyLikeTermConstantNodes.test.js | 2 +- 5 files changed, 65 insertions(+), 56 deletions(-) create mode 100644 lib/simplifyExpression/collectAndCombineSearch/ConstantOrPowerTerm.js diff --git a/lib/checks/canMultiplyLikeTermConstantNodes.js b/lib/checks/canMultiplyLikeTermConstantNodes.js index 7bd8f165..1b941951 100644 --- a/lib/checks/canMultiplyLikeTermConstantNodes.js +++ b/lib/checks/canMultiplyLikeTermConstantNodes.js @@ -1,5 +1,5 @@ +const ConstantOrPowerTerm = require('../simplifyExpression/collectAndCombineSearch/ConstantOrPowerTerm'); const Node = require('../node'); -const NodeType = require('../node/Type'); // Returns true if node is a multiplication of constant power nodes // where you can combine their exponents, e.g. 10^2 * 10^4 * 10 can become 10^7. @@ -9,52 +9,14 @@ function canMultiplyLikeTermConstantNodes(node) { return false; } const args = node.args; - if (!args.every(n => isConstantOrConstantPower(n))) { + if (!args.every(n => ConstantOrPowerTerm.isConstantOrConstantPower(n))) { return false; } - const constantTermList = node.map(n => getBaseNode(n)); - const firstTerm = constantTermList.args[0]; - const restTerms = constantTermList.args.slice(1); + const constantTermList = node.args.map(n => ConstantOrPowerTerm.getBaseNode(n)); + const firstTerm = constantTermList[0]; + const restTerms = constantTermList.slice(1); // they're considered like terms if they have the same base value - return restTerms.every(term => getBaseValue(firstTerm) === getBaseValue(term)); -} -// GETTER FUNCTIONS -// Returns the base node if the node is on power form, else returns the node as it is constant. -function getBaseNode(node) { - if (node.args) { - return node.args[0]; - } - else { - return node; - } -} -// Returns the value of the base for comparison -function getBaseValue(node) { - return node.value; -} -// First if returns false so that addOneExponent can take care of the rest, by adding -// one to the exponent if there are none. -function getExponentNode(node) { - if (node.args === undefined || !node.args[1]) { - return false; - } - else { - return node.args[1]; - } -} -// Checks if the node is an constant or a power with constant base. -function isConstantOrConstantPower(node) { - if ((NodeType.isOperator(node, '^') && NodeType.isConstant(node.args[0])) || - NodeType.isConstant(node)) { - return true; - } - else { - return false; - } + return restTerms.every(term => ConstantOrPowerTerm.getBaseValue(firstTerm) === ConstantOrPowerTerm.getBaseValue(term)); } -module.exports = { - canMultiplyLikeTermConstantNodes, - getBaseNode, - getExponentNode -}; \ No newline at end of file +module.exports = canMultiplyLikeTermConstantNodes; diff --git a/lib/simplifyExpression/collectAndCombineSearch/ConstantOrPowerTerm.js b/lib/simplifyExpression/collectAndCombineSearch/ConstantOrPowerTerm.js new file mode 100644 index 00000000..b4e0b0a6 --- /dev/null +++ b/lib/simplifyExpression/collectAndCombineSearch/ConstantOrPowerTerm.js @@ -0,0 +1,45 @@ +const NodeType = require('../../node/Type'); + +// Returns the base if the node is on power form, else returns the node as it is constant. +// e.g 2^4 returns 2 +// e.g 3 returns 3, as that is the node +function getBaseNode(node) { + if (node.args) { + return node.args[0]; + } + else { + return node; + } +} + +// Returns the value of the base. +function getBaseValue(node) { + return node.value; +} + +// Returns the node that is an exponent to a constant, or a constant node with +// value 1 if there's no exponent. +// e.g. on the node representing 2^3, returns a constant node with value 3 +// e.g. on the node representing 2, returns a constant node with value 1 (since 2 = 2^1) +function getExponentNode(node) { + if (node.args === undefined || !node.args[1]) { + return false; + } + else { + return node.args[1]; + } +} + +// Checks if the node is an constant or a power with constant base. +// e.g. 2^3 is a constant power node, 5 is a constant node, x and x^2 are not +function isConstantOrConstantPower(node) { + return ((NodeType.isOperator(node, '^') && NodeType.isConstant(node.args[0])) || + NodeType.isConstant(node)); +} + +module.exports = { + getBaseNode, + getBaseValue, + getExponentNode, + isConstantOrConstantPower +}; diff --git a/lib/simplifyExpression/collectAndCombineSearch/index.js b/lib/simplifyExpression/collectAndCombineSearch/index.js index c46365f5..a8cdd541 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/index.js +++ b/lib/simplifyExpression/collectAndCombineSearch/index.js @@ -35,7 +35,7 @@ function collectAndCombineLikeTerms(node) { else if (node.op === '*') { // collect and combine involves there being coefficients pulled the front // e.g. 2x * x^2 * 5x => (2*5) * (x * x^2 * x) => ... => 10 x^4 - if (checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { + if (checks.canMultiplyLikeTermConstantNodes(node)) { return multiplyLikeTerms(node, true); } const status = collectAndCombineOperation(node); diff --git a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js index c3056d2e..0de67710 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js @@ -1,12 +1,14 @@ const arithmeticSearch = require('../arithmeticSearch'); const checks = require('../../checks'); const clone = require('../../util/clone'); +const ConstantOrPowerTerm = require('./ConstantOrPowerTerm'); const multiplyFractionsSearch = require('../multiplyFractionsSearch'); const ChangeTypes = require('../../ChangeTypes'); const Node = require('../../node'); -// Multiplies a list of nodes that are polynomial or constant power like terms. Returns a node. +// Multiplies a list of nodes that are polynomial or constant power like terms. +// Returns a node. // The polynomial nodes should *not* have coefficients. (multiplying // coefficients is handled in collecting like terms for multiplication) function multiplyLikeTerms(node, polynomialOnly=false) { @@ -15,7 +17,7 @@ function multiplyLikeTerms(node, polynomialOnly=false) { } let status; - if (!polynomialOnly && !checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { + if (!polynomialOnly && !checks.canMultiplyLikeTermConstantNodes(node)) { status = arithmeticSearch(node); if (status.hasChanged()) { status.changeType = ChangeTypes.MULTIPLY_COEFFICIENTS; @@ -39,7 +41,7 @@ function multiplyLikeTerms(node, polynomialOnly=false) { } function multiplyPolynomialTerms(node) { - if (!checks.canMultiplyLikeTermPolynomialNodes(node) && !checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { + if (!checks.canMultiplyLikeTermPolynomialNodes(node) && !checks.canMultiplyLikeTermConstantNodes(node)) { return Node.Status.noChange(node); } const substeps = []; @@ -58,7 +60,7 @@ function multiplyPolynomialTerms(node) { // STEP 2: collect exponents to a single exponent sum // e.g. x^1 * x^3 -> x^(1+3) // e.g. 10^2 * 10^3 -> 10^(2+3) - if (checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { + if (checks.canMultiplyLikeTermConstantNodes(node)) { status = collectConstantExponents(newNode); } else { @@ -101,11 +103,11 @@ function addOneExponent(node) { let change = false; let changeGroup = 1; - if (checks.canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes(node)) { + if (checks.canMultiplyLikeTermConstantNodes(node)) { newNode.args.forEach((child, i) => { - if (!checks.canMultiplyLikeTermConstantNodes.getExponentNode(child)) { + if (!ConstantOrPowerTerm.getExponentNode(child)) { newNode.args[i] = Node.Creator.powerTerm( - checks.canMultiplyLikeTermConstantNodes.getBaseNode(child), + ConstantOrPowerTerm.getBaseNode(child), Node.Creator.constant(1)); newNode.args[i].changeGroup = changeGroup; @@ -148,10 +150,10 @@ function addOneExponent(node) { function collectConstantExponents(node) { // If we're multiplying constant nodes together, they all share the same // base. Get that from the first node. - const baseNode = checks.canMultiplyLikeTermConstantNodes.getBaseNode(node.args[0]); + const baseNode = ConstantOrPowerTerm.getBaseNode(node.args[0]); // The new exponent will be a sum of exponents (an operation, wrapped in // parens) e.g. 10^(3+4+5) - const exponentNodeList = node.args.map(p => checks.canMultiplyLikeTermConstantNodes.getExponentNode(p)); + const exponentNodeList = node.args.map(p => ConstantOrPowerTerm.getExponentNode(p)); const newExponent = Node.Creator.parenthesis( Node.Creator.operator('+', exponentNodeList)); const newNode = Node.Creator.powerTerm(baseNode, newExponent, null); diff --git a/test/canMultiplyLikeTermConstantNodes.test.js b/test/canMultiplyLikeTermConstantNodes.test.js index 30f12088..7fd3c75e 100644 --- a/test/canMultiplyLikeTermConstantNodes.test.js +++ b/test/canMultiplyLikeTermConstantNodes.test.js @@ -4,7 +4,7 @@ const canMultiplyLikeTermConstantNodes = require('../lib/checks/canMultiplyLikeT const TestUtil = require('./TestUtil'); function testCanBeMultipliedConstants(expr, multipliable) { - TestUtil.testBooleanFunction(canMultiplyLikeTermConstantNodes.canMultiplyLikeTermConstantNodes, expr, multipliable); + TestUtil.testBooleanFunction(canMultiplyLikeTermConstantNodes, expr, multipliable); } describe('can multiply like term constants', () => { From 57a2a9aacf5c2d600af9dd039e68a9ade546acf5 Mon Sep 17 00:00:00 2001 From: bebbe Date: Thu, 27 Apr 2017 09:24:07 +0200 Subject: [PATCH 25/40] Updated to match structure of multiplication of powers --- lib/checks/canDivideLikeTermConstantNodes.js | 53 +++---------------- .../divideLikeTerms.js | 21 ++++---- test/canDivideLikeTermConstantNodes.test.js | 2 +- 3 files changed, 21 insertions(+), 55 deletions(-) diff --git a/lib/checks/canDivideLikeTermConstantNodes.js b/lib/checks/canDivideLikeTermConstantNodes.js index 75e3a916..1fc8fbf0 100644 --- a/lib/checks/canDivideLikeTermConstantNodes.js +++ b/lib/checks/canDivideLikeTermConstantNodes.js @@ -1,61 +1,24 @@ +const ConstantOrPowerTerm = require('../simplifyExpression/collectAndCombineSearch/ConstantOrPowerTerm'); const Node = require('../node'); -const NodeType = require('../node/Type'); // Returns true if node is a division of constant power nodes // where you can combine their exponents, e.g. 10^4 / 10^2 can become 10^2 +// The node can be on the form c^n or c, as long is c is the same for all function canDivideLikeTermConstantNodes(node) { if (!Node.Type.isOperator(node) || node.op !== '/') { return false; } const args = node.args; - if (!args.every(n => isConstantOrConstantPower(n))) { + if (!args.every(n => ConstantOrPowerTerm.isConstantOrConstantPower(n))) { return false; } - const constantTermList = node.map(n => getBaseNode(n)); - const firstTerm = constantTermList.args[0]; - const restTerms = constantTermList.args.slice(1); + const constantTermList = node.args.map(n => ConstantOrPowerTerm.getBaseNode(n)); + const firstTerm = constantTermList[0]; + const restTerms = constantTermList.slice(1); // they're considered like terms if they have the same base value - return restTerms.every(term => getBaseValue(firstTerm) === getBaseValue(term)); -} -// GETTER FUNCTIONS -// Returns the base node if the node is on power form, else returns the node as it is constant. -function getBaseNode(node) { - if (node.args) { - return node.args[0]; - } - else { - return node; - } -} -// Returns the value of the base for comparison -function getBaseValue(node) { - return node.value; -} -// First if returns false so that addOneExponent can take care of the rest, by adding -// one to the exponent if there are none. -function getExponentNode(node) { - if (node.args === undefined || !node.args[1]) { - return false; - } - else { - return node.args[1]; - } -} -// Checks if the node is an constant or a power with constant base. -function isConstantOrConstantPower(node) { - if ((NodeType.isOperator(node, '^') && NodeType.isConstant(node.args[0])) || - NodeType.isConstant(node)) { - return true; - } - else { - return false; - } + return restTerms.every(term => ConstantOrPowerTerm.getBaseValue(firstTerm) === ConstantOrPowerTerm.getBaseValue(term)); } -module.exports = { - canDivideLikeTermConstantNodes, - getBaseNode, - getExponentNode -}; +module.exports = canDivideLikeTermConstantNodes; diff --git a/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js index 8e8338e3..71dcc3ce 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js @@ -1,17 +1,20 @@ const arithmeticSearch = require('../arithmeticSearch'); const checks = require('../../checks'); const clone = require('../../util/clone'); +const ConstantOrPowerTerm = require('./ConstantOrPowerTerm'); const ChangeTypes = require('../../ChangeTypes'); const Node = require('../../node'); -// Divides a list of nodes that are polynomial like terms or constants with same base. Returns a node. + +// Divides a list of nodes that are polynomial like terms or constants with same base. +// Returns a node. // The nodes should *not* have coefficients. function divideLikeTerms(node, polynomialOnly = false) { if (!Node.Type.isOperator(node)) { return Node.Status.noChange(node); } let status; - if (!polynomialOnly && !checks.canDivideLikeTermConstantNodes.canDivideLikeTermConstantNodes(node)) { + if (!polynomialOnly && !checks.canDivideLikeTermConstantNodes(node)) { status = arithmeticSearch(node); if (status.hasChanged()) { status.changeType = ChangeTypes.SIMPLIFY_FRACTION; @@ -27,7 +30,7 @@ function divideLikeTerms(node, polynomialOnly = false) { return Node.Status.noChange(node); } function dividePolynomialTerms(node) { - if (!checks.canDivideLikeTermPolynomialNodes(node) && !checks.canDivideLikeTermConstantNodes.canDivideLikeTermConstantNodes(node)) { + if (!checks.canDivideLikeTermPolynomialNodes(node) && !checks.canDivideLikeTermConstantNodes(node)) { return Node.Status.noChange(node); } const substeps = []; @@ -45,7 +48,7 @@ function dividePolynomialTerms(node) { // STEP 2: collect exponents to a single exponent difference // e.g. x^1 / x^3 -> x^(1 + -3) - if (checks.canDivideLikeTermConstantNodes.canDivideLikeTermConstantNodes(node)) { + if (checks.canDivideLikeTermConstantNodes(node)) { status = collectConstantExponents(newNode); } else { @@ -87,11 +90,11 @@ function addOneExponent(node) { let change = false; let changeGroup = 1; - if (checks.canDivideLikeTermConstantNodes.canDivideLikeTermConstantNodes(node)) { + if (checks.canDivideLikeTermConstantNodes(node)) { newNode.args.forEach((child, i) => { - if (!checks.canDivideLikeTermConstantNodes.getExponentNode(child)) { + if (!ConstantOrPowerTerm.getExponentNode(child)) { newNode.args[i] = Node.Creator.powerTerm( - checks.canDivideLikeTermConstantNodes.getBaseNode(child), + ConstantOrPowerTerm.getBaseNode(child), Node.Creator.constant(1)); newNode.args[i].changeGroup = changeGroup; @@ -134,10 +137,10 @@ function addOneExponent(node) { function collectConstantExponents(node) { // If we're dividing constant nodes together, they all share the same // base. Get that from the first node. - const baseNode = checks.canDivideLikeTermConstantNodes.getBaseNode(node.args[0]); + const baseNode = ConstantOrPowerTerm.getBaseNode(node.args[0]); // The new exponent will be a difference of exponents (an operation, wrapped in // parens) e.g. 10^(5-3) - const exponentNodeList = node.args.map(p => checks.canDivideLikeTermConstantNodes.getExponentNode(p)); + const exponentNodeList = node.args.map(p => ConstantOrPowerTerm.getExponentNode(p)); const newExponent = Node.Creator.parenthesis( Node.Creator.operator('-', exponentNodeList)); const newNode = Node.Creator.powerTerm(baseNode, newExponent, null); diff --git a/test/canDivideLikeTermConstantNodes.test.js b/test/canDivideLikeTermConstantNodes.test.js index 2b3b657d..3ec2a7f5 100644 --- a/test/canDivideLikeTermConstantNodes.test.js +++ b/test/canDivideLikeTermConstantNodes.test.js @@ -4,7 +4,7 @@ const canDivideLikeTermConstantNodes = require('../lib/checks/canDivideLikeTermC const TestUtil = require('./TestUtil'); function testCanBeDividedConstants(expr, multipliable) { - TestUtil.testBooleanFunction(canDivideLikeTermConstantNodes.canDivideLikeTermConstantNodes, expr, multipliable); + TestUtil.testBooleanFunction(canDivideLikeTermConstantNodes, expr, multipliable); } describe('can divide like term constants', () => { From 938714ab0978680c9b3f1081a69099aa08a7fe0b Mon Sep 17 00:00:00 2001 From: bebbe Date: Fri, 28 Apr 2017 00:48:04 +0200 Subject: [PATCH 26/40] Easy changes according to review --- .../canMultiplyLikeTermConstantNodes.js | 4 ++-- lib/node/PolynomialTerm.js | 2 +- ...owerTerm.js => ConstantOrConstantPower.js} | 23 +++++++++++-------- .../multiplyLikeTerms.js | 5 ++-- 4 files changed, 19 insertions(+), 15 deletions(-) rename lib/simplifyExpression/collectAndCombineSearch/{ConstantOrPowerTerm.js => ConstantOrConstantPower.js} (59%) diff --git a/lib/checks/canMultiplyLikeTermConstantNodes.js b/lib/checks/canMultiplyLikeTermConstantNodes.js index 1b941951..e307b864 100644 --- a/lib/checks/canMultiplyLikeTermConstantNodes.js +++ b/lib/checks/canMultiplyLikeTermConstantNodes.js @@ -1,4 +1,4 @@ -const ConstantOrPowerTerm = require('../simplifyExpression/collectAndCombineSearch/ConstantOrPowerTerm'); +const ConstantOrPowerTerm = require('../simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower'); const Node = require('../node'); // Returns true if node is a multiplication of constant power nodes @@ -16,7 +16,7 @@ function canMultiplyLikeTermConstantNodes(node) { const firstTerm = constantTermList[0]; const restTerms = constantTermList.slice(1); // they're considered like terms if they have the same base value - return restTerms.every(term => ConstantOrPowerTerm.getBaseValue(firstTerm) === ConstantOrPowerTerm.getBaseValue(term)); + return restTerms.every(term => firstTerm.value === term.value); } module.exports = canMultiplyLikeTermConstantNodes; diff --git a/lib/node/PolynomialTerm.js b/lib/node/PolynomialTerm.js index 36713f68..cd70ea30 100644 --- a/lib/node/PolynomialTerm.js +++ b/lib/node/PolynomialTerm.js @@ -140,7 +140,7 @@ class PolynomialTerm { } // note: there is no exponent value getter function because the exponent - // can be any expresion and not necessarily a number. + // can be any expression and not necessarily a number. /* CHECKER FUNCTIONS (returns true / false for certain conditions) */ diff --git a/lib/simplifyExpression/collectAndCombineSearch/ConstantOrPowerTerm.js b/lib/simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower.js similarity index 59% rename from lib/simplifyExpression/collectAndCombineSearch/ConstantOrPowerTerm.js rename to lib/simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower.js index b4e0b0a6..aedd58ac 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/ConstantOrPowerTerm.js +++ b/lib/simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower.js @@ -1,8 +1,17 @@ const NodeType = require('../../node/Type'); -// Returns the base if the node is on power form, else returns the node as it is constant. +// This module is needed when simplifying multiplication of constant powers +// as it contains functions to get different parts of the node instead of +// creating a new class, like polynomialTerm. The functions can return the base +// and the exponent of the power, it can also check if the node is constant or +// constant power. +// e.g 2^10 is an constant power, while x^10 is not +// e.g 2 is an constant, while x is not + +// Returns the base if the node is on power form +// else returns the node as it is constant. // e.g 2^4 returns 2 -// e.g 3 returns 3, as that is the node +// e.g 3 returns 3, since 3 is equal to 3^1 which has a base of 3 function getBaseNode(node) { if (node.args) { return node.args[0]; @@ -12,15 +21,10 @@ function getBaseNode(node) { } } -// Returns the value of the base. -function getBaseValue(node) { - return node.value; -} - // Returns the node that is an exponent to a constant, or a constant node with // value 1 if there's no exponent. // e.g. on the node representing 2^3, returns a constant node with value 3 -// e.g. on the node representing 2, returns a constant node with value 1 (since 2 = 2^1) +// e.g. on the node representing 2, returns a constant node with value 1 (2 = 2^1) function getExponentNode(node) { if (node.args === undefined || !node.args[1]) { return false; @@ -34,12 +38,11 @@ function getExponentNode(node) { // e.g. 2^3 is a constant power node, 5 is a constant node, x and x^2 are not function isConstantOrConstantPower(node) { return ((NodeType.isOperator(node, '^') && NodeType.isConstant(node.args[0])) || - NodeType.isConstant(node)); + NodeType.isConstant(node)); } module.exports = { getBaseNode, - getBaseValue, getExponentNode, isConstantOrConstantPower }; diff --git a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js index 0de67710..390b60cd 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js @@ -1,7 +1,7 @@ const arithmeticSearch = require('../arithmeticSearch'); const checks = require('../../checks'); const clone = require('../../util/clone'); -const ConstantOrPowerTerm = require('./ConstantOrPowerTerm'); +const ConstantOrPowerTerm = require('./ConstantOrConstantPower'); const multiplyFractionsSearch = require('../multiplyFractionsSearch'); const ChangeTypes = require('../../ChangeTypes'); @@ -41,7 +41,8 @@ function multiplyLikeTerms(node, polynomialOnly=false) { } function multiplyPolynomialTerms(node) { - if (!checks.canMultiplyLikeTermPolynomialNodes(node) && !checks.canMultiplyLikeTermConstantNodes(node)) { + if (!checks.canMultiplyLikeTermPolynomialNodes(node) && + !checks.canMultiplyLikeTermConstantNodes(node)) { return Node.Status.noChange(node); } const substeps = []; From 81ba0eb84eb1581f4d53aa561ef4e1ac4924acd8 Mon Sep 17 00:00:00 2001 From: bebbe Date: Fri, 28 Apr 2017 08:46:26 +0200 Subject: [PATCH 27/40] Fixed formatting and deleted powerTerm. --- .../canMultiplyLikeTermConstantNodes.js | 7 +++--- lib/checks/index.js | 1 - lib/node/Creator.js | 7 ------ lib/node/PolynomialTerm.js | 1 - lib/node/index.js | 1 - .../arithmeticSearch/index.js | 3 +++ .../ConstantOrConstantPower.js | 7 +++--- .../multiplyLikeTerms.js | 22 +++++++++++-------- 8 files changed, 24 insertions(+), 25 deletions(-) diff --git a/lib/checks/canMultiplyLikeTermConstantNodes.js b/lib/checks/canMultiplyLikeTermConstantNodes.js index e307b864..b7873777 100644 --- a/lib/checks/canMultiplyLikeTermConstantNodes.js +++ b/lib/checks/canMultiplyLikeTermConstantNodes.js @@ -12,9 +12,10 @@ function canMultiplyLikeTermConstantNodes(node) { if (!args.every(n => ConstantOrPowerTerm.isConstantOrConstantPower(n))) { return false; } - const constantTermList = node.args.map(n => ConstantOrPowerTerm.getBaseNode(n)); - const firstTerm = constantTermList[0]; - const restTerms = constantTermList.slice(1); + + const constantTermBaseList = args.map(n => ConstantOrPowerTerm.getBaseNode(n)); + const firstTerm = constantTermBaseList[0]; + const restTerms = constantTermBaseList.slice(1); // they're considered like terms if they have the same base value return restTerms.every(term => firstTerm.value === term.value); } diff --git a/lib/checks/index.js b/lib/checks/index.js index 2808feab..ddf93f61 100644 --- a/lib/checks/index.js +++ b/lib/checks/index.js @@ -7,7 +7,6 @@ const hasUnsupportedNodes = require('./hasUnsupportedNodes'); const isQuadratic = require('./isQuadratic'); const resolvesToConstant = require('./resolvesToConstant'); - module.exports = { canAddLikeTermPolynomialNodes, canMultiplyLikeTermConstantNodes, diff --git a/lib/node/Creator.js b/lib/node/Creator.js index c172fd76..16e842e6 100644 --- a/lib/node/Creator.js +++ b/lib/node/Creator.js @@ -72,13 +72,6 @@ const NodeCreator = { const symbol = NodeCreator.symbol('nthRoot'); return new math.expression.node.FunctionNode(symbol, [radicandNode, rootNode]); }, - powerTerm (base, exponent) { - let powerTerm = base; - if (exponent) { - powerTerm = this.operator('^', [powerTerm, exponent]); - } - return powerTerm; - } }; module.exports = NodeCreator; diff --git a/lib/node/PolynomialTerm.js b/lib/node/PolynomialTerm.js index cd70ea30..97362e8c 100644 --- a/lib/node/PolynomialTerm.js +++ b/lib/node/PolynomialTerm.js @@ -49,7 +49,6 @@ class PolynomialTerm { ' and ' + nonCoefficientTerm.getCoeffNode()); } this.symbol = nonCoefficientTerm.getSymbolNode(); - this.exponent = nonCoefficientTerm.getExponentNode(); } // this means there's a fraction coefficient diff --git a/lib/node/index.js b/lib/node/index.js index c8370723..c40c0797 100644 --- a/lib/node/index.js +++ b/lib/node/index.js @@ -3,7 +3,6 @@ const PolynomialTerm = require('./PolynomialTerm'); const Status = require('./Status'); const Type = require('./Type'); - module.exports = { Creator, PolynomialTerm, diff --git a/lib/simplifyExpression/arithmeticSearch/index.js b/lib/simplifyExpression/arithmeticSearch/index.js index 1339618f..b84c7f8f 100644 --- a/lib/simplifyExpression/arithmeticSearch/index.js +++ b/lib/simplifyExpression/arithmeticSearch/index.js @@ -2,6 +2,7 @@ const ChangeTypes = require('../../ChangeTypes'); const evaluate = require('../../util/evaluate'); const Node = require('../../node'); const TreeSearch = require('../../TreeSearch'); + // Searches through the tree, prioritizing deeper nodes, and evaluates // arithmetic (e.g. 2+2 or 3*5*2) on an operation node if possible. // Returns a Node.Status object. @@ -16,11 +17,13 @@ function arithmetic(node) { if (!node.args.every(child => Node.Type.isConstant(child, true))) { return Node.Status.noChange(node); } + // we want to eval each arg so unary minuses around constant nodes become // constant nodes with negative values node.args.forEach((arg, i) => { node.args[i] = Node.Creator.constant(evaluate(arg)); }); + // Only resolve division of integers if we get an integer result. // Note that a fraction of decimals will be divided out. if (Node.Type.isIntegerFraction(node)) { diff --git a/lib/simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower.js b/lib/simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower.js index aedd58ac..9342cdd9 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower.js +++ b/lib/simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower.js @@ -1,3 +1,4 @@ +const NodeCreator = require('../../node/Creator'); const NodeType = require('../../node/Type'); // This module is needed when simplifying multiplication of constant powers @@ -24,10 +25,10 @@ function getBaseNode(node) { // Returns the node that is an exponent to a constant, or a constant node with // value 1 if there's no exponent. // e.g. on the node representing 2^3, returns a constant node with value 3 -// e.g. on the node representing 2, returns a constant node with value 1 (2 = 2^1) +// e.g 3 returns 3, since 3 is equal to 3^1 which has a base of 3 function getExponentNode(node) { - if (node.args === undefined || !node.args[1]) { - return false; + if (node.args === undefined ) { + return NodeCreator.constant(1); } else { return node.args[1]; diff --git a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js index 390b60cd..aa7f87b0 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js @@ -1,7 +1,7 @@ const arithmeticSearch = require('../arithmeticSearch'); const checks = require('../../checks'); const clone = require('../../util/clone'); -const ConstantOrPowerTerm = require('./ConstantOrConstantPower'); +const ConstantOrConstantPower = require('./ConstantOrConstantPower'); const multiplyFractionsSearch = require('../multiplyFractionsSearch'); const ChangeTypes = require('../../ChangeTypes'); @@ -45,6 +45,7 @@ function multiplyPolynomialTerms(node) { !checks.canMultiplyLikeTermConstantNodes(node)) { return Node.Status.noChange(node); } + const substeps = []; let newNode = clone(node); @@ -93,7 +94,8 @@ function multiplyPolynomialTerms(node) { } } -// Given a product of polynomial or constant terms, changes any term with no exponent +// Given a product of polynomial or constant terms, changes any +// term with no exponent // into a term with an explicit exponent of 1. This is for pedagogy, and // makes the adding exponents step clearer. // e.g. x^2 * x -> x^2 * x^1 @@ -106,10 +108,10 @@ function addOneExponent(node) { let changeGroup = 1; if (checks.canMultiplyLikeTermConstantNodes(node)) { newNode.args.forEach((child, i) => { - if (!ConstantOrPowerTerm.getExponentNode(child)) { - newNode.args[i] = Node.Creator.powerTerm( - ConstantOrPowerTerm.getBaseNode(child), - Node.Creator.constant(1)); + if (child.args === undefined) { + const base = ConstantOrConstantPower.getBaseNode(child); + const exponent = Node.Creator.constant(1); + newNode.args[i] = Node.Creator.operator('^', [base, exponent]); newNode.args[i].changeGroup = changeGroup; node.args[i].changeGroup = changeGroup; // note that this is the "oldNode" @@ -145,22 +147,24 @@ function addOneExponent(node) { return Node.Status.noChange(node); } } + // Given a product of constant terms, groups the exponents into a sum // e.g. 10^2 * 10^3 -> 10^(2+3) // Returns a Node.Status object. function collectConstantExponents(node) { // If we're multiplying constant nodes together, they all share the same // base. Get that from the first node. - const baseNode = ConstantOrPowerTerm.getBaseNode(node.args[0]); + const baseNode = ConstantOrConstantPower.getBaseNode(node.args[0]); // The new exponent will be a sum of exponents (an operation, wrapped in // parens) e.g. 10^(3+4+5) - const exponentNodeList = node.args.map(p => ConstantOrPowerTerm.getExponentNode(p)); + const exponentNodeList = node.args.map(p => ConstantOrConstantPower.getExponentNode(p)); const newExponent = Node.Creator.parenthesis( Node.Creator.operator('+', exponentNodeList)); - const newNode = Node.Creator.powerTerm(baseNode, newExponent, null); + const newNode = Node.Creator.operator('^', [baseNode, newExponent]); return Node.Status.nodeChanged( ChangeTypes.COLLECT_CONSTANT_EXPONENTS, node, newNode); } + // Given a product of polynomial terms, groups the exponents into a sum // e.g. x^2 * x^3 * x^1 -> x^(2 + 3 + 1) // Returns a Node.Status object. From 2e2877b3793942df85adb38ef2ff8a5346eaf075 Mon Sep 17 00:00:00 2001 From: bebbe Date: Fri, 28 Apr 2017 08:51:56 +0200 Subject: [PATCH 28/40] deleted .idea files and added .idea to gitignore --- .gitignore | 1 + .idea/codeStyleSettings.xml | 9 - .idea/inspectionProfiles/Project_Default.xml | 6 - .idea/mathsteps.iml | 12 - .idea/misc.xml | 6 - .idea/modules.xml | 8 - .idea/vcs.xml | 6 - .idea/watcherTasks.xml | 4 - .idea/workspace.xml | 857 ------------------- 9 files changed, 1 insertion(+), 908 deletions(-) delete mode 100644 .idea/codeStyleSettings.xml delete mode 100644 .idea/inspectionProfiles/Project_Default.xml delete mode 100644 .idea/mathsteps.iml delete mode 100644 .idea/misc.xml delete mode 100644 .idea/modules.xml delete mode 100644 .idea/vcs.xml delete mode 100644 .idea/watcherTasks.xml delete mode 100644 .idea/workspace.xml diff --git a/.gitignore b/.gitignore index f58d10d0..6a48e0fb 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules *.log .DS_Store +.idea diff --git a/.idea/codeStyleSettings.xml b/.idea/codeStyleSettings.xml deleted file mode 100644 index 5555dd26..00000000 --- a/.idea/codeStyleSettings.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index c6cc8c81..00000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/mathsteps.iml b/.idea/mathsteps.iml deleted file mode 100644 index 24643cc3..00000000 --- a/.idea/mathsteps.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 28a804d8..00000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index 5976885e..00000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f..00000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/watcherTasks.xml b/.idea/watcherTasks.xml deleted file mode 100644 index 9338ba68..00000000 --- a/.idea/watcherTasks.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index 495e3d41..00000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,857 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - console.log - console. - math - math er - math error - math erro - re - return - returning - returning no - testArithmeticSearch - testSimplification - node.to - getBaseValue - isPolynomialTerm - every - hejhej - console - ar - arig - arith - arithmet - arithmeti - arithmeticA - arithmetic - arithmeticS - arithmeticSea - arithmeticSear - arithmeticSearc - arithmeticSearch - - - - - - - - - - - - - - - - true - DEFINITION_ORDER - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - project - - - - - - - - - - - - - - - - project - - - true - - - - DIRECTORY - - false - - - - - - - - - - - - - - - - - - - - - - - 1491376618838 - - - 1492068942886 - - - 1492069109577 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From cb0bcb1160a9bc64eb78436c7b8ed3fd7fa60156 Mon Sep 17 00:00:00 2001 From: bebbe Date: Sat, 29 Apr 2017 00:28:15 +0200 Subject: [PATCH 29/40] Fixed comments --- lib/node/Creator.js | 2 +- .../collectAndCombineSearch/ConstantOrConstantPower.js | 5 +++-- .../collectAndCombineSearch/multiplyLikeTerms.js | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/node/Creator.js b/lib/node/Creator.js index 16e842e6..165a6863 100644 --- a/lib/node/Creator.js +++ b/lib/node/Creator.js @@ -71,7 +71,7 @@ const NodeCreator = { nthRoot (radicandNode, rootNode) { const symbol = NodeCreator.symbol('nthRoot'); return new math.expression.node.FunctionNode(symbol, [radicandNode, rootNode]); - }, + } }; module.exports = NodeCreator; diff --git a/lib/simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower.js b/lib/simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower.js index 9342cdd9..d7eb2060 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower.js +++ b/lib/simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower.js @@ -25,7 +25,7 @@ function getBaseNode(node) { // Returns the node that is an exponent to a constant, or a constant node with // value 1 if there's no exponent. // e.g. on the node representing 2^3, returns a constant node with value 3 -// e.g 3 returns 3, since 3 is equal to 3^1 which has a base of 3 +// e.g 3 returns 1, since 3 is equal to 3^1 which has a base of 3 function getExponentNode(node) { if (node.args === undefined ) { return NodeCreator.constant(1); @@ -38,7 +38,8 @@ function getExponentNode(node) { // Checks if the node is an constant or a power with constant base. // e.g. 2^3 is a constant power node, 5 is a constant node, x and x^2 are not function isConstantOrConstantPower(node) { - return ((NodeType.isOperator(node, '^') && NodeType.isConstant(node.args[0])) || + return ((NodeType.isOperator(node, '^') && + NodeType.isConstant(node.args[0])) || NodeType.isConstant(node)); } diff --git a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js index aa7f87b0..0dd96921 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js @@ -108,7 +108,7 @@ function addOneExponent(node) { let changeGroup = 1; if (checks.canMultiplyLikeTermConstantNodes(node)) { newNode.args.forEach((child, i) => { - if (child.args === undefined) { + if (child.args === undefined) { // true if child is a constant node, e.g 3 const base = ConstantOrConstantPower.getBaseNode(child); const exponent = Node.Creator.constant(1); newNode.args[i] = Node.Creator.operator('^', [base, exponent]); @@ -157,7 +157,8 @@ function collectConstantExponents(node) { const baseNode = ConstantOrConstantPower.getBaseNode(node.args[0]); // The new exponent will be a sum of exponents (an operation, wrapped in // parens) e.g. 10^(3+4+5) - const exponentNodeList = node.args.map(p => ConstantOrConstantPower.getExponentNode(p)); + const exponentNodeList = node.args.map( + p => ConstantOrConstantPower.getExponentNode(p)); const newExponent = Node.Creator.parenthesis( Node.Creator.operator('+', exponentNodeList)); const newNode = Node.Creator.operator('^', [baseNode, newExponent]); From fa35784796170b7eab198a12cbb57f246e5a5993 Mon Sep 17 00:00:00 2001 From: bebbe Date: Tue, 2 May 2017 09:22:12 +0200 Subject: [PATCH 30/40] Fixed comments --- .../collectAndCombineSearch/ConstantOrConstantPower.js | 6 +++--- .../collectAndCombineSearch/multiplyLikeTerms.js | 4 ++-- test/canMultiplyLikeTermConstantNodes.test.js | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower.js b/lib/simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower.js index d7eb2060..7fbe07fb 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower.js +++ b/lib/simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower.js @@ -25,9 +25,9 @@ function getBaseNode(node) { // Returns the node that is an exponent to a constant, or a constant node with // value 1 if there's no exponent. // e.g. on the node representing 2^3, returns a constant node with value 3 -// e.g 3 returns 1, since 3 is equal to 3^1 which has a base of 3 +// e.g 3 returns 1, since 3 is equal to 3^1 which has an exponent of 1 function getExponentNode(node) { - if (node.args === undefined ) { + if (NodeType.isConstant(node)) { return NodeCreator.constant(1); } else { @@ -39,7 +39,7 @@ function getExponentNode(node) { // e.g. 2^3 is a constant power node, 5 is a constant node, x and x^2 are not function isConstantOrConstantPower(node) { return ((NodeType.isOperator(node, '^') && - NodeType.isConstant(node.args[0])) || + NodeType.isConstant(node.args[0])) || NodeType.isConstant(node)); } diff --git a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js index 0dd96921..052560ea 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js @@ -108,7 +108,7 @@ function addOneExponent(node) { let changeGroup = 1; if (checks.canMultiplyLikeTermConstantNodes(node)) { newNode.args.forEach((child, i) => { - if (child.args === undefined) { // true if child is a constant node, e.g 3 + if (Node.Type.isConstant(child)) { // true if child is a constant node, e.g 3 const base = ConstantOrConstantPower.getBaseNode(child); const exponent = Node.Creator.constant(1); newNode.args[i] = Node.Creator.operator('^', [base, exponent]); @@ -158,7 +158,7 @@ function collectConstantExponents(node) { // The new exponent will be a sum of exponents (an operation, wrapped in // parens) e.g. 10^(3+4+5) const exponentNodeList = node.args.map( - p => ConstantOrConstantPower.getExponentNode(p)); + ConstantOrConstantPower.getExponentNode); const newExponent = Node.Creator.parenthesis( Node.Creator.operator('+', exponentNodeList)); const newNode = Node.Creator.operator('^', [baseNode, newExponent]); diff --git a/test/canMultiplyLikeTermConstantNodes.test.js b/test/canMultiplyLikeTermConstantNodes.test.js index 7fd3c75e..4b9963cc 100644 --- a/test/canMultiplyLikeTermConstantNodes.test.js +++ b/test/canMultiplyLikeTermConstantNodes.test.js @@ -1,6 +1,5 @@ const canMultiplyLikeTermConstantNodes = require('../lib/checks/canMultiplyLikeTermConstantNodes'); - const TestUtil = require('./TestUtil'); function testCanBeMultipliedConstants(expr, multipliable) { From 7c1758d086b48c25d1ca3d3d4f087da0e421b0eb Mon Sep 17 00:00:00 2001 From: Sebastian Liljevall Date: Tue, 2 May 2017 09:23:37 +0200 Subject: [PATCH 31/40] Delete .npmignore --- .npmignore | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .npmignore diff --git a/.npmignore b/.npmignore deleted file mode 100644 index f58d10d0..00000000 --- a/.npmignore +++ /dev/null @@ -1,4 +0,0 @@ -*swp -node_modules -*.log -.DS_Store From b10ae0f6fc10eb2865a1530512e18225ca0aaa96 Mon Sep 17 00:00:00 2001 From: Sebastian Liljevall Date: Tue, 2 May 2017 09:48:01 +0200 Subject: [PATCH 32/40] Delete .gitignore --- .gitignore | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 .gitignore diff --git a/.gitignore b/.gitignore deleted file mode 100644 index 6a48e0fb..00000000 --- a/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -*swp -node_modules -*.log -.DS_Store -.idea From 26edd37714447e8b728b605f67f6c5df179eb67d Mon Sep 17 00:00:00 2001 From: bebbe Date: Tue, 2 May 2017 09:54:20 +0200 Subject: [PATCH 33/40] Merge branch 'multiply_powers_integers' into divide_like_terms_powers --- .npmignore | 4 --- lib/checks/canDivideLikeTermConstantNodes.js | 12 ++++----- .../divideLikeTerms.js | 26 ++++++++++++------- 3 files changed, 23 insertions(+), 19 deletions(-) delete mode 100644 .npmignore diff --git a/.npmignore b/.npmignore deleted file mode 100644 index f58d10d0..00000000 --- a/.npmignore +++ /dev/null @@ -1,4 +0,0 @@ -*swp -node_modules -*.log -.DS_Store diff --git a/lib/checks/canDivideLikeTermConstantNodes.js b/lib/checks/canDivideLikeTermConstantNodes.js index 1fc8fbf0..f38f30d2 100644 --- a/lib/checks/canDivideLikeTermConstantNodes.js +++ b/lib/checks/canDivideLikeTermConstantNodes.js @@ -1,10 +1,9 @@ -const ConstantOrPowerTerm = require('../simplifyExpression/collectAndCombineSearch/ConstantOrPowerTerm'); +const ConstantOrPowerTerm = require('../simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower'); const Node = require('../node'); // Returns true if node is a division of constant power nodes // where you can combine their exponents, e.g. 10^4 / 10^2 can become 10^2 // The node can be on the form c^n or c, as long is c is the same for all - function canDivideLikeTermConstantNodes(node) { if (!Node.Type.isOperator(node) || node.op !== '/') { return false; @@ -13,11 +12,12 @@ function canDivideLikeTermConstantNodes(node) { if (!args.every(n => ConstantOrPowerTerm.isConstantOrConstantPower(n))) { return false; } - const constantTermList = node.args.map(n => ConstantOrPowerTerm.getBaseNode(n)); - const firstTerm = constantTermList[0]; - const restTerms = constantTermList.slice(1); + + const constantTermBaseList = args.map(n => ConstantOrPowerTerm.getBaseNode(n)); + const firstTerm = constantTermBaseList[0]; + const restTerms = constantTermBaseList.slice(1); // they're considered like terms if they have the same base value - return restTerms.every(term => ConstantOrPowerTerm.getBaseValue(firstTerm) === ConstantOrPowerTerm.getBaseValue(term)); + return restTerms.every(term => firstTerm.value === term.value); } module.exports = canDivideLikeTermConstantNodes; diff --git a/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js index 71dcc3ce..e041cfff 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js @@ -1,7 +1,7 @@ const arithmeticSearch = require('../arithmeticSearch'); const checks = require('../../checks'); const clone = require('../../util/clone'); -const ConstantOrPowerTerm = require('./ConstantOrPowerTerm'); +const ConstantOrConstantPower = require('./ConstantOrConstantPower'); const ChangeTypes = require('../../ChangeTypes'); const Node = require('../../node'); @@ -14,6 +14,7 @@ function divideLikeTerms(node, polynomialOnly = false) { return Node.Status.noChange(node); } let status; + if (!polynomialOnly && !checks.canDivideLikeTermConstantNodes(node)) { status = arithmeticSearch(node); if (status.hasChanged()) { @@ -27,12 +28,16 @@ function divideLikeTerms(node, polynomialOnly = false) { status.changeType = ChangeTypes.SIMPLIFY_FRACTION; return status; } + return Node.Status.noChange(node); } + function dividePolynomialTerms(node) { - if (!checks.canDivideLikeTermPolynomialNodes(node) && !checks.canDivideLikeTermConstantNodes(node)) { + if (!checks.canDivideLikeTermPolynomialNodes(node) && + !checks.canDivideLikeTermConstantNodes(node)) { return Node.Status.noChange(node); } + const substeps = []; let newNode = clone(node); @@ -92,10 +97,10 @@ function addOneExponent(node) { let changeGroup = 1; if (checks.canDivideLikeTermConstantNodes(node)) { newNode.args.forEach((child, i) => { - if (!ConstantOrPowerTerm.getExponentNode(child)) { - newNode.args[i] = Node.Creator.powerTerm( - ConstantOrPowerTerm.getBaseNode(child), - Node.Creator.constant(1)); + if (Node.Type.isConstant(child)) { + const base = ConstantOrConstantPower.getBaseNode(child); + const exponent = Node.Creator.constant(1); + newNode.args[i] = Node.Creator.operator('^', [base, exponent]); newNode.args[i].changeGroup = changeGroup; node.args[i].changeGroup = changeGroup; // note that this is the "oldNode" @@ -131,19 +136,22 @@ function addOneExponent(node) { return Node.Status.noChange(node); } } + // Given a division of constant terms, groups the exponents into a difference // e.g. 10^5 / 10^3 -> 10^(5 - 3) // Returns a Node.Status object. function collectConstantExponents(node) { + // If we're dividing constant nodes together, they all share the same // base. Get that from the first node. - const baseNode = ConstantOrPowerTerm.getBaseNode(node.args[0]); + const baseNode = ConstantOrConstantPower.getBaseNode(node.args[0]); + // The new exponent will be a difference of exponents (an operation, wrapped in // parens) e.g. 10^(5-3) - const exponentNodeList = node.args.map(p => ConstantOrPowerTerm.getExponentNode(p)); + const exponentNodeList = node.args.map(ConstantOrConstantPower.getExponentNode); const newExponent = Node.Creator.parenthesis( Node.Creator.operator('-', exponentNodeList)); - const newNode = Node.Creator.powerTerm(baseNode, newExponent, null); + const newNode = Node.Creator.operator('^', [baseNode, newExponent]); return Node.Status.nodeChanged( ChangeTypes.COLLECT_CONSTANT_EXPONENTS, node, newNode); } From 8337df8353dfe005e504d7e5bef058746bec71a7 Mon Sep 17 00:00:00 2001 From: bebbe Date: Tue, 2 May 2017 09:56:11 +0200 Subject: [PATCH 34/40] Added gitignore again. --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4031d39a --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*swp +node_modules +*.log +.DS_Store +.idea \ No newline at end of file From 9ab482c966b5ff7f4867f432711f170ef8966a93 Mon Sep 17 00:00:00 2001 From: bebbe Date: Tue, 2 May 2017 09:57:47 +0200 Subject: [PATCH 35/40] Deleted .idea --- .idea/workspace.xml | 899 -------------------------------------------- 1 file changed, 899 deletions(-) delete mode 100644 .idea/workspace.xml diff --git a/.idea/workspace.xml b/.idea/workspace.xml deleted file mode 100644 index c8af0ae5..00000000 --- a/.idea/workspace.xml +++ /dev/null @@ -1,899 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - math er - math error - math erro - re - return - returning - returning no - testArithmeticSearch - testSimplification - node.to - getBaseValue - isPolynomialTerm - every - hejhej - console - ar - arig - arith - arithmet - arithmeti - arithmeticA - arithmetic - arithmeticS - arithmeticSea - arithmeticSear - arithmeticSearc - arithmeticSearch - canMu - simplify_arithme - collectExponents - - - - - - - - - - - - - - - - true - DEFINITION_ORDER - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - project - - - - - - - - - - - - - - - - project - - - true - - - - DIRECTORY - - false - - - - - - - - - - - - - - - - - - - - - - - 1491376618838 - - - 1492068942886 - - - 1492069109577 - - - 1492074808637 - - - 1492080213487 - - - 1492084557369 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From d955b34c073f9848939a78edc944fdfc92e8d78f Mon Sep 17 00:00:00 2001 From: bebbe Date: Tue, 2 May 2017 10:04:32 +0200 Subject: [PATCH 36/40] Added/removed lines for clearer code. --- lib/checks/canDivideLikeTermPolynomialNodes.js | 2 ++ lib/checks/canMultiplyLikeTermPolynomialNodes.js | 1 + .../collectAndCombineSearch/multiplyLikeTerms.js | 1 + 3 files changed, 4 insertions(+) diff --git a/lib/checks/canDivideLikeTermPolynomialNodes.js b/lib/checks/canDivideLikeTermPolynomialNodes.js index 5614c1b6..c77b5631 100644 --- a/lib/checks/canDivideLikeTermPolynomialNodes.js +++ b/lib/checks/canDivideLikeTermPolynomialNodes.js @@ -13,10 +13,12 @@ function canDivideLikeTermPolynomialNodes(node) { if (args.length === 1) { return false; } + const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); if (!polynomialTermList.every(polyTerm => !polyTerm.hasCoeff())) { return false; } + const firstTerm = polynomialTermList[0]; const restTerms = polynomialTermList.slice(1); // they're considered like terms if they have the same symbol name diff --git a/lib/checks/canMultiplyLikeTermPolynomialNodes.js b/lib/checks/canMultiplyLikeTermPolynomialNodes.js index 60c3ffda..0654ae82 100644 --- a/lib/checks/canMultiplyLikeTermPolynomialNodes.js +++ b/lib/checks/canMultiplyLikeTermPolynomialNodes.js @@ -13,6 +13,7 @@ function canMultiplyLikeTermPolynomialNodes(node) { if (args.length === 1) { return false; } + const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); if (!polynomialTermList.every(polyTerm => !polyTerm.hasCoeff())) { return false; diff --git a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js index 052560ea..215818ee 100644 --- a/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js +++ b/lib/simplifyExpression/collectAndCombineSearch/multiplyLikeTerms.js @@ -9,6 +9,7 @@ const Node = require('../../node'); // Multiplies a list of nodes that are polynomial or constant power like terms. // Returns a node. + // The polynomial nodes should *not* have coefficients. (multiplying // coefficients is handled in collecting like terms for multiplication) function multiplyLikeTerms(node, polynomialOnly=false) { From 4dc5206a6f249603f41bf866481c1bf2c099c728 Mon Sep 17 00:00:00 2001 From: Sebastian Liljevall Date: Tue, 2 May 2017 19:21:38 +0200 Subject: [PATCH 37/40] Update canMultiplyLikeTermPolynomialNodes.js --- lib/checks/canMultiplyLikeTermPolynomialNodes.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/checks/canMultiplyLikeTermPolynomialNodes.js b/lib/checks/canMultiplyLikeTermPolynomialNodes.js index 60c3ffda..9acb5187 100644 --- a/lib/checks/canMultiplyLikeTermPolynomialNodes.js +++ b/lib/checks/canMultiplyLikeTermPolynomialNodes.js @@ -13,6 +13,7 @@ function canMultiplyLikeTermPolynomialNodes(node) { if (args.length === 1) { return false; } + const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); if (!polynomialTermList.every(polyTerm => !polyTerm.hasCoeff())) { return false; From 6b86cc49a7ce43ba3f3b9fdf1d3a69ec503e7c73 Mon Sep 17 00:00:00 2001 From: bebbe Date: Wed, 3 May 2017 11:14:11 +0200 Subject: [PATCH 38/40] Removed test --- test/simplifyExpression/simplify.test.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/test/simplifyExpression/simplify.test.js b/test/simplifyExpression/simplify.test.js index 736cf00e..1cefba3b 100644 --- a/test/simplifyExpression/simplify.test.js +++ b/test/simplifyExpression/simplify.test.js @@ -163,14 +163,6 @@ describe('handles unnecessary parens at root level', function() { tests.forEach(t => testSimplify(t[0], t[1], t[2])); }); -describe('multiplication of powers support', function(){ - const tests = [ - ['10^3 * 10^2', '100000'], - ['2^3*2^4', '128'], - ]; - tests.forEach(t => testSimplify(t[0], t[1], t[2])); -}); - describe('keeping parens in important places, on printing', function() { testSimplify('2 / (2x^2) + 5', '2 / (2x^2) + 5'); }); From 22f2dc496edded5f4670455495b952871ff3cadd Mon Sep 17 00:00:00 2001 From: bebbe Date: Wed, 3 May 2017 11:17:16 +0200 Subject: [PATCH 39/40] Fixed lint --- lib/checks/canMultiplyLikeTermPolynomialNodes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/checks/canMultiplyLikeTermPolynomialNodes.js b/lib/checks/canMultiplyLikeTermPolynomialNodes.js index 9acb5187..0654ae82 100644 --- a/lib/checks/canMultiplyLikeTermPolynomialNodes.js +++ b/lib/checks/canMultiplyLikeTermPolynomialNodes.js @@ -13,7 +13,7 @@ function canMultiplyLikeTermPolynomialNodes(node) { if (args.length === 1) { return false; } - + const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); if (!polynomialTermList.every(polyTerm => !polyTerm.hasCoeff())) { return false; From d47dd580c6e67d4d26e16e9f02cfd6001a24753b Mon Sep 17 00:00:00 2001 From: bebbe Date: Mon, 8 May 2017 09:13:18 +0200 Subject: [PATCH 40/40] Removed some unnecessary tests --- test/simplifyExpression/simplify.test.js | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/test/simplifyExpression/simplify.test.js b/test/simplifyExpression/simplify.test.js index 175a557f..1cefba3b 100644 --- a/test/simplifyExpression/simplify.test.js +++ b/test/simplifyExpression/simplify.test.js @@ -163,22 +163,6 @@ describe('handles unnecessary parens at root level', function() { tests.forEach(t => testSimplify(t[0], t[1], t[2])); }); -describe('multiplication of powers support', function() { - const tests = [ - ['10^3 * 10^2', '100000'], - ['2^3*2^4', '128'], - ]; - tests.forEach(t => testSimplify(t[0], t[1], t[2])); -}); - -describe('division of powers support', function() { - const tests = [ - ['10^3 / 10^2', '10'], - ['2^5/2^4', '2'], - ]; - tests.forEach(t => testSimplify(t[0], t[1], t[2])); -}); - describe('keeping parens in important places, on printing', function() { testSimplify('2 / (2x^2) + 5', '2 / (2x^2) + 5'); });