Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding factoring support #104

Merged
merged 8 commits into from
Feb 10, 2017
Merged

adding factoring support #104

merged 8 commits into from
Feb 10, 2017

Conversation

aelnaiem
Copy link
Collaborator

@aelnaiem aelnaiem commented Jan 27, 2017

Issue: #50

Adds support for factoring by:

  • difference of squares
  • perfect squares
  • sum product rule

Quadratic equation to be added in the future. The reason I didn't add it yet is because it requires simplification of only what will be in the brackets (without expansion), and I didn't want to make a decision on how that should happen.

Copy link
Contributor

@evykassirer evykassirer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yay factoring! added some thoughts :)


const Node = require('../node');

// TODO(ael)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring

@@ -0,0 +1,45 @@
'use strict';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you merge in the new linter changes? apparently we don't need 'use strict' in modules because they're strict by default (the linter will catch this now 😄 )


// TODO(ael)
function isQuadratic(node) {
if (!Node.Type.isOperator(node) || node.op !== '+') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can now do if (!Node.Type.isOperator(node, '+') thanks to a recent change :)

const constantTerms = node.args.filter(Node.Type.isConstant);

if (secondDegreeTerms.length !== 1 || firstDegreeTerms.length > 1 ||
constantTerms.length !== 1) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmm why'd you choose this combination?

I agree that there should be a secondDegreeTerm for sure

but what about x^2 + x? that seems like a quadratic, but there's no constant term

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmmmmm. Interesting. You're right :)


const secondDegreeTerms = node.args.filter(isSecondDegree);
const firstDegreeTerms = node.args.filter(isFirstDegree);
const constantTerms = node.args.filter(Node.Type.isConstant);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what if there are other args that don't fall into one of these buckets? we should return false right? so I guess check that the length of each of these add up to the number of args

if (constantTermValue < 0 &&
firstTermCoeffRootValue % 1 === 0 &&
constantTermRootValue % 1 === 0) {
const polyTerm = Node.Creator.polynomialTerm(symbol, null, firstTermCoeffRootNode);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you split this line into 2 lines? (and everywhere else you have a line like this)

const secondTermCoeffValue = secondTermNode.getCoeffValue();
if (secondTermCoeffValue < 0) {
constantTermRootValue = constantTermRootValue * -1;
constantTermRootNode = Negative.negate(constantTermRootNode);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

alternatively, you can just make constantTermRootNode at the times you need it instead of at the beginning

not sure if that's better or not

I have a slight preference for this because it becomes more obvious why you need it

['x^2 + 4x + 1', 'x^2 + 4x + 1'],
];
tests.forEach(t => testFactorQuadratic(t[0], t[1]));
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add some tests for things that shouldn't factor yet? to make sure we're not treating things as difference of square when they're not, etc.

I'm especially cautious about stuff with signs e.g. -x^2 - 2x - 1

constantTermRootNode = Negative.negate(constantTermRootNode);
}

const perfectProduct = 2 * firstTermCoeffRootValue * constantTermRootValue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some more comments describing how these variables fit into things might be helpful

I understood what was going on, but had to read over the code a couple times to get it

const factorPairs = ConstantFactors.getFactorPairs(constantTermValue, true);
for (const pair of factorPairs) {
if (pair[0] + pair[1] === secondTermCoeffValue) {
const polyTerm = Node.Creator.polynomialTerm(symbol, null, firstTermCoeffRootNode);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we know that the coeff is 1, so the polyTerm is just the symbol right?

return false;
}

if (node.args.length > 3) {
return false;
}

const secondDegreeTerms = node.args.filter(isSecondDegree);
const firstDegreeTerms = node.args.filter(isFirstDegree);
const symbolSet = Symbols.getSymbolsInExpression(node);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this might be confusing to an outsider - add a comment explaining what this is checking?

return exponent && exponent.value === '2';
}
return false;
function isPolynomialTermOfDegree(degree) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a docstring that also makes it clear we have to pass a string as the degree (which is unfortunate :p)

const exponent = polyTerm.getExponentNode(true);
return exponent && exponent.value === '1';
// check that there are no terms that don't fall into these groups
if (secondDegreeTerms.length + firstDegreeTerms.length + constantTerms.length !== node.args.length) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line is too long, idk how strict we wanna be about this though

@@ -21,19 +20,20 @@ function factorQuadratic(node) {
}

// get a, b and c
let firstTermNode, secondTermNode, constantTermValue;
let symbol, aValue, bValue, cValue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe have them default to 0? what happens if they don't get set (if those terms aren't there)

}

