-
Notifications
You must be signed in to change notification settings - Fork 1
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
Factoring Trinomials and Difference of Squares #41
base: master
Are you sure you want to change the base?
Conversation
lib/rules.js
Outdated
// assume monic polynomial | ||
// TODO: add min and max to evaluate | ||
|
||
export const FACTOR_SYMBOL = defineRuleString('#a^#b_0 + ...', '#a^#eval(min(#b_0, ...)) (#a^#eval(#b_0 - min(#b_0, ...)) + ...)', {b: query.isNumber}) |
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.
Neato! Could you provide an example what this does?
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.
Ahhh, this doesn't exactly work yet. This rule only applies to addition (x^2 + x^3 + x^4 -> x^2(x^0 + x^1 + x^2). Not exactly sure if we want two separate rules for addition and subtraction or if there is a way to do both in a rule string. I also need the min and max functions to be exposed in math-evaluator.
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 this should work with subtraction as is. Have you tried writing a test case with subtraction?
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, I need min and max to support nary parameters.
lib/rules.js
Outdated
(node) => { | ||
const isFactorable = canApplyRule(FACTOR_SUM_PRODUCT_RULE, node) | ||
if (isFactorable) { | ||
const result = applyRule(FACTOR_SUM_PRODUCT_RULE, 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.
Nice!
lib/rules.js
Outdated
@@ -353,6 +353,267 @@ export const GROUP_TERMS_BY_ROOT = defineRule( | |||
// e.g nthRoot(9, 2) -> 3 | |||
export const NTH_ROOT_VALUE = defineRuleString('nthRoot(#a, #b)', '#eval(nthRoot(#a, #b))', {a: query.isNumber, b: query.isNumber}) | |||
|
|||
// FACTOR |
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 put these in their own file? I'd like eventually rename this file to be simple-rules.js
and have it contain only rules which can be full expressed with patterns.
lib/rules.js
Outdated
let isFactorable = false | ||
const {constants, coefficientMap} = getCoefficientsAndConstants(node) | ||
const variables = Object.keys(coefficientMap).map(key => parse(key)) | ||
const coefficients = Object.keys(coefficientMap).map(key => {return coefficientMap[key]}) |
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.
const coeffs = Object.keys(coeffMap).map(key => coeffMap[key][0])
This keeps it to a single line and you don't have to do coeffs[1][0]
later you can just do coeffs[1]
.
lib/rules.js
Outdated
const isTrinomial = | ||
isPolynomialTerm(firstTerm) | ||
&& isPolynomialTerm(secondTerm) | ||
&& (query.isNumber(thirdTerm) || isPolynomialTerm(thirdTerm)) |
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 probably handle things like 1 + 2x + x^2
as well... maybe we should have a transform to order terms in a polynomial in descending order. This can be a TODO.
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 I was thinking to go with the second approach.
lib/rules.js
Outdated
(node) => { | ||
const {constants, coefficientMap} = getCoefficientsAndConstants(node) | ||
const variables = Object.keys(coefficientMap).map(key => parse(key)) | ||
const coefficients = Object.keys(coefficientMap).map(key => {return coefficientMap[key]}) |
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.
return
isn't necessary.
lib/rules.js
Outdated
for (var i in firstFactors){ | ||
for (var j in thirdFactors) { | ||
const l1 = firstFactors[i] | ||
const l2 = thirdFactors[j] |
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 have no idea what's going on here. Can you add a comment explaining this algorithm?
lib/rules.js
Outdated
build.apply( | ||
'mul', | ||
[build.number(factor[0]), ...firstPoly.map( | ||
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.
term -> factor
lib/rules.js
Outdated
} | ||
|
||
const result = build.applyNode | ||
('mul', |
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.
All of the these implicit mul
s can be build using build.implicitMul
.
lib/rules.js
Outdated
} | ||
if (combo == []) { | ||
throw new Error('cannot factor polynomial') | ||
} |
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.
If feels weird that canApplyRule
might be successful, but applyRule
might still fail.
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.
Good point, I have to fix this.
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.
Not entirely sure how to without replicating the code for rewriteFn
.
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 would extract a common function. Obviously it's not ideal to run the same code twice, but I'd rather maintain the invariant that if canApplyRule
returns true
that applyRule
will succeed.
lib/factor-rules.js
Outdated
|
||
const getExponent = (node) => { | ||
if (query.isNumber(node)) { | ||
return 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.
This seems like a weird value to return for a number. I would expect the exponent to be 1
. Maybe there should be an if
statement before calling getExponent
to check if the node is a number.
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.
Oh, the getExponent
function is to get the degree of x for a given polynomial term. So for instance, 6x^2 + 3x + 1
, getExponent
on 1 would return 0 because it is 1x^0
. Maybe the function name itself is a bit misleading?
} else if (query.isPow(node)) { | ||
return query.getValue(node.args[1]) | ||
} else if (query.isMul(node)){ | ||
return getExponent(node.args[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.
Shouldn't this be return Math.max(...node.args.map(getExponent))
?
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's okay to limit ourselves to single variables for now, but we should have a TODO somewhere.
lib/factor-rules.js
Outdated
} | ||
|
||
// e.g. 2 + 3x^2 + 3x - 4x^3 -> -4x^3 + 3x^2 + 3x + 2 | ||
export const REARRANGE_TERMS = defineRule( |
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 doesn't take into account polynomials with multiple variables. Maybe add a TODO.
lib/factor-rules.js
Outdated
|
||
3. Cross multiply the factors and check if | ||
the result matches one of the eight conditions | ||
(the second factor shld also multiply to equal the thirdCoef) |
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.
shld --> should
|
||
Check: 1 * 2 + 6 * 1 ?= 7 so on and so forth | ||
Once we reach 2 * 2 + 3 * 1 ?= 7, we have found a match. | ||
(Note: 3 * 1 = 3 satisfying the second condition) |
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'm confused about this note.... don't we want to check if the multiple the first candidate coefficients results in the first coefficient of the trinomial and multiplying the both of the second candidate numbers results in the constant of the trinomial. In this case 2 * 3 = 6 and 2 * 1 = 2 so it works out.
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.
You already know that 2 * 3 = 6
and 2 * 1 = 2
. It comes from the fact that those are the factors of 6 and 2 respectively (pairs that we got from firstFactors
and thirdFactors
). The note here is just to make sure we don't have an erroneous combo / solution (this happened a few times in my test cases).
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 need to spend some more time with this to really understand what's going on. I have some of pieces but not everything. This is eventually going to get re-reviewed when it gets moved over to mathsteps so I'm okay with merging this.
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.
Awesome, let me know when you merge this, so I can update my other PRs.
lib/factor-rules.js
Outdated
} | ||
) | ||
|
||
const getFactors = (num) => { |
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 should maybe be called getFactorPairs
or add a comment describing what it does. I was confused b/c I thought calling getFactors(12)
would return [1, 2, 3, 4, 6, 12]
but in fact it returns [[1, 12], [2, 6], [3, 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.
Sorry!
lib/factor-rules.js
Outdated
? query.isMul(variables[2]) | ||
? variables[2].args | ||
: [variables[2]] | ||
: null |
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 rewrite this ternary as an if-else?
lib/factor-rules.js
Outdated
query.getValue(coeffs[2]) | ||
: constants[0] | ||
? query.getValue(constants[0]) | ||
: null |
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.
same
lib/factor-rules.js
Outdated
At this point, we find that the correct combination is | ||
: [2,3] and [1,2] -> (2x + 3)(1x + 2) | ||
*/ | ||
const factor_trinomial_helper = (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.
Does this handle cases where the lead coefficient is negative?
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 assume that lead coefficient is positive because it should have been factored out previously by another rule.
lib/factor-rules.js
Outdated
? query.isMul(variables[2]) | ||
? variables[2].args | ||
: [variables[2]] | ||
: null |
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 indentation is a little off here. It should probably be:
const thirdPoly =
variables[2]
? query.isMul(variables[2])
? variables[2].args
: [variables[2]]
: null
even that is hard to parse... since the default is null
maybe do:
let thirdPoly = null;
if (variables[2]) {
if (query.isMul(variables[2]) {
thirdPoly = variables[2].args
} else {
thirdPoly = [variables[2]]
}
}
lib/factor-rules.js
Outdated
? query.isMul(variables[2]) | ||
? variables[2].args | ||
: [variables[2]] | ||
: null |
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.
Please rewrite as if-else
statements.
PERFECT_SQUARE_TRINOMIAL
andFACTOR_TRINOMIAL
logic is somewhat redundant, not sure if we should remove it and just have one rule. Code should be refactored if possible.