-
Notifications
You must be signed in to change notification settings - Fork 279
Divide like terms powers #154
base: master
Are you sure you want to change the base?
Changes from all commits
91f21f3
14cf759
3567101
5c1f306
3e9b8a3
008ca26
43f9c49
c20657a
48ff961
9c86ab8
34df2cd
b9b10df
1ae2158
6118f79
8016ed9
a98c170
a9d2a9b
1af42a2
94c5510
b745688
2768d3c
154a2e6
0496a72
03d62a3
922f77e
9781053
00ca609
fbe40a0
56ca446
57a2a9a
938714a
81ba0eb
2e2877b
cb0bcb1
fa35784
7c1758d
141d017
b10ae0f
26edd37
0bf966f
8337df8
9ab482c
d955b34
4dc5206
6b86cc4
22f2dc4
5eec11a
d47dd58
3ba56c3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -42,8 +42,10 @@ module.exports = { | |
// e.g. x + 2 + x^2 + x + 4 -> x^2 + (x + x) + (4 + 2) | ||
COLLECT_LIKE_TERMS: 'COLLECT_LIKE_TERMS', | ||
|
||
// MULTIPLYING CONSTANT POWERS | ||
// e.g. 10^2 * 10^3 -> 10^(2+3) | ||
// MULTIPLYING/DIVIDING CONSTANT POWERS | ||
|
||
// e.g. 10^2 * 10^3 -> 10^(3+2) | ||
// e.g. 10^4 / 10^2 -> 10^(4-2) | ||
COLLECT_CONSTANT_EXPONENTS: 'COLLECT_CONSTANT_EXPONENTS', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you have two changeGroups?
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Surely! Will be more descriptive |
||
|
||
// ADDING POLYNOMIALS | ||
|
@@ -67,6 +69,8 @@ module.exports = { | |
MULTIPLY_COEFFICIENTS: 'MULTIPLY_COEFFICIENTS', | ||
// e.g. 2x * x -> 2x ^ 2 | ||
MULTIPLY_POLYNOMIAL_TERMS: 'MULTIPLY_POLYNOMIAL_TERMS', | ||
// e.g x^5 / x^3 -> x^2 | ||
DIVIDE_POLYNOMIAL_TERMS: 'DIVIDE_POLYNOMIAL_TERMS', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oo nice does this change not happen already? I feel like it's already handled in the cancelling code - see if that already happens (but maybe this change group is more descriptive than cancelling?) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is handled by cancelling but I think it is clearer with this change group. Guess it depends on the exercise There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. there are more cases in cancelling like 3x^2 / x that aren't cancelled here, so maybe we should leave it all handled there. also means less extra code doing the same thing which is good |
||
|
||
// FRACTIONS | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
const ConstantOrPowerTerm = require('../simplifyExpression/collectAndCombineSearch/ConstantOrConstantPower'); | ||
const Node = require('../node'); | ||
|
||
// Returns true if node is a division of constant power nodes | ||
// where you can combine their exponents, e.g. 10^4 / 10^2 can become 10^2 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you use a different example where all the exponents are different? |
||
// The node can be on the form c^n or c, as long is c is the same for all | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. be of the form |
||
function canDivideLikeTermConstantNodes(node) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if this function is the exact same as multiply but with just the node.op being different (which I think it is?) you should share that code and pass in the op to the function - much cleaner :D |
||
if (!Node.Type.isOperator(node) || node.op !== '/') { | ||
return false; | ||
} | ||
const args = node.args; | ||
if (!args.every(n => ConstantOrPowerTerm.isConstantOrConstantPower(n))) { | ||
return false; | ||
} | ||
|
||
const constantTermBaseList = args.map(n => ConstantOrPowerTerm.getBaseNode(n)); | ||
const firstTerm = constantTermBaseList[0]; | ||
const restTerms = constantTermBaseList.slice(1); | ||
// they're considered like terms if they have the same base value | ||
return restTerms.every(term => firstTerm.value === term.value); | ||
} | ||
|
||
module.exports = canDivideLikeTermConstantNodes; | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you have an extra new line here - it should always be exactly one (maybe we can add that to the linter ooo) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
const Node = require('../node'); | ||
|
||
// Returns true if the nodes are symbolic terms with the same symbol and no | ||
// coefficients. | ||
function canDivideLikeTermPolynomialNodes(node) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (same thing about code reuse) |
||
if (!Node.Type.isOperator(node) || node.op !== '/') { | ||
return false; | ||
} | ||
const args = node.args; | ||
if (!args.every(n => Node.PolynomialTerm.isPolynomialTerm(n))) { | ||
return false; | ||
} | ||
if (args.length === 1) { | ||
return false; | ||
} | ||
|
||
const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); | ||
if (!polynomialTermList.every(polyTerm => !polyTerm.hasCoeff())) { | ||
return false; | ||
} | ||
|
||
const firstTerm = polynomialTermList[0]; | ||
const restTerms = polynomialTermList.slice(1); | ||
// they're considered like terms if they have the same symbol name | ||
return restTerms.every(term => firstTerm.getSymbolName() === term.getSymbolName()); | ||
} | ||
|
||
module.exports = canDivideLikeTermPolynomialNodes; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
const arithmeticSearch = require('../arithmeticSearch'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how much of this code is copied from multiplying and how much is new? ideally very little code should be copied There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pretty much everything everything. It should do the same thing as multiply but with another operator. Should I just pass the operator "-" to multiplyLikeTerms? Will propably be cleaner There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. send '/' not '-' but yes, that's what I'm thinking. You'll want to rename the file though to be more descriptive of its new purpose |
||
const checks = require('../../checks'); | ||
const clone = require('../../util/clone'); | ||
const ConstantOrConstantPower = require('./ConstantOrConstantPower'); | ||
|
||
const ChangeTypes = require('../../ChangeTypes'); | ||
const Node = require('../../node'); | ||
|
||
// Divides a list of nodes that are polynomial like terms or constants with same base. | ||
// Returns a node. | ||
// The nodes should *not* have coefficients. | ||
function divideLikeTerms(node, polynomialOnly = false) { | ||
if (!Node.Type.isOperator(node)) { | ||
return Node.Status.noChange(node); | ||
} | ||
let status; | ||
|
||
if (!polynomialOnly && !checks.canDivideLikeTermConstantNodes(node)) { | ||
status = arithmeticSearch(node); | ||
if (status.hasChanged()) { | ||
status.changeType = ChangeTypes.SIMPLIFY_FRACTION; | ||
return status; | ||
} | ||
} | ||
|
||
status = dividePolynomialTerms(node); | ||
if (status.hasChanged()) { | ||
status.changeType = ChangeTypes.SIMPLIFY_FRACTION; | ||
return status; | ||
} | ||
|
||
return Node.Status.noChange(node); | ||
} | ||
|
||
function dividePolynomialTerms(node) { | ||
if (!checks.canDivideLikeTermPolynomialNodes(node) && | ||
!checks.canDivideLikeTermConstantNodes(node)) { | ||
return Node.Status.noChange(node); | ||
} | ||
|
||
const substeps = []; | ||
let newNode = clone(node); | ||
|
||
// STEP 1: If any term has no exponent, make it have exponent 1 | ||
// e.g. x -> x^1 (this is for pedagogy reasons) | ||
// (this step only happens under certain conditions and later steps might | ||
// happen even if step 1 does not) | ||
let status = addOneExponent(newNode); | ||
if (status.hasChanged()) { | ||
substeps.push(status); | ||
newNode = Node.Status.resetChangeGroups(status.newNode); | ||
} | ||
|
||
// STEP 2: collect exponents to a single exponent difference | ||
// e.g. x^1 / x^3 -> x^(1 + -3) | ||
if (checks.canDivideLikeTermConstantNodes(node)) { | ||
status = collectConstantExponents(newNode); | ||
} | ||
else { | ||
status = collectPolynomialExponents(newNode); | ||
} | ||
substeps.push(status); | ||
newNode = Node.Status.resetChangeGroups(status.newNode); | ||
|
||
// STEP 3: calculate difference of exponents. | ||
// NOTE: This might not be a step if the exponents aren't all constants, | ||
// but this case isn't that common and can be caught in other steps. | ||
// e.g. x^(2-4-z) | ||
// TODO: handle fractions, combining and collecting like terms, etc, here | ||
const exponentDiff = newNode.args[1].content; | ||
const diffStatus = arithmeticSearch(exponentDiff); | ||
if (diffStatus.hasChanged()) { | ||
status = Node.Status.childChanged(newNode, diffStatus, 1); | ||
substeps.push(status); | ||
newNode = Node.Status.resetChangeGroups(status.newNode); | ||
} | ||
|
||
if (substeps.length === 1) { // possible if only step 2 happens | ||
return substeps[0]; | ||
} | ||
else { | ||
return Node.Status.nodeChanged( | ||
ChangeTypes.DIVIDE_POLYNOMIAL_TERMS, | ||
node, newNode, true, substeps); | ||
} | ||
} | ||
|
||
// Given a product of polynomial terms, changes any term with no exponent | ||
// into a term with an explicit exponent of 1. This is for pedagogy, and | ||
// makes the adding coefficients step clearer. | ||
// e.g. x^2 / x -> x^2 / x^1 | ||
// Returns a Node.Status object. | ||
function addOneExponent(node) { | ||
const newNode = clone(node); | ||
let change = false; | ||
|
||
let changeGroup = 1; | ||
if (checks.canDivideLikeTermConstantNodes(node)) { | ||
newNode.args.forEach((child, i) => { | ||
if (Node.Type.isConstant(child)) { | ||
const base = ConstantOrConstantPower.getBaseNode(child); | ||
const exponent = Node.Creator.constant(1); | ||
newNode.args[i] = Node.Creator.operator('^', [base, exponent]); | ||
|
||
newNode.args[i].changeGroup = changeGroup; | ||
node.args[i].changeGroup = changeGroup; // note that this is the "oldNode" | ||
|
||
change = true; | ||
changeGroup++; | ||
} | ||
}); | ||
} | ||
else { | ||
newNode.args.forEach((child, i) => { | ||
const polyTerm = new Node.PolynomialTerm(child); | ||
if (!polyTerm.getExponentNode()) { | ||
newNode.args[i] = Node.Creator.polynomialTerm( | ||
polyTerm.getSymbolNode(), | ||
Node.Creator.constant(1), | ||
polyTerm.getCoeffNode()); | ||
|
||
newNode.args[i].changeGroup = changeGroup; | ||
node.args[i].changeGroup = changeGroup; // note that this is the "oldNode" | ||
|
||
change = true; | ||
changeGroup++; | ||
} | ||
}); | ||
} | ||
|
||
if (change) { | ||
return Node.Status.nodeChanged( | ||
ChangeTypes.ADD_EXPONENT_OF_ONE, node, newNode, false); | ||
} | ||
else { | ||
return Node.Status.noChange(node); | ||
} | ||
} | ||
|
||
// Given a division of constant terms, groups the exponents into a difference | ||
// e.g. 10^5 / 10^3 -> 10^(5 - 3) | ||
// Returns a Node.Status object. | ||
function collectConstantExponents(node) { | ||
|
||
// If we're dividing constant nodes together, they all share the same | ||
// base. Get that from the first node. | ||
const baseNode = ConstantOrConstantPower.getBaseNode(node.args[0]); | ||
|
||
// The new exponent will be a difference of exponents (an operation, wrapped in | ||
// parens) e.g. 10^(5-3) | ||
const exponentNodeList = node.args.map(ConstantOrConstantPower.getExponentNode); | ||
const newExponent = Node.Creator.parenthesis( | ||
Node.Creator.operator('-', exponentNodeList)); | ||
const newNode = Node.Creator.operator('^', [baseNode, newExponent]); | ||
return Node.Status.nodeChanged( | ||
ChangeTypes.COLLECT_CONSTANT_EXPONENTS, node, newNode); | ||
} | ||
// Given a division of polynomial terms, groups the exponents into a difference | ||
// e.g. x^5 / x^3 -> x^(5 - 3) | ||
// Returns a Node.Status object. | ||
function collectPolynomialExponents(node) { | ||
const polynomialTermList = node.args.map(n => new Node.PolynomialTerm(n)); | ||
|
||
// If we're dividing polynomial nodes together, they all share the same | ||
// symbol. Get that from the first node. | ||
const symbolNode = polynomialTermList[0].getSymbolNode(); | ||
|
||
// The new exponent will be a difference of exponents (an operation, wrapped in | ||
// parens) e.g. x^(5-3) | ||
const exponentNodeList = polynomialTermList.map(p => p.getExponentNode(true)); | ||
const newExponent = Node.Creator.parenthesis( | ||
Node.Creator.operator('-', exponentNodeList)); | ||
const newNode = Node.Creator.polynomialTerm(symbolNode, newExponent, null); | ||
return Node.Status.nodeChanged( | ||
ChangeTypes.COLLECT_POLYNOMIAL_EXPONENTS, node, newNode); | ||
} | ||
|
||
module.exports = divideLikeTerms; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
const canDivideLikeTermConstantNodes = require('../lib/checks/canDivideLikeTermConstantNodes'); | ||
|
||
|
||
const TestUtil = require('./TestUtil'); | ||
|
||
function testCanBeDividedConstants(expr, multipliable) { | ||
TestUtil.testBooleanFunction(canDivideLikeTermConstantNodes, expr, multipliable); | ||
} | ||
|
||
describe('can divide like term constants', () => { | ||
const tests = [ | ||
['3^2 / 3^5', true], | ||
['2^3 / 3^2', false], | ||
['10^3 / 10^2', true], | ||
['10^6 / 10^4', true] | ||
]; | ||
tests.forEach(t => testCanBeDividedConstants(t[0], t[1])); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
const canDivideLikeTermPolynomialNodes = require('../lib/checks/canDivideLikeTermPolynomialNodes'); | ||
|
||
const TestUtil = require('./TestUtil'); | ||
|
||
function testCanBeDivided(expr, multipliable) { | ||
TestUtil.testBooleanFunction(canDivideLikeTermPolynomialNodes, expr, multipliable); | ||
} | ||
|
||
describe('can divide like term polynomials', () => { | ||
const tests = [ | ||
['x^2 / x', true], | ||
['3x^2 / x ', false], | ||
['y^3 / y^2', true], | ||
['x^8 / x^5', true] | ||
]; | ||
tests.forEach(t => testCanBeDivided(t[0], t[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.
no new line here