diff --git a/lib/checks/canFindDenominatorInNumerator.js b/lib/checks/canFindDenominatorInNumerator.js new file mode 100644 index 00000000..f7beaf30 --- /dev/null +++ b/lib/checks/canFindDenominatorInNumerator.js @@ -0,0 +1,98 @@ +const Node = require('../node'); + +// Returns true if by adding a term you can simplify part of the function into +// an integer +// e.g. (2x+1)/(2x+3) -> True because of the following simplification +// (2x+1)/(2x+3) -> (2x + 3)/(2x + 3) - 2/(2x + 3) -> 1 - 2/(2x + 3) +// e.g. (2x+1)/(2x^2 + 3) -> False +// ============================================================================== +// CHECKS +// - Check for division in parent node +// - Numerator has to be addition/subtraction of a polynomial term to the power +// of 1 and a constant term, in that order OR a polynomial term to the +// power of 1 +// - Denominator has to be addition/subtraction of a polynomial term to the power +// of 1 and a constant term, in that order. +// - Check to see that the denominator and numerator have the same symbol + +function canFindDenominatorInNumerator(node) { + if (node.op !== '/' ) { + return false; + } + let numerator = node.args[0]; + let denominator = node.args[1]; + if (Node.Type.isParenthesis(numerator)) { + numerator = numerator.content; + } + if (Node.Type.isParenthesis(denominator)) { + denominator = denominator.content; + } + + let numeratorArgsLength; + // If numerator has args, but it's just a polynomial term, length is 1 + // Ex. 3x/2x+3 => numeratorArgsLength=1 + if (Node.PolynomialTerm.isPolynomialTerm(numerator)) { + numeratorArgsLength = 1; + } + // If numerator has args and args are two seperate values length is 2 + // Ex. 3x+4/2x+3 => numeratorArgsLength=2 + else if (numerator.op === '+' || numerator.op === '-') { + numeratorArgsLength = numerator.args.length; + } + // If numerator doesn't have args and isn't a polynomial term, there's + // nothing the added functionality can do + // Ex. 3/(2x + 3) => False + else { + return false; + } + let denominatorArgsLength; + if (denominator.op === '+' || denominator.op === '-') { + denominatorArgsLength = denominator.args.length; + } + // If denominator doesn't have args, it's length is 1. This case is already + // resolved by splitting the denominator into all the numerators + // Ex. (x + 3)/2x => x/2x + 3/2x + else { + return false; + } + // Function doesn't support denominators with args > 2 + if (denominatorArgsLength !== 2) { + return false; + } + // Check if numerator's second argument is a constant if numerator has two arguments + if (numeratorArgsLength === 2) { + if (!Node.Type.isConstant(numerator.args[1])) { + return false; + } + } + // Check if denominator's second argument is a constant + if (!Node.Type.isConstant(denominator.args[1])) { + return false; + } + // Defines the first term depending on whether there's a coefficient value + // with the first term + let numeratorFirstTerm; + if (numerator.op === '+') { + numeratorFirstTerm = new Node.PolynomialTerm(numerator.args[0]); + } + else { + numeratorFirstTerm = new Node.PolynomialTerm(numerator); + } + let denominatorFirstTerm; + if (denominator.op === '+') { + denominatorFirstTerm = new Node.PolynomialTerm(denominator.args[0]); + } + // If an exponent exists (aka not x^1), return false + if (numeratorFirstTerm.getExponentNode() || + denominatorFirstTerm.getExponentNode()) { + return false; + } + // Check that the symbols are the same, Ex. (x+1)/(y+1) would not pass + if (!(numeratorFirstTerm.getSymbolName() === + denominatorFirstTerm.getSymbolName())) { + return false; + } + return true; +} + +module.exports = canFindDenominatorInNumerator; diff --git a/lib/checks/canSimplifyPolynomialTerms.js b/lib/checks/canSimplifyPolynomialTerms.js index eed134d0..5e105f6b 100644 --- a/lib/checks/canSimplifyPolynomialTerms.js +++ b/lib/checks/canSimplifyPolynomialTerms.js @@ -1,4 +1,5 @@ const canAddLikeTermPolynomialNodes = require('./canAddLikeTermPolynomialNodes'); +const canFindDenominatorInNumerator = require('./canFindDenominatorInNumerator'); const canMultiplyLikeTermPolynomialNodes = require('./canMultiplyLikeTermPolynomialNodes'); const canRearrangeCoefficient = require('./canRearrangeCoefficient'); @@ -6,6 +7,7 @@ const canRearrangeCoefficient = require('./canRearrangeCoefficient'); // polynomial terms that can be combined in some way. function canSimplifyPolynomialTerms(node) { return (canAddLikeTermPolynomialNodes(node) || + canFindDenominatorInNumerator(node) || canMultiplyLikeTermPolynomialNodes(node) || canRearrangeCoefficient(node)); } diff --git a/lib/checks/index.js b/lib/checks/index.js index ddf93f61..f0dbf43f 100644 --- a/lib/checks/index.js +++ b/lib/checks/index.js @@ -1,4 +1,5 @@ const canAddLikeTermPolynomialNodes = require('./canAddLikeTermPolynomialNodes'); +const canFindDenominatorInNumerator = require('./canFindDenominatorInNumerator'); const canMultiplyLikeTermConstantNodes = require('./canMultiplyLikeTermConstantNodes'); const canMultiplyLikeTermPolynomialNodes = require('./canMultiplyLikeTermPolynomialNodes'); const canRearrangeCoefficient = require('./canRearrangeCoefficient'); @@ -9,6 +10,7 @@ const resolvesToConstant = require('./resolvesToConstant'); module.exports = { canAddLikeTermPolynomialNodes, + canFindDenominatorInNumerator, canMultiplyLikeTermConstantNodes, canMultiplyLikeTermPolynomialNodes, canRearrangeCoefficient, diff --git a/lib/simplifyExpression/breakUpNumeratorSearch/index.js b/lib/simplifyExpression/breakUpNumeratorSearch/index.js index 2dcff7a2..1fd40baa 100644 --- a/lib/simplifyExpression/breakUpNumeratorSearch/index.js +++ b/lib/simplifyExpression/breakUpNumeratorSearch/index.js @@ -1,3 +1,4 @@ +const canFindDenominatorInNumerator = require('../../checks/canFindDenominatorInNumerator'); const ChangeTypes = require('../../ChangeTypes'); const Node = require('../../node'); const TreeSearch = require('../../TreeSearch'); @@ -27,8 +28,47 @@ function breakUpNumerator(node) { // At this point, we know that node is a fraction and its numerator is a sum // of terms that can't be collected or combined, so we should break it up. const fractionList = []; - const denominator = node.args[1]; - numerator.args.forEach(arg => { + let denominator = node.args[1]; + + // Check if we can add/subtract a constant to make the fraction nicer + // fraction e.g. (2+x)/(5+x) -> (5+x)/(5+x) - 3/(5+x) + if (canFindDenominatorInNumerator(node)) { + let denominatorParenRemoved = false; + if (Node.Type.isParenthesis(denominator)) { + denominatorParenRemoved = true; + denominator = denominator.content; + } + const newNumerator = []; + + // The constant value difference between the numerator and the denominator + const num_n = numerator.args.length; + const den_n = denominator.args.length; + const numeratorFirstTerm = new Node.PolynomialTerm(numerator.args[0]); + const denominatorFirstTerm = new Node.PolynomialTerm(denominator.args[0]); + const numeratorPolyCoeff = numeratorFirstTerm.getCoeffValue(); + const denominatorPolyCoeff = denominatorFirstTerm.getCoeffValue(); + const multiplier = numeratorPolyCoeff / denominatorPolyCoeff; + + const numeratorConstant = parseInt(numerator.args[num_n-1].value) || 0; + const denominatorConstant = parseInt(denominator.args[den_n-1].value) || 0; + const addedConstant = numeratorConstant - (denominatorConstant * multiplier); + + if (multiplier === 1) { + newNumerator.push(denominator); + } + else { + const multiplierNode = Node.Creator.constant(multiplier); + newNumerator.push(Node.Creator.operator('*', [multiplierNode, denominator])); + } + newNumerator.push(Node.Creator.constant(addedConstant)); + + numerator = newNumerator; + + if (denominatorParenRemoved) { + denominator = Node.Creator.parenthesis(denominator); + } + } + numerator.forEach(arg => { const newFraction = Node.Creator.operator('/', [arg, denominator]); newFraction.changeGroup = 1; fractionList.push(newFraction); @@ -41,5 +81,4 @@ function breakUpNumerator(node) { return Node.Status.nodeChanged( ChangeTypes.BREAK_UP_FRACTION, node, newNode, false); } - module.exports = search; diff --git a/test/checks/checks.test.js b/test/checks/checks.test.js index 4df3005f..14264317 100644 --- a/test/checks/checks.test.js +++ b/test/checks/checks.test.js @@ -29,3 +29,26 @@ describe('canSimplifyPolynomialTerms addition', function() { ]; tests.forEach(t => testCanCombine(t[0], t[1])); }); + +describe('canSimplifyPolynomialTerms denominator in numerator', function() { + const tests = [ + ['(x+1)/(x-2)', true], + ['(2x)/(x+4)', true], + ['(x)/(x+4)', true], + ['(x)/(2x+4)', true], + ['(x+3)/(x)', false], // Normal breakup function already solves this + ['(2x + 3)/(2x + 2)', true], + ['(2x+3)/(2x)', false], // Normal breakup function already solves this + ['(2x)/(2x + 2)', true], + ['(5x + 3)/(4)', false], // Normal breakup function already solves this + // Not supported yet + ['(2x)/(2 + 2x)', false], + ['(2 + 2x)/(3x + 4)', false], + ['(x + 3)/(2x^2 + 5)', false], + ['(3x^2 + 3)/(2x^2 + 5)', false], + ['(5x^2 + 3)/(2x + 5)', false], + ['(5x^2-4x + 3)/(2x + 5)', false], + ['(-4x + 3)/(2x^2 + 5x +7)', false], + ]; + tests.forEach(t => testCanCombine(t[0], t[1])); +}); diff --git a/test/simplifyExpression/breakUpNumeratorSearch/breakUpNumeratorSearch.test.js b/test/simplifyExpression/breakUpNumeratorSearch/breakUpNumeratorSearch.test.js index 73aef5c1..b3901a88 100644 --- a/test/simplifyExpression/breakUpNumeratorSearch/breakUpNumeratorSearch.test.js +++ b/test/simplifyExpression/breakUpNumeratorSearch/breakUpNumeratorSearch.test.js @@ -11,6 +11,15 @@ describe('breakUpNumerator', function() { ['(x+3+y)/3', '(x / 3 + 3/3 + y / 3)'], ['(2+x)/4', '(2/4 + x / 4)'], ['2(x+3)/3', '2 * (x / 3 + 3/3)'], + ['(2x + 3)/(2x + 2)', '((2x + 2) / (2x + 2) + 1 / (2x + 2))'], + ['(2x - 3)/(2x + 2)', '((2x + 2) / (2x + 2) - 5 / (2x + 2))'], + ['(2x + 3)/(2x)', '(2x / (2x) + 3 / (2x))'], + ['(3 + 2x)/(2x)', '(3 / (2x) + 2x / (2x))'], + ['(4x + 3)/(2x + 2)', '(2 * (2x + 2) / (2x + 2) - 1 / (2x + 2))'], + // TODO: Pre-sort numerator and denominator 'args' + // ['(2x)/(3 + 2x)', '((3 + 2x) / (3 + 2x) - 3 / (3 + 2x))'], + // TODO: Fix beginning checks in 'breakUpNumeratorSearch' + // ['(2x)/(2x + 3)', '((2x + 3) / (2x + 3)) - 3 / (2x + 3)'], ]; tests.forEach(t => testBreakUpNumeratorSearch(t[0], t[1])); });