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

Divide like terms powers #154

Open
wants to merge 49 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
91f21f3
Added support for multiplication of powers with integer as base, with…
Apr 13, 2017
14cf759
Added support for multiplication of powers with integer as base, with…
Apr 13, 2017
3567101
Merge remote-tracking branch 'remotes/origin/master' into multiply_po…
Apr 13, 2017
5c1f306
Delete test-mathsteps.js
Apr 13, 2017
3e9b8a3
Fixed errors from the CI
Apr 13, 2017
008ca26
Update index.js
Apr 13, 2017
43f9c49
Update index.js
Apr 13, 2017
c20657a
Update multiplyLikeTerms.js
Apr 13, 2017
48ff961
Update TreeSearch.js
Apr 13, 2017
9c86ab8
Update canMultiplyLikeTermConstantNodes.test.js
Apr 13, 2017
34df2cd
Added support for division with powers too, for better pedagogical ap…
Apr 13, 2017
b9b10df
Delete test-mathsteps.js
Apr 13, 2017
1ae2158
Fixed errors and added semicolon
Apr 13, 2017
6118f79
Merge remote-tracking branch 'origin/divide_like_terms_powers' into d…
Apr 13, 2017
8016ed9
Fixed errors and added semicolon
Apr 13, 2017
a98c170
Fixed some stuff according to the comments from the pull request.
Apr 18, 2017
a9d2a9b
Added new ChangeTypes for collecting exponents
Apr 18, 2017
1af42a2
Merged
Apr 18, 2017
94c5510
Fixed some things and added new ChangeTypes.
Apr 18, 2017
b745688
Better comment for the new ChangeType
Apr 18, 2017
2768d3c
Deleted the ConstantTerms class and added functions inside "CanMultip…
Apr 20, 2017
154a2e6
Merged some changes from multiply power branch
Apr 21, 2017
0496a72
Changed so that it doesn't create power node but returns exponent and…
Apr 24, 2017
03d62a3
Merge branch 'multiply_powers_integers' into divide_like_terms_powers…
Apr 26, 2017
922f77e
Changed to work the same as in other branch
Apr 26, 2017
9781053
Changed structure when dividing like terms. Pretty much does the same…
Apr 26, 2017
00ca609
Added more tests
Apr 26, 2017
fbe40a0
Changed stuff according to the comments. Also added module with helpe…
Apr 27, 2017
56ca446
Merge branch 'multiply_powers_integers' into divide_like_terms_powers
Apr 27, 2017
57a2a9a
Updated to match structure of multiplication of powers
Apr 27, 2017
938714a
Easy changes according to review
Apr 27, 2017
81ba0eb
Fixed formatting and deleted powerTerm.
Apr 28, 2017
2e2877b
deleted .idea files and added .idea to gitignore
Apr 28, 2017
cb0bcb1
Fixed comments
Apr 28, 2017
fa35784
Fixed comments
May 2, 2017
7c1758d
Delete .npmignore
May 2, 2017
141d017
Merge branch 'multiply_powers_integers' into divide_like_terms_powers
May 2, 2017
b10ae0f
Delete .gitignore
May 2, 2017
26edd37
Merge branch 'multiply_powers_integers' into divide_like_terms_powers
May 2, 2017
0bf966f
Merge remote-tracking branch 'origin/divide_like_terms_powers' into d…
May 2, 2017
8337df8
Added gitignore again.
May 2, 2017
9ab482c
Deleted .idea
May 2, 2017
d955b34
Added/removed lines for clearer code.
May 2, 2017
4dc5206
Update canMultiplyLikeTermPolynomialNodes.js
May 2, 2017
6b86cc4
Removed test
May 3, 2017
22f2dc4
Fixed lint
May 3, 2017
5eec11a
Merge branch 'multiply_powers_integers' into divide_like_terms_powers
May 3, 2017
d47dd58
Removed some unnecessary tests
May 8, 2017
3ba56c3
Fixed conflicts
May 12, 2017
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions lib/ChangeTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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

Copy link
Contributor

Choose a reason for hiding this comment

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

no new line here

// e.g. 10^2 * 10^3 -> 10^(3+2)
// e.g. 10^4 / 10^2 -> 10^(4-2)
COLLECT_CONSTANT_EXPONENTS: 'COLLECT_CONSTANT_EXPONENTS',
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 have two changeGroups?

COLLECT_CONSTANT_EXPONENTS_MULT and COLLECT_CONSTANT_EXPONENTS_DIV for example

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Surely! Will be more descriptive


// ADDING POLYNOMIALS
Expand All @@ -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',
Copy link
Contributor

Choose a reason for hiding this comment

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

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 handled by cancelling but I think it is clearer with this change group. Guess it depends on the exercise

