Skip to content
This repository has been archived by the owner on Aug 29, 2024. It is now read-only.

Enhancement: Multiply denominators with terms #88

Merged
merged 17 commits into from
Jul 19, 2017

Conversation

arbylee
Copy link
Contributor

@arbylee arbylee commented Jan 22, 2017

Attempt to resolve #51 and implement removeSymbolFromDenominator.
This change also needed to enhance the cancelLikeTerms logic. Cancelling needed to account for denominators matching what was being multiplied. Otherwise if they didn't cancel out, it would continue trying to multiply the denominator across until the equation reaches the length limit. Let me know any feedback

@evykassirer
Copy link
Contributor

Looking sweet! Thanks so much for this big change and also all the little changes you're making to make the codebase cleaner 🌟

I'm feeling a bit sick today, so I'll take a more thorough look early this week. I also hope to chat about this a bit with Ahmed, who wrote the original equations code.

@evykassirer evykassirer mentioned this pull request Jan 25, 2017
3 tasks
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.

Thank you!!

I made a whole bunch of comments:

  • a bunch are gratitude for helpful things you did 😄
  • a bunch are also formatting/documentation related
  • some are small changes for you to make

For the most part I think this approach is right. My one concern is around the additions to cancelling out- the is existing code is already is a bit more messy than average, so I'm happy to discuss what we can do a bit more once you've read over the comments.

You're the first contributor to make a change this big - thanks so much for your time, and for your patience in waiting for me to have the time to review it ;D

I'm also in general really excited for this change because we'll be able to solve a whole bunch more equations thanks to you!

lib/Symbols.js Outdated
@@ -28,7 +28,7 @@ Symbols.getLastSymbolTerm = function(node, symbolName) {
if(isSymbolTerm(node, symbolName)) {
return node;
}
// Otherwise, it's a sum of terms. Look through the operands for a term
// If it's a sum of terms, look through the operands for a term
Copy link
Contributor

Choose a reason for hiding this comment

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

thanks :)

