From fbf4514404982547d5c1bfbf8ced4f640dcc4700 Mon Sep 17 00:00:00 2001 From: markus polleryd Date: Tue, 16 Jan 2018 13:32:51 +0100 Subject: [PATCH 1/3] Expressions with polynomials divided by polynomials are now simplified --- .../fractionsSearch/cancelLikeTerms.js | 53 +++++++++++++++++-- .../fractionsSearch/cancelLikeTerms.test.js | 6 ++- test/simplifyExpression/simplify.test.js | 2 + 3 files changed, 54 insertions(+), 7 deletions(-) diff --git a/lib/simplifyExpression/fractionsSearch/cancelLikeTerms.js b/lib/simplifyExpression/fractionsSearch/cancelLikeTerms.js index ca8f7763..2c5f879a 100644 --- a/lib/simplifyExpression/fractionsSearch/cancelLikeTerms.js +++ b/lib/simplifyExpression/fractionsSearch/cancelLikeTerms.js @@ -240,7 +240,6 @@ function cancelTerms(numerator, denominator) { if (print.ascii(numerator) === print.ascii(denominator)) { return new CancelOutStatus(null, null, true); } - // case 2: they're both exponent nodes with the same base // e.g. (2x+5)^8 and (2x+5)^2 if (Node.Type.isOperator(numerator, '^') && @@ -257,16 +256,25 @@ function cancelTerms(numerator, denominator) { numerator.args[1] = newExponent; return new CancelOutStatus(numerator, null, true); } - // case 3: they're both polynomial terms, check if they have the same symbol // e.g. 4x^2 / 5x^2 => 4 / 5 // e.g. 4x^3 / 5x^2 => 4x^(3-2) / 5 + // Case 3.1: they're both polynomial terms with different symbols but with coefficients + // e.g 20x / 40y => 1x / 2y + // e.g 60x / 40y => 3x / 2y + // e.g 4x / 2y => 2x / y if (Node.PolynomialTerm.isPolynomialTerm(numerator) && Node.PolynomialTerm.isPolynomialTerm(denominator)) { const numeratorTerm = new Node.PolynomialTerm(numerator); const denominatorTerm = new Node.PolynomialTerm(denominator); if (numeratorTerm.getSymbolName() !== denominatorTerm.getSymbolName()) { - return new CancelOutStatus(numerator, denominator); + if (Node.Type.isOperator(numerator, '*') && Node.Type.isOperator(denominator, '*')) { + // case 3.1 + return cancelCoeffs(numerator, denominator); + } + else { + return new CancelOutStatus(numerator, denominator); + } } const numeratorExponent = numeratorTerm.getExponentNode(true); let denominatorExponent = denominatorTerm.getExponentNode(true); @@ -294,7 +302,6 @@ function cancelTerms(numerator, denominator) { // or is multiplication node // e.g. 2 / 4x -> 1 / 2x // e.g. ignore cases like: 2 / a and 2 / x^2 - if (Node.Type.isConstant(numerator) && Node.Type.isOperator(denominator, '*') && Node.PolynomialTerm.isPolynomialTerm(denominator)) { @@ -330,7 +337,6 @@ function cancelTerms(numerator, denominator) { // case 5: both numerator and denominator are numbers within a more complicated fraction // e.g. (35 * nthRoot (7)) / (5 * nthRoot(5)) -> (7 * nthRoot(7)) / nthRoot(5) - if (Node.Type.isConstant(numerator) && Node.Type.isConstant(denominator)) { const frac = Node.Creator.operator('/', [numerator, denominator]); const reduceStatus = divideByGCD(frac); @@ -371,4 +377,41 @@ function isMultiplicationOfTerms(node) { !Node.PolynomialTerm.isPolynomialTerm(node)); } +function cancelCoeffs(numerator, denominator){ + const denominatorTerm = new Node.PolynomialTerm(denominator); + const numeratorTerm = new Node.PolynomialTerm(numerator); + + const denominatorCoeff = denominatorTerm.getCoeffNode(); + const denomintorVariable = denominatorTerm.getSymbolNode(); + const denominatorExponent = denominatorTerm.getExponentNode(); + + const numeratorCoeff = numeratorTerm.getCoeffNode(); + const numeratorVariable = numeratorTerm.getSymbolNode(); + const numeratorExponent = numeratorTerm.getExponentNode(); + + // simplify a constant fraction (e.g 2 / 4) + const frac = Node.Creator.operator('/', [numeratorCoeff, denominatorCoeff]); + + let newCoeff = clone(denominatorCoeff); + const reduceStatus = divideByGCD(frac); + + if (!reduceStatus.hasChanged()) { + return new CancelOutStatus(numerator, denominator, false); + } + + // Sometimes the fraction reduces to a constant e.g. 6 / 2 -> 3, + // in which case `newCoeff` (the denominator coefficient) should be null + if (Node.Type.isConstant(reduceStatus.newNode)) { + numerator = Node.Creator.polynomialTerm(numeratorVariable,numeratorExponent,reduceStatus.newNode); + newCoeff = null; + } + else { + numerator = Node.Creator.polynomialTerm(numeratorVariable,numeratorExponent,reduceStatus.newNode.args[0]); + newCoeff = reduceStatus.newNode.args[1]; + } + denominator = Node.Creator.polynomialTerm(denomintorVariable, denominatorExponent, newCoeff); + + return new CancelOutStatus(numerator, denominator, true); +} + module.exports = cancelLikeTerms; diff --git a/test/simplifyExpression/fractionsSearch/cancelLikeTerms.test.js b/test/simplifyExpression/fractionsSearch/cancelLikeTerms.test.js index a877684e..c94fafa5 100644 --- a/test/simplifyExpression/fractionsSearch/cancelLikeTerms.test.js +++ b/test/simplifyExpression/fractionsSearch/cancelLikeTerms.test.js @@ -24,9 +24,11 @@ describe('cancel like terms', function () { ['2/ (4x)', '1 / (2x)'], ['2/ (4x^2)', '1 / (2x^2)'], ['2 a / a', '2'], - ['(35 * nthRoot (7)) / (5 * nthRoot(5))','(7 * nthRoot(7)) / nthRoot(5)'], + ['(35 * nthRoot (7)) / (5 * nthRoot(5))', '(7 * nthRoot(7)) / nthRoot(5)'], ['3/(9r^2)', '1 / (3r^2)'], - ['6/(2x)', '3 / (x)'] + ['6/(2x)', '3 / (x)'], + ['(40 * x) / (20 * y)', '(2x) / (y)'], + ['(20 * x) / (40 * y)', '(x) / (2y)'], ]; tests.forEach(t => testCancelLikeTerms(t[0], t[1])); diff --git a/test/simplifyExpression/simplify.test.js b/test/simplifyExpression/simplify.test.js index 6fec2487..f42b5429 100644 --- a/test/simplifyExpression/simplify.test.js +++ b/test/simplifyExpression/simplify.test.js @@ -51,6 +51,8 @@ describe('can simplify with division', function () { ['2x/x', '2'], ['2x/4/3', '1/6 x'], ['((2+x)(3+x))/(2+x)', '3 + x'], + ['(20 * x) / (5 * (40 * y))', 'x / (10y)'], + ['400 * z / ((20 * x) / (5 * (40 * y)))', '(4000y * z) / x'] ]; tests.forEach(t => testSimplify(t[0], t[1], t[2])); // TODO: factor the numerator to cancel out with denominator From 6e10e9ea9ddefe0baee4c0cf8a1fa5b448898269 Mon Sep 17 00:00:00 2001 From: markus polleryd Date: Wed, 17 Jan 2018 11:43:14 +0100 Subject: [PATCH 2/3] minor naming/style changes --- .../fractionsSearch/cancelLikeTerms.js | 23 ++++++++++--------- .../fractionsSearch/cancelLikeTerms.test.js | 3 +++ test/simplifyExpression/simplify.test.js | 5 +++- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/lib/simplifyExpression/fractionsSearch/cancelLikeTerms.js b/lib/simplifyExpression/fractionsSearch/cancelLikeTerms.js index 2c5f879a..d8fc87bf 100644 --- a/lib/simplifyExpression/fractionsSearch/cancelLikeTerms.js +++ b/lib/simplifyExpression/fractionsSearch/cancelLikeTerms.js @@ -259,8 +259,8 @@ function cancelTerms(numerator, denominator) { // case 3: they're both polynomial terms, check if they have the same symbol // e.g. 4x^2 / 5x^2 => 4 / 5 // e.g. 4x^3 / 5x^2 => 4x^(3-2) / 5 - // Case 3.1: they're both polynomial terms with different symbols but with coefficients - // e.g 20x / 40y => 1x / 2y + // case 3.1: they're both polynomial terms with different symbols but with coefficients + // e.g 20x / 40y => x / 2y // e.g 60x / 40y => 3x / 2y // e.g 4x / 2y => 2x / y if (Node.PolynomialTerm.isPolynomialTerm(numerator) && @@ -382,7 +382,7 @@ function cancelCoeffs(numerator, denominator){ const numeratorTerm = new Node.PolynomialTerm(numerator); const denominatorCoeff = denominatorTerm.getCoeffNode(); - const denomintorVariable = denominatorTerm.getSymbolNode(); + const denominatorVariable = denominatorTerm.getSymbolNode(); const denominatorExponent = denominatorTerm.getExponentNode(); const numeratorCoeff = numeratorTerm.getCoeffNode(); @@ -392,7 +392,6 @@ function cancelCoeffs(numerator, denominator){ // simplify a constant fraction (e.g 2 / 4) const frac = Node.Creator.operator('/', [numeratorCoeff, denominatorCoeff]); - let newCoeff = clone(denominatorCoeff); const reduceStatus = divideByGCD(frac); if (!reduceStatus.hasChanged()) { @@ -400,18 +399,20 @@ function cancelCoeffs(numerator, denominator){ } // Sometimes the fraction reduces to a constant e.g. 6 / 2 -> 3, - // in which case `newCoeff` (the denominator coefficient) should be null + // in which case the denominator coefficient should be null + let newDenominator = null; + let newNumerator = null; if (Node.Type.isConstant(reduceStatus.newNode)) { - numerator = Node.Creator.polynomialTerm(numeratorVariable,numeratorExponent,reduceStatus.newNode); - newCoeff = null; + newNumerator = Node.Creator.polynomialTerm(numeratorVariable, numeratorExponent, reduceStatus.newNode); + newDenominator = null; } else { - numerator = Node.Creator.polynomialTerm(numeratorVariable,numeratorExponent,reduceStatus.newNode.args[0]); - newCoeff = reduceStatus.newNode.args[1]; + newNumerator = Node.Creator.polynomialTerm(numeratorVariable, numeratorExponent, reduceStatus.newNode.args[0]); + newDenominator = reduceStatus.newNode.args[1]; } - denominator = Node.Creator.polynomialTerm(denomintorVariable, denominatorExponent, newCoeff); + denominator = Node.Creator.polynomialTerm(denominatorVariable, denominatorExponent, newDenominator); - return new CancelOutStatus(numerator, denominator, true); + return new CancelOutStatus(newNumerator, denominator, true); } module.exports = cancelLikeTerms; diff --git a/test/simplifyExpression/fractionsSearch/cancelLikeTerms.test.js b/test/simplifyExpression/fractionsSearch/cancelLikeTerms.test.js index c94fafa5..6fe79df6 100644 --- a/test/simplifyExpression/fractionsSearch/cancelLikeTerms.test.js +++ b/test/simplifyExpression/fractionsSearch/cancelLikeTerms.test.js @@ -29,6 +29,9 @@ describe('cancel like terms', function () { ['6/(2x)', '3 / (x)'], ['(40 * x) / (20 * y)', '(2x) / (y)'], ['(20 * x) / (40 * y)', '(x) / (2y)'], + ['20x / (40y)', 'x / (2y)'], + ['60x / (40y)', '3x / (2y)'], + ['4x / (2y)', '2x / (y)'] ]; tests.forEach(t => testCancelLikeTerms(t[0], t[1])); diff --git a/test/simplifyExpression/simplify.test.js b/test/simplifyExpression/simplify.test.js index f42b5429..d6a30840 100644 --- a/test/simplifyExpression/simplify.test.js +++ b/test/simplifyExpression/simplify.test.js @@ -52,7 +52,10 @@ describe('can simplify with division', function () { ['2x/4/3', '1/6 x'], ['((2+x)(3+x))/(2+x)', '3 + x'], ['(20 * x) / (5 * (40 * y))', 'x / (10y)'], - ['400 * z / ((20 * x) / (5 * (40 * y)))', '(4000y * z) / x'] + ['400 * z / ((20 * x) / (5 * (40 * y)))', '(4000y * z) / x'], + ['20x / (40y)', 'x / (2y)'], + ['60x / (40y)', '3x / (2y)'], + ['4x / (2y)', '2x / y'] ]; tests.forEach(t => testSimplify(t[0], t[1], t[2])); // TODO: factor the numerator to cancel out with denominator From d2ec124648008fa59840fd2741d68718f683bf3e Mon Sep 17 00:00:00 2001 From: markus polleryd Date: Wed, 17 Jan 2018 11:54:31 +0100 Subject: [PATCH 3/3] minor thought fail fix --- .../fractionsSearch/cancelLikeTerms.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/simplifyExpression/fractionsSearch/cancelLikeTerms.js b/lib/simplifyExpression/fractionsSearch/cancelLikeTerms.js index d8fc87bf..85f84b4b 100644 --- a/lib/simplifyExpression/fractionsSearch/cancelLikeTerms.js +++ b/lib/simplifyExpression/fractionsSearch/cancelLikeTerms.js @@ -400,19 +400,19 @@ function cancelCoeffs(numerator, denominator){ // Sometimes the fraction reduces to a constant e.g. 6 / 2 -> 3, // in which case the denominator coefficient should be null - let newDenominator = null; + let newDenominatorCoeff = null; let newNumerator = null; if (Node.Type.isConstant(reduceStatus.newNode)) { newNumerator = Node.Creator.polynomialTerm(numeratorVariable, numeratorExponent, reduceStatus.newNode); - newDenominator = null; + newDenominatorCoeff = null; } else { newNumerator = Node.Creator.polynomialTerm(numeratorVariable, numeratorExponent, reduceStatus.newNode.args[0]); - newDenominator = reduceStatus.newNode.args[1]; + newDenominatorCoeff = reduceStatus.newNode.args[1]; } - denominator = Node.Creator.polynomialTerm(denominatorVariable, denominatorExponent, newDenominator); + const newDenominator = Node.Creator.polynomialTerm(denominatorVariable, denominatorExponent, newDenominatorCoeff); - return new CancelOutStatus(newNumerator, denominator, true); + return new CancelOutStatus(newNumerator, newDenominator, true); } module.exports = cancelLikeTerms;