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

Factoring Trinomials and Difference of Squares #41

Open
wants to merge 90 commits into
base: master
Choose a base branch
from

Conversation

aliang8
Copy link
Contributor

@aliang8 aliang8 commented Jun 2, 2017

PERFECT_SQUARE_TRINOMIAL and FACTOR_TRINOMIAL logic is somewhat redundant, not sure if we should remove it and just have one rule. Code should be refactored if possible.

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})
Copy link
Member

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?

Copy link
Contributor Author

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.

Copy link
Member

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?

Copy link
Contributor Author

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)
Copy link
Member

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
Copy link
Member

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]})
Copy link
Member

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))
Copy link
Member

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.

Copy link
Contributor Author

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]})
Copy link
Member

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]
Copy link
Member

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 =>
Copy link
Member

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',
Copy link
Member

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 muls can be build using build.implicitMul.

lib/rules.js Outdated
}
if (combo == []) {
throw new Error('cannot factor polynomial')
}
Copy link
Member

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.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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.

Copy link
Member

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.


const getExponent = (node) => {
if (query.isNumber(node)) {
return 0
Copy link
Member

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.

Copy link
Contributor Author

@aliang8 aliang8 Jun 19, 2017

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])
Copy link
Member

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))?

Copy link
Member

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.

}

// e.g. 2 + 3x^2 + 3x - 4x^3 -> -4x^3 + 3x^2 + 3x + 2
export const REARRANGE_TERMS = defineRule(
Copy link
Member

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.


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)
Copy link
Member

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)
Copy link
Member

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.

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 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).

Copy link
Member

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.

Copy link
Contributor Author

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.

}
)

const getFactors = (num) => {
Copy link
Member

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]].

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry!

? query.isMul(variables[2])
? variables[2].args
: [variables[2]]
: null
Copy link
Member

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?

query.getValue(coeffs[2])
: constants[0]
? query.getValue(constants[0])
: null
Copy link
Member

Choose a reason for hiding this comment

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

same

At this point, we find that the correct combination is
: [2,3] and [1,2] -> (2x + 3)(1x + 2)
*/
const factor_trinomial_helper = (node) => {
Copy link
Member

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?

Copy link
Contributor Author

@aliang8 aliang8 Jun 19, 2017

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.

? query.isMul(variables[2])
? variables[2].args
: [variables[2]]
: null
Copy link
Member

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]]
   }
}

? query.isMul(variables[2])
? variables[2].args
: [variables[2]]
: null
Copy link
Member

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.

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.

2 participants