Skip to content

Commit 85fde87

Browse files
committed
Merge remote-tracking branch 'socraticorg/mathsteps/master' into 'nitin42/mathsteps/build-configuration' (PR: google#175)
2 parents bfdf4b4 + 8a8f26b commit 85fde87

File tree

90 files changed

+3736
-601
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+3736
-601
lines changed

.eslintrc.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,20 @@ module.exports = {
6666
],
6767
"strict": [
6868
"error"
69+
],
70+
"eol-last": [
71+
"error",
72+
"always"
73+
],
74+
"no-multiple-empty-lines": [
75+
"error",
76+
{
77+
"max": 2,
78+
"maxEOF": 1
79+
}
80+
],
81+
"no-trailing-spaces": [
82+
"error"
6983
]
7084
}
7185
};

CONTRIBUTING.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ We're excited to see your [pull request](https://help.github.com/articles/about-
9393
before you get started, so others are aware that you're working on it. If
9494
there's no existing issue for the change you'd like to make, you can
9595
[create a new issue](https://github.com/socraticorg/mathsteps/issues/new).
96-
96+
9797
- The best issues to work on are [these issues that are not assigned or long term goals](https://github.com/socraticorg/mathsteps/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aopen%20-label%3Aassigned%20%20-label%3Aquestion%20%20-label%3A%22longer%20term%20goals%22%20%20-label%3A%22needs%20further%20discussion%22%20)
9898

9999
- Make sure all the unit tests pass (with `npm test`) before creating the pull
@@ -110,7 +110,7 @@ We're excited to see your [pull request](https://help.github.com/articles/about-
110110
which makes sure tests are passing and the code is eslint-compliant.
111111
- If you want to see what the expression tree looks like at any point in the
112112
code (for debugging), you can log a `node` as an expression string (e.g.
113-
'2x + 5') with `console.log(print(node))`, and you can log the full tree
113+
'2x + 5') with `console.log(print.ascii(node))`, and you can log the full tree
114114
structure with `console.log(JSON.stringify(node, null, 2))`
115115

116116

HISTORY.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,36 @@
11
# History
22

3+
4+
## 2017-10-26, version 0.1.7
5+
6+
There's been a lot of great changes since the last release, here are the main updates
7+
8+
Functionality and Teaching Enhancements:
9+
10+
- new pedagogy for multiply powers integers #153
11+
- exposing the factoring module and adding more coverage #148
12+
- simplify roots of any degree #183
13+
- more cases for cancelling terms #182
14+
- greatest common denominator substep #188
15+
- multiply nthRoots #189
16+
- multiply fractions with parenthesis #185
17+
- remove unnecessary parens before solving equations #205
18+
- multiply denominators with terms #88
19+
- Better sum-product factoring steps #210
20+
21+
22+
Bug Fixes
23+
24+
- fix the check for perfect roots of a constant when there's roundoff error #224
25+
- large negtive number rounding #216
26+
27+
Other:
28+
29+
- (code structure) generalizing polynomial terms #190
30+
- latex printing for equations
31+
- added linting rules #222
32+
33+
334
## 2017-04-03, version 0.1.6
435

536
updated mathjs to incorporate vulnerability patch #149

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,15 @@ To solve an equation:
3333
const steps = mathsteps.solveEquation('2x + 3x = 35');
3434

3535
steps.forEach(step => {
36-
console.log("before change: " + step.oldEquation.print()); // e.g. before change: 2x + 3x = 35
36+
console.log("before change: " + step.oldEquation.ascii()); // e.g. before change: 2x + 3x = 35
3737
console.log("change: " + step.changeType); // e.g. change: SIMPLIFY_LEFT_SIDE
38-
console.log("after change: " + step.newEquation.print()); // e.g. after change: 5x = 35
38+
console.log("after change: " + step.newEquation.ascii()); // e.g. after change: 5x = 35
3939
console.log("# of substeps: " + step.substeps.length); // e.g. # of substeps: 2
4040
});
4141
```
4242

43+
(if you're using mathsteps v0.1.6 or lower, use `.print()` instead of `.ascii()`)
44+
4345
To see all the change types:
4446
```js
4547
const changes = mathsteps.ChangeTypes;

lib/ChangeTypes.js

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ module.exports = {
3535
// e.g. 2 - - 3 -> 2 + 3
3636
RESOLVE_DOUBLE_MINUS: 'RESOLVE_DOUBLE_MINUS',
3737

38-
// COLLECT AND COMBINE
38+
// COLLECT AND COMBINE AND BREAK UP
3939

4040
// e.g. 2 + x + 3 + x -> 5 + 2x
4141
COLLECT_AND_COMBINE_LIKE_TERMS: 'COLLECT_AND_COMBINE_LIKE_TERMS',
@@ -80,6 +80,14 @@ module.exports = {
8080
SIMPLIFY_FRACTION: 'SIMPLIFY_FRACTION',
8181
// e.g. 2/-3 -> -2/3
8282
SIMPLIFY_SIGNS: 'SIMPLIFY_SIGNS',
83+
// e.g. 15/6 -> (5*3)/(2*3)
84+
FIND_GCD: 'FIND_GCD',
85+
// e.g. (5*3)/(2*3) -> 5/2
86+
CANCEL_GCD: 'CANCEL_GCD',
87+
// e.g. 1 2/3 -> 5/3
88+
CONVERT_MIXED_NUMBER_TO_IMPROPER_FRACTION: 'CONVERT_MIXED_NUMBER_TO_IMPROPER_FRACTION',
89+
// e.g. 1 2/3 -> ((1 * 3) + 2) / 3
90+
IMPROPER_FRACTION_NUMERATOR: 'IMPROPER_FRACTION_NUMERATOR',
8391

8492
// ADDING FRACTIONS
8593

@@ -120,6 +128,9 @@ module.exports = {
120128
DISTRIBUTE_NEGATIVE_ONE: 'DISTRIBUTE_NEGATIVE_ONE',
121129
// e.g. 2 * 4x + 2*5 --> 8x + 10 (as part of distribution)
122130
SIMPLIFY_TERMS: 'SIMPLIFY_TERMS',
131+
// e.g. (nthRoot(x, 2))^2 -> nthRoot(x, 2) * nthRoot(x, 2)
132+
// e.g. (2x + 3)^2 -> (2x + 3) (2x + 3)
133+
EXPAND_EXPONENT: 'EXPAND_EXPONENT',
123134

124135
// ABSOLUTE
125136
// e.g. |-3| -> 3
@@ -146,6 +157,10 @@ module.exports = {
146157
GROUP_TERMS_BY_ROOT: 'GROUP_TERMS_BY_ROOT',
147158
// e.g. nthRoot(4) -> 2
148159
NTH_ROOT_VALUE: 'NTH_ROOT_VALUE',
160+
// e.g. nthRoot(4) + nthRoot(4) = 2*nthRoot(4)
161+
ADD_NTH_ROOTS: 'ADD_NTH_ROOTS',
162+
// e.g. nthRoot(x, 2) * nthRoot(x, 2) -> nthRoot(x^2, 2)
163+
MULTIPLY_NTH_ROOTS: 'MULTIPLY_NTH_ROOTS',
149164

150165
// SOLVING FOR A VARIABLE
151166

@@ -167,6 +182,8 @@ module.exports = {
167182
SUBTRACT_FROM_BOTH_SIDES: 'SUBTRACT_FROM_BOTH_SIDES',
168183
// e.g. 2 = x -> x = 2
169184
SWAP_SIDES: 'SWAP_SIDES',
185+
// e.g. (x - 2) (x + 2) = 0 => x = [-2, 2]
186+
FIND_ROOTS: 'FIND_ROOTS',
170187

171188
// CONSTANT EQUATION
172189

@@ -185,4 +202,6 @@ module.exports = {
185202
FACTOR_PERFECT_SQUARE: 'FACTOR_PERFECT_SQUARE',
186203
// e.g. x^2 + 3x + 2 -> (x + 1)(x + 2)
187204
FACTOR_SUM_PRODUCT_RULE: 'FACTOR_SUM_PRODUCT_RULE',
205+
// e.g. 2x^2 + 4x + 2 -> 2x^2 + 2x + 2x + 2
206+
BREAK_UP_TERM: 'BREAK_UP_TERM',
188207
};

lib/Negative.js

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,28 @@
1-
const Node = require('./node');
1+
const NodeCreator = require('./node/Creator');
2+
const NodeType = require('./node/Type');
3+
const PolynomialTerm = require('./node/PolynomialTerm');
24

35
const Negative = {};
46

57
// Returns if the given node is negative. Treats a unary minus as a negative,
68
// as well as a negative constant value or a constant fraction that would
79
// evaluate to a negative number
810
Negative.isNegative = function(node) {
9-
if (Node.Type.isUnaryMinus(node)) {
11+
if (NodeType.isUnaryMinus(node)) {
1012
return !Negative.isNegative(node.args[0]);
1113
}
12-
else if (Node.Type.isConstant(node)) {
14+
else if (NodeType.isConstant(node)) {
1315
return parseFloat(node.value) < 0;
1416
}
15-
else if (Node.Type.isConstantFraction(node)) {
17+
else if (NodeType.isConstantFraction(node)) {
1618
const numeratorValue = parseFloat(node.args[0].value);
1719
const denominatorValue = parseFloat(node.args[1].value);
1820
if (numeratorValue < 0 || denominatorValue < 0) {
1921
return !(numeratorValue < 0 && denominatorValue < 0);
2022
}
2123
}
22-
else if (Node.PolynomialTerm.isPolynomialTerm(node)) {
23-
const polyNode = new Node.PolynomialTerm(node);
24+
else if (PolynomialTerm.isPolynomialTerm(node)) {
25+
const polyNode = new PolynomialTerm(node);
2426
return Negative.isNegative(polyNode.getCoeffNode(true));
2527
}
2628

@@ -34,22 +36,22 @@ Negative.isNegative = function(node) {
3436
// not naive: -3 -> 3, x -> -x
3537
// naive: -3 -> --3, x -> -x
3638
Negative.negate = function(node, naive=false) {
37-
if (Node.Type.isConstantFraction(node)) {
39+
if (NodeType.isConstantFraction(node)) {
3840
node.args[0] = Negative.negate(node.args[0], naive);
3941
return node;
4042
}
41-
else if (Node.PolynomialTerm.isPolynomialTerm(node)) {
43+
else if (PolynomialTerm.isPolynomialTerm(node)) {
4244
return Negative.negatePolynomialTerm(node, naive);
4345
}
4446
else if (!naive) {
45-
if (Node.Type.isUnaryMinus(node)) {
47+
if (NodeType.isUnaryMinus(node)) {
4648
return node.args[0];
4749
}
48-
else if (Node.Type.isConstant(node)) {
49-
return Node.Creator.constant(0 - parseFloat(node.value));
50+
else if (NodeType.isConstant(node)) {
51+
return NodeCreator.constant(0 - parseFloat(node.value));
5052
}
5153
}
52-
return Node.Creator.unaryMinus(node);
54+
return NodeCreator.unaryMinus(node);
5355
};
5456

5557
// Multiplies a polynomial term by -1 and returns the new node
@@ -59,14 +61,14 @@ Negative.negate = function(node, naive=false) {
5961
// not naive: -3x -> 3x, x -> -x
6062
// naive: -3x -> --3x, x -> -x
6163
Negative.negatePolynomialTerm = function(node, naive=false) {
62-
if (!Node.PolynomialTerm.isPolynomialTerm(node)) {
64+
if (!PolynomialTerm.isPolynomialTerm(node)) {
6365
throw Error('node is not a polynomial term');
6466
}
65-
const polyNode = new Node.PolynomialTerm(node);
67+
const polyNode = new PolynomialTerm(node);
6668

6769
let newCoeff;
6870
if (!polyNode.hasCoeff()) {
69-
newCoeff = Node.Creator.constant(-1);
71+
newCoeff = NodeCreator.constant(-1);
7072
}
7173
else {
7274
const oldCoeff = polyNode.getCoeffNode();
@@ -78,7 +80,7 @@ Negative.negatePolynomialTerm = function(node, naive=false) {
7880
numerator = Negative.negate(numerator, naive);
7981

8082
const denominator = oldCoeff.args[1];
81-
newCoeff = Node.Creator.operator('/', [numerator, denominator]);
83+
newCoeff = NodeCreator.operator('/', [numerator, denominator]);
8284
}
8385
else {
8486
newCoeff = Negative.negate(oldCoeff, naive);
@@ -87,7 +89,7 @@ Negative.negatePolynomialTerm = function(node, naive=false) {
8789
}
8890
}
8991
}
90-
return Node.Creator.polynomialTerm(
92+
return NodeCreator.polynomialTerm(
9193
polyNode.getSymbolNode(), polyNode.getExponentNode(), newCoeff);
9294
};
9395

lib/Symbols.js

Lines changed: 60 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,23 @@ Symbols.getSymbolsInExpression = function(expression) {
1818
return symbolSet;
1919
};
2020

21-
// Iterates through a node and returns the polynomial term with the symbol name
21+
// Iterates through a node and returns the last term with the symbol name
2222
// Returns null if no terms with the symbol name are in the node.
2323
// e.g. 4x^2 + 2x + y + 2 with `symbolName=x` would return 2x
2424
Symbols.getLastSymbolTerm = function(node, symbolName) {
2525
// First check if the node itself is a polyomial term with symbolName
2626
if (isSymbolTerm(node, symbolName)) {
2727
return node;
2828
}
29-
// Otherwise, it's a sum of terms. Look through the operands for a term
29+
// If it's a sum of terms, look through the operands for a term
3030
// with `symbolName`
3131
else if (Node.Type.isOperator(node, '+')) {
3232
for (let i = node.args.length - 1; i >= 0 ; i--) {
3333
const child = node.args[i];
34-
if (isSymbolTerm(child, symbolName)) {
34+
if (Node.Type.isOperator(child, '+')) {
35+
return Symbols.getLastSymbolTerm(child, symbolName);
36+
}
37+
else if (isSymbolTerm(child, symbolName)) {
3538
return child;
3639
}
3740
}
@@ -46,13 +49,19 @@ Symbols.getLastSymbolTerm = function(node, symbolName) {
4649
// e.g. 4x^2 + 2x + 2/4 with `symbolName=x` would return 2/4
4750
// e.g. 4x^2 + 2x + y with `symbolName=x` would return y
4851
Symbols.getLastNonSymbolTerm = function(node, symbolName) {
49-
if (isSymbolTerm(node, symbolName)) {
52+
if (isPolynomialTermWithSymbol(node, symbolName)) {
5053
return new Node.PolynomialTerm(node).getCoeffNode();
5154
}
55+
else if (hasDenominatorSymbol(node, symbolName)) {
56+
return null;
57+
}
5258
else if (Node.Type.isOperator(node)) {
5359
for (let i = node.args.length - 1; i >= 0 ; i--) {
5460
const child = node.args[i];
55-
if (!isSymbolTerm(child, symbolName)) {
61+
if (Node.Type.isOperator(child, '+')) {
62+
return Symbols.getLastNonSymbolTerm(child, symbolName);
63+
}
64+
else if (!isSymbolTerm(child, symbolName)) {
5665
return child;
5766
}
5867
}
@@ -61,14 +70,59 @@ Symbols.getLastNonSymbolTerm = function(node, symbolName) {
6170
return null;
6271
};
6372

64-
// Returns if `node` is a polynomial term with symbol `symbolName`
73+
// Iterates through a node and returns the denominator if it has a
74+
// symbolName in its denominator
75+
// e.g. 1/(2x) with `symbolName=x` would return 2x
76+
// e.g. 1/(x+2) with `symbolName=x` would return x+2
77+
// e.g. 1/(x+2) + (x+1)/(2x+3) with `symbolName=x` would return (2x+3)
78+
Symbols.getLastDenominatorWithSymbolTerm = function(node, symbolName) {
79+
// First check if the node itself has a symbol in the denominator
80+
if (hasDenominatorSymbol(node, symbolName)) {
81+
return node.args[1];
82+
}
83+
// Otherwise, it's a sum of terms. e.g. 1/x + 1(2+x)
84+
// Look through the operands for a
85+
// denominator term with `symbolName`
86+
else if (Node.Type.isOperator(node, '+')) {
87+
for (let i = node.args.length - 1; i >= 0 ; i--) {
88+
const child = node.args[i];
89+
if (Node.Type.isOperator(child, '+')) {
90+
return Symbols.getLastDenominatorWithSymbolTerm(child, symbolName);
91+
}
92+
else if (hasDenominatorSymbol(child, symbolName)) {
93+
return child.args[1];
94+
}
95+
}
96+
}
97+
return null;
98+
};
99+
100+
// Returns if `node` is a term with symbol `symbolName`
65101
function isSymbolTerm(node, symbolName) {
102+
return isPolynomialTermWithSymbol(node, symbolName) ||
103+
hasDenominatorSymbol(node, symbolName);
104+
}
105+
106+
function isPolynomialTermWithSymbol(node, symbolName) {
66107
if (Node.PolynomialTerm.isPolynomialTerm(node)) {
67108
const polyTerm = new Node.PolynomialTerm(node);
68109
if (polyTerm.getSymbolName() === symbolName) {
69110
return true;
70111
}
71112
}
113+
114+
return false;
115+
}
116+
117+
// Return if `node` has a symbol in its denominator
118+
// e.g. true for 1/(2x)
119+
// e.g. false for 5x
120+
function hasDenominatorSymbol(node, symbolName) {
121+
if (Node.Type.isOperator(node) && node.op === '/') {
122+
const allSymbols = Symbols.getSymbolsInExpression(node.args[1]);
123+
return allSymbols.has(symbolName);
124+
}
125+
72126
return false;
73127
}
74128

lib/TreeSearch.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,4 @@ function search(simplificationFunction, node, preOrder) {
6666
}
6767

6868

69-
7069
module.exports = TreeSearch;

0 commit comments

Comments
 (0)