Copy link
Contributor

Choose a reason for hiding this comment

The 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

Expand Down
24 changes: 24 additions & 0 deletions lib/checks/canDivideLikeTermConstantNodes.js
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
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 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
Copy link
Contributor

Choose a reason for hiding this comment

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

be of the form

function canDivideLikeTermConstantNodes(node) {
Copy link
Contributor

Choose a reason for hiding this comment

The 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;

Copy link
Contributor

Choose a reason for hiding this comment

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

28 changes: 28 additions & 0 deletions lib/checks/canDivideLikeTermPolynomialNodes.js
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) {
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 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;
4 changes: 4 additions & 0 deletions lib/checks/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
const canAddLikeTermPolynomialNodes = require('./canAddLikeTermPolynomialNodes');
const canDivideLikeTermConstantNodes = require('./canDivideLikeTermConstantNodes');
const canDivideLikeTermPolynomialNodes = require('./canDivideLikeTermPolynomialNodes');
const canMultiplyLikeTermConstantNodes = require('./canMultiplyLikeTermConstantNodes');
const canMultiplyLikeTermPolynomialNodes = require('./canMultiplyLikeTermPolynomialNodes');
const canRearrangeCoefficient = require('./canRearrangeCoefficient');
Expand All @@ -9,6 +11,8 @@ const resolvesToConstant = require('./resolvesToConstant');

module.exports = {
canAddLikeTermPolynomialNodes,
canDivideLikeTermConstantNodes,
canDivideLikeTermPolynomialNodes,
canMultiplyLikeTermConstantNodes,
canMultiplyLikeTermPolynomialNodes,
canRearrangeCoefficient,
Expand Down
178 changes: 178 additions & 0 deletions lib/simplifyExpression/collectAndCombineSearch/divideLikeTerms.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
const arithmeticSearch = require('../arithmeticSearch');
Copy link
Contributor

Choose a reason for hiding this comment

The 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

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Copy link
Contributor

Choose a reason for hiding this comment

The 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;
8 changes: 8 additions & 0 deletions lib/simplifyExpression/collectAndCombineSearch/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
const addLikeTerms = require('./addLikeTerms');
const checks = require('../../checks');
const clone = require('../../util/clone');
const divideLikeTerms = require('./divideLikeTerms');
const multiplyLikeTerms = require('./multiplyLikeTerms');

const ChangeTypes = require('../../ChangeTypes');
Expand All @@ -12,6 +13,7 @@ const TreeSearch = require('../../TreeSearch');

const termCollectorFunctions = {
'+': addLikeTerms,
'/': divideLikeTerms,
'*': multiplyLikeTerms
};

Expand Down Expand Up @@ -48,6 +50,12 @@ function collectAndCombineLikeTerms(node) {
// e.g. x * x^2 * x => ... => x^4
return multiplyLikeTerms(node, true);
}
else if (node.op === '/') {
// we might also be able to just combine like terms
// e.g 10^6 / 10^3 => ... => 10^3
// e.g x^5 / x^2 => ... => x^3
return divideLikeTerms(node, true);
}
else {
return Node.Status.noChange(node);
}
Expand Down
18 changes: 18 additions & 0 deletions test/canDivideLikeTermConstantNodes.test.js
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]));
});
17 changes: 17 additions & 0 deletions test/canDivideLikeTermPolynomialNodes.test.js
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]));
});
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,22 @@ describe('combinePolynomialTerms multiplication', function() {
tests.forEach(t => testCollectAndCombineSubsteps(t[0], t[1], t[2]));
});

describe('combinePolynomialPowerTerms division', function() {
const tests = [
['x^2 / x',
['x^2 / (x^1)',
'x^(2 - 1)',
'x^1'],
],
['y / y^3',
['y^1 / (y^3)',
'y^(1 - 3)',
'y^-2'],
],
];
tests.forEach(t => testCollectAndCombineSubsteps(t[0], t[1], t[2]));
});

describe('combinePolynomialTerms addition', function() {
const tests = [
['x+x',
Expand Down Expand Up @@ -88,3 +104,15 @@ describe('collect and multiply like terms', function() {
];
tests.forEach(t => testSimpleCollectAndCombineSearch(t[0], t[1]));
});

describe('collect and divide multiply like terms', function() {
const tests = [
['10^5 / 10^2', '10^3'],
['2^4 / 2^2', '2^2'],
['2^3 / 2^4', '2^-1'],
['x^3 / x^4', 'x^-1'],
['y^5 / y^2', 'y^3'],
['z^4 / z^2', 'z^2'],
];
tests.forEach(t => testSimpleCollectAndCombineSearch(t[0], t[1]));
});