// factor sum product rule e.g. x^2 + 3x + 2
status = factorSumProductRule(node, symbol, aValue, bValue, cValue, negate);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could loop through these like we do in the other places where we check lots of different simplification functions

const aRootNode = Node.Creator.constant(aRootValue);

const polyTerm = Node.Creator.polynomialTerm(
symbol, null, aRootNode);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we know that aRootNode is 1 - so you could just not pass aRootNode at all right?

Node.Creator.polynomialTerm(symbol)

@@ -120,6 +121,7 @@ function addNegativeOneCoefficient(node) {
polyTerm.getSymbolNode(),
polyTerm.getExponentNode(),
polyTerm.getCoeffNode(),
false,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a comment for what this means maybe

@@ -84,7 +84,8 @@ function addPositiveOneCoefficient(node) {
newNode.args[i] = Node.Creator.polynomialTerm(
polyTerm.getSymbolNode(),
polyTerm.getExponentNode(),
Node.Creator.constant(1));
Node.Creator.constant(1),
true);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add comment for what true means

return Node.Status.noChange(node);
}


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra line

@@ -48,12 +48,12 @@ const NodeCreator = {
// exponent might be null, which means there's no exponent node.
// similarly, coefficient might be null, which means there's no coefficient
// the symbol node can never be null.
polynomialTerm (symbol, exponent, coeff, coeffIsNegOne=false) {
polynomialTerm (symbol, exponent, coeff, coeffIsOne=false, coeffIsNegOne=false) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we could combine coeffIsOne and coeffIsNegOne to a shared variable... something about explicit coefficients

or is that more confusing?

@@ -12,13 +12,14 @@ function isQuadratic(node) {
return false;
}

// get the number of symbols in the expression and ensure there's only one
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sorry I'm being really picky now so you don't have to change this but

I think this comment is pretty close to what the code is doing, I meant more like
"make sure only one symbol appears in the expression, i.e. just x and not both x and y"

the point of confusion for people could be that it's a set (which you show in the var name which is helpful, so it's probably fine) and there can be multiple symbol terms (multiple symbols) as long as they're of the same symbol name


const polyTerm = Node.Creator.polynomialTerm(
symbol, null, aRootNode);
const polyTerm = Node.Creator.polynomialTerm(symbol, null);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh wait you can just use Node.Creator.symbol

@evykassirer
Copy link
Contributor

I'm worried that 2x^2 + 2x -> 2x(x + 1) might still not work

const exponent = Node.Creator.constant(2);

if (negate) {
paren = Negative.negate(paren);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you negate before doing the ^2, won't the negating cancel out?

ie (-(x+2))^2

I see your test passes though, which confuses me

@evykassirer
Copy link
Contributor

still missing tests for factorSymbol with negate - I think there might be bugs there

@@ -56,35 +69,13 @@ function factorQuadratic(node) {
cValue = -cValue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these should default to 0 above, or else negating doesn't really make sense in the null case

@evykassirer
Copy link
Contributor

added 6 comments, mostly just stuff you missed from the last code review

lemme know if there's anything I can do to make it easier for you to see and check off my comments to make sure you address them all (I know github doesn't really do a good job of making it easy)

…gative and also fixing negation when factoring
@aelnaiem aelnaiem merged commit 906cddf into master Feb 10, 2017
@aelnaiem aelnaiem deleted the ae_quadratic branch February 10, 2017 17:22
@evykassirer
Copy link
Contributor

👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants