-
Notifications
You must be signed in to change notification settings - Fork 1
Fractions #50
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
base: master
Are you sure you want to change the base?
Fractions #50
Conversation
lib/rule-list.js
Outdated
let sameDenom | ||
// rule cannot apply if all of the terms are fractions | ||
// and they all have the same denominator | ||
if (terms.every(term => query.isDiv(term))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should avoid requiring every single term b/c it prevents handling of common denominators of fractions surrounded by non-fraction terms, e.g.
1 + 2/x + 3/y + 4
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should be able to run matchNode
on the current node with the #a_0 / #b_0 + ...
as the pattern to get the start/end indexes of the first group of 2 or more consecutive fractions. You'll have the pattern to a matching function before you can use it with matchNode
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, I didn't consider using matchNode
inside of a defineRule
function. Wouldn't that pattern not take into consideration the case of non-fraction terms (1 + 2/x + 3/y + 4
)?
lib/rule-list.js
Outdated
(num, i) => build.mul(num, | ||
build.number(LCM / denoms[i]))) | ||
} else { | ||
newDenoms = build.mul(...denoms) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't ideal, if all of the denominators are x
then we'll make this overly complicated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When will all the denominators be x
? I don't think COMMON_DENOMINATORS will match that.
lib/rule-list.js
Outdated
(node) => { | ||
const terms = node.args | ||
// an array storing the index where fraction is negative | ||
const negatives = terms.map(term => query.isNeg(term)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be could if we could extract this logic for reuse. Don't worry about it for this review, but something to think about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What logic are you referring to? The negatives array logic or the rule itself?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Computing the negatives
array from the terms.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed!
lib/__test__/rule-list.test.js
Outdated
@@ -181,12 +188,14 @@ describe('rules', () => { | |||
|
|||
suite('common denominators', rules.COMMON_DENOMINATOR, [ | |||
['2/6 + 1/4', '(2 * 2) / (6 * 2) + (1 * 3) / (4 * 3)'], | |||
['2/(3 + x) + 3/(2 + x)', '(2 * (2 + x)) / ((3 + x) * (2 + x)) + (3 * (3 + x)) / ((3 + x) * (2 + x))'], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There should be some tests with:
- a mix of numeric denominators and variable denominators
- repeated variable denominators
- repeated numeric denominators
- non-fractional terms in the sum
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
COMMON_DENOMINATORS won't match when the denominator is the same throughout regardless of whether they are variable or numeric. I'm not entirely sure how we want to handle non-fractional terms in the sum yet.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a couple of options:
- don't include non-fractions when matching
- include non-fractions, but convert them to fractions before computing the common denominator.
You should ask Gen for their input on this.
What happens if some of the fractions have the same denominator while others don't?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yup good points. I had a test case that was commented out addressing this issue. I will ask for Gen's input.
What happens if some of the fractions have the same denominator while others don't?
It works fine in this scenario. I've added a test case for this. The only time when this rule doesn't match is when all the terms are fractions and they all have the same denominator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a fix for it.
lib/rule-list.js
Outdated
? build.div(term, build.number(1)) | ||
: query.isNeg(term) | ||
? term.args[0] | ||
: term) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is super confusing. Can you rewrite this use if-else
statements?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah xD I realized. Sure.
lib/rule-list.js
Outdated
|
||
let sameDenom | ||
// rule cannot apply if all of the terms are fractions | ||
// and they all have the same denominator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It might be worth moving this comment up to the top of the function so that people don't have to read the whole function to understand the restrictions.
lib/rule-list.js
Outdated
const denom = getDenominator(terms[0]) | ||
|
||
sameDenom = terms.every(term => | ||
print(getDenominator(term)) == print(denom)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We might want to encapsulate this into an equals
function in utils.js
.
lib/rule-list.js
Outdated
(node) => { | ||
node = canApplyRule(CONVERT_TO_FRACTION, node) | ||
? applyRule(CONVERT_TO_FRACTION, node) | ||
: node |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We convert non-fraction terms to fractions here, but in the match function part of this rule we return false if any of the terms are non-fractions. Am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first part is correct. The match function of COMMON_DENOMINATORS
returns false when all the terms are fractions and they all have the same denoms or none of the terms are fractions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see, it has to have at least one fraction... I obviously need to get more sleep. :P
? node.args[0].args[1] | ||
: node.args[1] | ||
|
||
export const isDecimal = (node) => query.isNumber(node) && query.getValue(node) % 1 != 0 |
There was a problem hiding this comment.
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 to move these helper functions to math-nodes
?
lib/rule-list.js
Outdated
: LCM / denom == 1 | ||
? build.number(denom) | ||
: build.mul(build.number(denom), | ||
build.number(LCM / denom))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These chained ternaries are really hard to understand. Can you rewrite this using if-else
statements?
lib/rule-list.js
Outdated
: build.mul(num, | ||
build.number(LCM / denoms[i]))) | ||
} else { | ||
newDenoms = build.mul(...denoms) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens for 2 / x + 3 / x + 4 / y
? Can you add a tests case like this? I would expect the result to be 2y / xy + 3y / xy + 4x / xy
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, good catch. Will fix.
lib/rule-list.js
Outdated
} else { | ||
newDenoms = build.mul(...denoms) | ||
newNumerators = nums.map( | ||
(num, i) => build.mul(num, ...denoms.filter(e => e != denoms[i]))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about situations where there are multiple factors in the denominator to begin with? I think there's an implicit assumption that each fraction as a single factor denominator. Can you add a test case for 2 / (x * x) + 3 / (x * y) + 4 / (y * y)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see. In ChangeTypes
, MULTIPLYING_POLYNOMIALS
comes before COMMON_DENOMINATORS
, so we won't see this exact case. We would see, however, 2/ x^2 + 3/ x^1y^1 + 4 / y^2
which we would want to simplify to (2 * y)/ x^2y^1 + (3 * x) / x^2y^1 + (4 * x) / x^2y^1
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A similar test case that we should also consider is:
2 / ((x^2 + 1)^2 * (x + 1)^1) + 3 / ((x^2 + 1)^2 * x)
This looks good except for the failing test. Any idea what's going on there? It looks like the rule returns an empty string. |
This PR is not ready to merge. I am still working on taking care of more edge cases. |
@aliang8 is this ready for review now? |
Fixed
COMMON_DENOMINATORS
to work with non integer denominators.