lib/Symbols.js Outdated
return new Node.PolynomialTerm(node).getCoeffNode();
} catch (e) {
if (e.message.startsWith('denominator must be constant node')) {
// Not sure what this should return for a case such as 1/(2x)
Copy link
Contributor

Choose a reason for hiding this comment

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

probably just return null like below - unless you want to use part of the 1/(2x) term somehow

and even if the error isn't "denominator must be constant node..." you would also want to return null right?

we should probably better define what "symbol term" and "non symbol term" mean for things like 1/2x, depending on whatever's useful for solving equations -- and then yeah I think moving this stuff to an equation specific file would make more sense

lib/Symbols.js Outdated
// Returns if `node` is a polynomial term with symbol `symbolName`
function isSymbolTerm(node, symbolName) {
if (Node.PolynomialTerm.isPolynomialTerm(node)) {
const polyTerm = new Node.PolynomialTerm(node);
if (polyTerm.getSymbolName() === symbolName) {
return true;
}
} else if (hasDenominatorSymbol(node, symbolName)) {
return true
Copy link
Contributor

Choose a reason for hiding this comment

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

missing semi colon! can you run the linter with npm lint to make sure you're not missing other stuff?

also, generally we put else on new line, so:

    return true;
  }
}
else if (hasDenominatorSymbol(node, symbolName)) {

(I just added this to #44)

lib/Symbols.js Outdated
}
return false;
}

function hasDenominatorSymbol(node, symbolName) {
if (Node.Type.isOperator(node) && node.op === '/') {
return Symbols.getSymbolsInExpression(node).size > 0;
Copy link
Contributor

Choose a reason for hiding this comment

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

you should use symbolName here to make sure that the symbol in the expression is indeed the symbol we were looking for

@@ -158,6 +90,7 @@ PolynomialTerm.isPolynomialTerm = function(
node, onlyImplicitMultiplication=false) {
try {
// will throw error if node isn't poly term
//
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)

@@ -179,4 +112,88 @@ function negativeCoefficient(node) {
return node;
}

function parseNode(node, onlyImplicitMultiplication) {
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 a docstring, now that it's its own function?

@@ -20,6 +20,52 @@ class CancelOutStatus {
// e.g. (2x^2 * 5) / 2x^2 => 5 / 1
// Returns a Node.Status object
function cancelLikeTerms(node) {
// Cancel out terms if the term being multiplied
// is the same as the denominator
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.

can you merge in master (now with #91) and change this to isOperator(node, '*')? thanks!

@@ -20,6 +20,52 @@ class CancelOutStatus {
// e.g. (2x^2 * 5) / 2x^2 => 5 / 1
// Returns a Node.Status object
function cancelLikeTerms(node) {
// Cancel out terms if the term being multiplied
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 comment isn't super clear about what 'the term being multiplied' is
can you add an example?

if (Node.Type.isOperator(node) && node.op === '*') {
let newNode = clone(node);
let leftNode = newNode.args[0];
let rightNode = newNode.args[1];
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 the roles you gave left node and right node are reversed? e.g. the right node is a fraction or sum that cancels out with the left node?

also, how do you know there's only two things being multiplied by each other? there can be any number of terms in a multiplication

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm actually surprised you need this - I think that multiplyFractions will turn (2+x) * (3+x)/(2+x) into ((2+x)(3+x))/(2+x) which should then just cancel out with the existing code... but if you had to add this stuff, there's probably something missing here that needs to be added. Not sure if what you have fills the hole quite right, but let's talk about it more!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The code on master doesn't cancel out terms for (2+x) * (3+x)/(2+x), which was what prompted these changes. Currently it would distribute out the (2+x). Without canceling, my changes caused an infinite loop by multiplying the denominator, distributing out, multiplying the denominator, etc.

Regarding the left node / right node positioning, I had made an assumption based on what would happen during equations steps, but you're right that it misses out on if an equation starts as (2+x) * (3+x)/(2+x). I'll work on resolving this in the next commit

From what I can tell that scenario wouldn't occur during equation solving though. Instead of multiplying by an inverted fraction, the steps would multiply by the denominator and then divide by the numerator

// being multiplied
if (Node.Type.isOperator(leftNode) && leftNode.op === '+') {
let allMatchingDenominators = true;
for (let i = 0; i < leftNode.args.length; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

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

@arbylee
Copy link
Contributor Author

arbylee commented Jan 29, 2017

I'm actually starting to think that this enhancement is a bit premature. There's a lot of functionality that I seem to need out of cancelLikeTerms that should probably be done first before working on the equation solving. For multiplication across, these things need to be handled first
(x+1)/(1-x) * (1-x) -> x + 1
(3/(1-x) + 3/(1+x)) * (1-x) -> 6-3x
((x+1)/(1-x) + 3/(1-x)) * (1-x) -> x + 4

I'm also not sure how the current approach would deal with factorization and canceling:
(x^2 + 4)/(x-2) -> x+2

I think I'll pull out some of the generic changes here out into a separate pull request, but I'm going to pause progress on this PR and maybe look into canceling terms separately when I have some time. I feel like that's going to be a considerable beast on its own

@evykassirer
Copy link
Contributor

I honestly think we can merge a lot of the stuff in this PR :)

if in simplifyExpression/stepThrough.js you switch the order of multiplyFractionsSearch and breakUpNumeratorSearch, (x+1)/(1-x) * (1-x) would become [(x+1) * (1-x)] / (1-x) and then cancelling will work. This will then cover most of the simple cases - which is why I think it's worth making the change even though some other things won't work properly yet.

(3/(1-x) + 3/(1+x)) * (1-x) -> 6-3x and ((x+1)/(1-x) + 3/(1-x)) * (1-x) -> x + 4 could be fixed with a change to distribution where if (exactly) one of the blocks being distributed into each other has fractions, we take the other block and add it to the numerator (not sure if I'm describing this well enough, but I think that one case will fix that issue for the most part, as well as ((x+1)/(1-x) + 3/(1-x)) * (1-x))

(aside, (3/(1-x) + 3/(1+x)) * (1-x) -> 6/(x+1) but we'll have to wait for adding polynomial fractions and common denominators there and stuff to get that covered)

For factoring, we haven't done any factoring stuff yet but it's on the horizon! There's a lot of cases where your code would be helpful without needing factoring, and I think it's quite reasonable to add this functionality now and wait for factoring later.

tl;dr, I think we should:

  • switch the order of multiplyFractionsSearch and breakUpNumeratorSearch and remove your addition to the cancelling terms code
  • merge this change!
  • later, add distribution rules and factoring to handle some cancelling cases better

what do you think?

@kevinbarabash
Copy link
Contributor

((x+1)/(1-x) + 3/(1-x)) * (1-x) -> x + 4 erases information about which values of x are invalid so the expressions are 100% equivalent. It's probably be worth keeping track of the fact that 1 - x can't be 0.

@arbylee
Copy link
Contributor Author

arbylee commented Jan 31, 2017

Sure, I'll try flipping those methods tomorrow and see where I get

@evykassirer
Copy link
Contributor

@kevinbarabash very true. We don't have anything that stores information like that yet. Do you think adding it to the NodeStatus object would be a good way to do it?

@kevinbarabash
Copy link
Contributor

@evykassirer a new NodeStatus object is created for each step, right? If that's the cas we'd probably want to copy any solution constraints to all subsequent steps so that it's easy to check. Any new solution constraints would have to be unioned with previous ones. The concept of solution constraints could also be applied to things like nthRoot(x) and other functions in the future.

@evykassirer
Copy link
Contributor

hey @arbylee - did you get a chance to try it? how'd it go? :)

@arbylee
Copy link
Contributor Author

arbylee commented Feb 13, 2017

Hey @evykassirer, I did try removing the cancelLikeTerms changes and flipping multiplyFractionsSearch and breakUpNumeratorSearch. It ended up breaking a few test cases, and I haven't had enough time to look through them (I moved this past weekend). I've updated the branch if you want to have a look

@evykassirer
Copy link
Contributor

thanks! I'll take a look and see what makes sense to fix here and what makes sense to fix in other issues

this change is a nice goldmine of revealing bugs ;D

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.

ah, okay I see what you mean about not really being able to merge this until those other things are fixed... otherwise none of it works haha!

I opened issues for the bugs, for exactly what needs to be fixed. Feel free to take them on, or we can wait until someone else can do them and then come back to this.

I also added some changes you can make in the meantime.

Hope you're settling into your new place nicely!

lib/Symbols.js Outdated
@@ -49,7 +49,13 @@ Symbols.getLastSymbolTerm = function(node, symbolName) {
// e.g. 4x^2 + 2x + y with `symbolName=x` would return y
Symbols.getLastNonSymbolTerm = function(node, symbolName) {
if (isSymbolTerm(node, symbolName)) {
return new Node.PolynomialTerm(node).getCoeffNode();
try {
Copy link
Contributor

Choose a reason for hiding this comment

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

actually this might be clearer to do a check for if it's a polynomial term instead of the throw catch - that's commonly used with the PolynomialTerm constructor actually

lib/Symbols.js Outdated
@@ -63,6 +69,30 @@ Symbols.getLastNonSymbolTerm = function(node, symbolName) {
return null;
};

// Iterates through a node and returns the last term that has a
// symbolName in its denominator
Copy link
Contributor

Choose a reason for hiding this comment

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

it actually returns the denominator, not the term, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes you're right

lib/Symbols.js Outdated
@@ -71,7 +101,17 @@ function isSymbolTerm(node, symbolName) {
return true;
}
}
else if (hasDenominatorSymbol(node, symbolName)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

why did you add this to isSymbolTerm?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's been a while now so I'm trying to remember. I believe I had changed isSymbolTerm to return true if there was a symbol in the denominator, and there's logic that would then call getLastSymbolTerm. And then getLastSymbolTerm relies on this isSymbolTerm function

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Jumping back to this because it should probably be discussed (and have code/comments updated appropriately). Prior to this change, isSymbolTerm specifically was checking only for polynomial symbol terms. So a term like 1/x would not be considered a symbol term because it's not a polynomial term. This change makes it so that terms with a symbol in the denominator are considered symbol terms as well.

In turn, this affects getLastNonSymbolTerm and getLastSymbolTerm which both use isSymbolTerm. I'm not sure if this change is going against the intentions of the Symbols object. If it is, then I'll need to find some other way to get a symbol term in a denominator

Copy link
Contributor

Choose a reason for hiding this comment

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

This makes sense to me! Agreed re: updating the comments to explain this


tests.forEach(t => runTest(Symbols.getLastDenominatorSymbolTerm, 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.

extra line here (you only need one new line at the bottom of a file)

tests.forEach(t => runTest(Symbols.getLastSymbolTerm, t[0], t[1]));
});

describe('getLastDenominatorSymbolTerm', function() {
Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@@ -58,6 +58,12 @@ describe('solveEquation for =', function () {
['2(x+3)/3 = 2', 'x = 0'],
['( u )/( 0.3) = 4u + 6.28', 'u = -9.42'],
['- q - 4.36= ( 2.2q )/( 1.8)', 'q = -1.962'],
//['2/x = 1', 'x = 2'], TODO: fixme
//['2/(4x) = 1', 'x = 1/2'], TODO: bombs out
Copy link
Contributor

Choose a reason for hiding this comment

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

this is also related because (2 / (4x)) * 4x = 4x doesn't get cancelled out

I can see that we'll have to fix that first before we can actually test any of this (or have this functionality work at all)

@@ -58,6 +58,12 @@ describe('solveEquation for =', function () {
['2(x+3)/3 = 2', 'x = 0'],
['( u )/( 0.3) = 4u + 6.28', 'u = -9.42'],
['- q - 4.36= ( 2.2q )/( 1.8)', 'q = -1.962'],
//['2/x = 1', 'x = 2'], TODO: fixme
//['2/(4x) = 1', 'x = 1/2'], TODO: bombs out
//['2/(8 - 4x) = 1/2', 'x = 1'], TODO: fixme
Copy link
Contributor

Choose a reason for hiding this comment

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

same thing... :(

(2 / (8 - 4x)) * (8 - 4x) = (1/2) * (8 - 4x)
4 - 2x = (2 / (8 - 4x)) * 8 + (2 / (8 - 4x)) * -4x
(4 - 2x) - 4 = ((2 / (8 - 4x)) * 8 + (2 / (8 - 4x)) * -4x) - 4
(-2x) / -2 = (((2 / (8 - 4x)) * 8 + (2 / (8 - 4x)) * -4x) - 4) / -2
....

//['2/x = 1', 'x = 2'], TODO: fixme
//['2/(4x) = 1', 'x = 1/2'], TODO: bombs out
//['2/(8 - 4x) = 1/2', 'x = 1'], TODO: fixme
//['2/(1 + 1 + 4x) = 1/3', 'x = 1'], TODO: fixme
Copy link
Contributor

Choose a reason for hiding this comment

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

and again

(2 / (4x + 2)) * (4x + 2) = (1/3) * (4x + 2)
4x / 3 + 2/3 = (2 / (4x + 2)) * 4x + (2 / (4x + 2)) * 2
(4x / 3 + 2/3) * 3 = ((2 / (4x + 2)) * 4x + (2 / (4x + 2)) * 2) * 3

//['2/(4x) = 1', 'x = 1/2'], TODO: bombs out
//['2/(8 - 4x) = 1/2', 'x = 1'], TODO: fixme
//['2/(1 + 1 + 4x) = 1/3', 'x = 1'], TODO: fixme
//['(3 + x) / (x^2 + 3) = 1', '-x^2 + x = 0'], TODO: bombs out
Copy link
Contributor

Choose a reason for hiding this comment

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

this problem is different! it's a bad distribution

(3 / (x^2 + 3) + x / (x^2 + 3)) * (x^2 + 3) turns into
(3 / (x^2 + 3) * x^2 + 9 / (x^2 + 3) + x / (x^2 + 3) * x^2 + 3x / (x^2 + 3))

I think distribution should check to see if one of the multiplying things is also a list of fractions, and if only one of them is, then multiply the other one into its numerators. I'll make this a separate issue #138

Copy link
Contributor

Choose a reason for hiding this comment

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

eventually it'd be nice to not split the numerator like that, but I'm not sure when we'd want and when we wouldn't

//['2/(8 - 4x) = 1/2', 'x = 1'], TODO: fixme
//['2/(1 + 1 + 4x) = 1/3', 'x = 1'], TODO: fixme
//['(3 + x) / (x^2 + 3) = 1', '-x^2 + x = 0'], TODO: bombs out
//['6/x + 8/2x = 10', '(-10x) + 4x^2 = -6'], TODO: fixme
Copy link
Contributor

Choose a reason for hiding this comment

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

this is an interesting case where x * 6/x isn't multiplied to get (x * 6) / x, which would cancel out

one solution is just to have better cancelling code that doesn't need it to be a fraction.

another (easier for now) solution would be to add a case for a symbol times a symbol in the denominator (i.e. we don't want 9/2 * x to multiply to 9x / 2, but we would want 9/x * x to multiply to 9)

... anyways I'll open an issue #139 for this too

(btw 8/2x is parsed as 8/2 x ie 4x, not as 8 / 2x)

@evykassirer
Copy link
Contributor

also @arbylee

do you want some mathsteps stickers as a thank you for your contributions? 😸

img_20170218_173323

if you're interested, email mathsteps@socratic.org with your mailing address and I can send them over!

@arbylee
Copy link
Contributor Author

arbylee commented Feb 24, 2017

@evykassirer Rebased and pushed with just the comment change for now. I'll have a look at some of the issues raised to see where I can help. And yes, I'd love some mathsteps stickers 😄

@aelnaiem
Copy link
Contributor

Hi @arbylee, I'd love to get this into master, what can I do to support you?

@evykassirer
Copy link
Contributor

evykassirer commented Mar 30, 2017

@aelnaiem there are some things blocking this change from being useful, around cancelling

for this code to work, we first need to fix: #137 #138 and #139

(see conversation above for details on that)

@aelnaiem
Copy link
Contributor

Great! So #138 is finished, and we only need #137 and #139 right? I'm thinking about how to denote high-priority issues. Those that block other things basically.

@evykassirer
Copy link
Contributor

I think a 'high priority' tag would do the trick!

I'd also use it for things that are blocked but we want to do soon

@ldworkin
Copy link
Contributor

So all blocking issues are taken care of now. @evykassirer how should we proceed? I could familiarize myself with this pull and try to fix the conflicts? Not sure if @arbylee is around to help?

@evykassirer
Copy link
Contributor

If this is urgent for Socratic you could take it over right away, but if you have stuff to work on and can give @arbylee until next week to see this and finish if they want, I think that'd be ideal. Your call though, I know this was a pretty big thing

@arbylee
Copy link
Contributor Author

arbylee commented Jul 18, 2017

I've rebased and I think addressed the remaining change requests on this. For the test case @ldworkin mentioned, I've only set it to NO_STEPS for now. I can't recall the context exactly (I think I just wanted to see how edge cases would behave in existing code), but I originally added that test in Cleanup from feedback, add comments, and rebase as NO_STEPS anyway. I think further simplification of that particular case could be handled as a separate issue. Thoughts?

@ldworkin
Copy link
Contributor

Awesome, this looks great @arbylee. I'll make a separate note/issue to address that test case. @evykassirer thoughts on merging?!

@evykassirer
Copy link
Contributor

kevin mentioned earlier

((x+1)/(1-x) + 3/(1-x)) * (1-x) -> x + 4 erases information about which values of x are invalid so the expressions are 100% equivalent. It's probably be worth keeping track of the fact that 1 - x can't be 0.

(see above for more context)

we should open an issue for this thing so we can address it once we make the NodeStatus more complicated and able to support constraints

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.

ahhhh amazing thanks!!! just commented on a style nit, and one case I think you're missing, after that looks ready to go! So excited for this change :D

lib/Symbols.js Outdated
@@ -47,7 +47,9 @@ Symbols.getLastSymbolTerm = function(node, symbolName) {
// e.g. 4x^2 + 2x + y with `symbolName=x` would return y
Symbols.getLastNonSymbolTerm = function(node, symbolName) {
if (isSymbolTerm(node, symbolName)) {
return new Node.PolynomialTerm(node).getCoeffNode();
if (Node.PolynomialTerm.isPolynomialTerm(node)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

this would probably be cleaner as if (isSymbolTerm(node, symbolName) && Node.PolynomialTerm.isPolynomialTerm(node))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That would actually change the behavior of the function, but I've cleaned it up to be more obvious

lib/Symbols.js Outdated
// symbolName in its denominator
// e.g. 1/(2x) with `symbolName=x` would return 2x
// e.g. 1/(x+2) with `symbolName=x` would return x+2
// e.g. 1/(x+2) + (x+1)/(2x+3) with `symbolName=x` would return 2x+3
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm curious - does it return 2x+3 or does it wrap it in parens like (2x+3) ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is wrapped in parens, updated

lib/Symbols.js Outdated
// e.g. false for 5x
function hasDenominatorSymbol(node, symbolName) {
if (Node.Type.isOperator(node) && node.op === '/') {
const allSymbols = 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 will return true for x/2 too, so should this line be calling Symbols.getSymbolsInExpression only on the denominator?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You're right. I've corrected that now

// pass for now
// Ensures that a symbol is not in the denominator by multiplying
// both sides by the denominator if there is a symbol present.
EquationOperations.removeSymbolFromDenominator = function(equation, symbolName) {
Copy link
Contributor

Choose a reason for hiding this comment

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

🎉

const leftNode = equation.leftNode;
const denominator = Symbols.getLastDenominatorWithSymbolTerm(leftNode, symbolName);
if (denominator) {
return performTermOperationOnEquation(
Copy link
Contributor

Choose a reason for hiding this comment

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

I love how simple this change is thanks to the great work @aliang8 and @ldworkin have done 😍

['x/(x+3)', 'x / (x + 3)'],
];

tests.forEach(t => runTest(Symbols.getLastSymbolTerm, 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 pass 'x' in too, and then also have some tests where there are multiple symbols and you are looking for one of them? e.g. '1/x + y', y

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added some test cases with other variables

['1/x', 'x'],
['1/(x+2)', '(x + 2)'],
['1/(x+2) + 3x', '(x + 2)'],
['1/(x+2) + 3x/(1+x)', '(1 + x)'],
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 2 + 2/x + x/2 to test that case I raised above?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done. There was even an existing issue with how symbols were being parsed that was made obvious with this particular test case. I've added a commit to recursively parse the nodes for symbols. It was previously only comparing 2 + 2/x and x/2 as the only two nodes, instead of checking 2 and 2/x individually

Copy link
Contributor

Choose a reason for hiding this comment

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

yay! glad we caught that :D

['2/(8 - 4x) = 1/2', 'x = 1'],
['2/(1 + 1 + 4x) = 1/3', 'x = 1'],
['(3 + x) / (x^2 + 3) = 1', 'x = [0, 1]'],
['6/x + 8/2x = 10', NO_STEPS],
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 a TODO for this just so it's clear that this isn't what we want?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I realized that the test I wanted from this was actually missing parens in 8/(2x). I've added that and adjusted the test result.

I didn't leave the previous test case without parens because it started to look similar to the test cases in the other TODO related to complex numbers. I can add it back with a comment if you think it's a separate scenario

Copy link
Contributor

@evykassirer evykassirer Jul 19, 2017

Choose a reason for hiding this comment

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

@ldworkin how do you want to track this? You said "I'll make a separate note/issue to address that test case."

is it in your Asana board? or can we open an issue here? should we just put the TODO in the code? I'm fine as long as it's being noted somewhere :) so we remember

Copy link
Contributor

Choose a reason for hiding this comment

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

It's in our Asana

@ldworkin
Copy link
Contributor

So excited for this, thanks for all your work, @arbylee !!

@ldworkin
Copy link
Contributor

Dammit, this change a21240d broke something! Investigating, will try to fix.

@ldworkin
Copy link
Contributor

Ok, I ended up reverting that change. I need to revisit the logic there. So this should be good -- not sure why it says there are still conflicts/failing checks, I think if we merge the latest master into it, it will be all good.

lib/Symbols.js Outdated
@@ -76,10 +83,13 @@ Symbols.getLastDenominatorWithSymbolTerm = function(node, symbolName) {
// Otherwise, it's a sum of terms. e.g. 1/x + 1(2+x)
// Look through the operands for a
// denominator term with `symbolName`
else if (Node.Type.isOperator(node) && node.op === '+') {
else if (Node.Type.isOperator(node, '+')) {
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

🎉 so excited about this, thanks @arbylee

@evykassirer evykassirer merged commit 1cbae0f into google:master Jul 19, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Equations: multiply by x on both sides
6 participants