-
Notifications
You must be signed in to change notification settings - Fork 279
WIP: Substitute symbols for expressions from optional scope #212
base: master
Are you sure you want to change the base?
Changes from 11 commits
b1fa321
9ed6e86
9e225bd
c62a82d
1219478
633522f
6effc8b
b382470
de2fa8e
921585d
0cbfe98
f378d87
c18ba6c
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 |
---|---|---|
@@ -1,5 +1,5 @@ | ||
*swp | ||
node_modules | ||
*.log | ||
.DS_Store | ||
.idea | ||
*swp | ||
node_modules | ||
*.log | ||
.DS_Store | ||
.idea | ||
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
function substituteScope(scope) { | ||
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 name this file 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. also can you add a comment explaining the purpose of this function? I'm a bit confused about what it does - is it replacing scope things? Shouldn't that happen in the steps instead? |
||
const newScope = Object.assign({}, scope); | ||
|
||
for (var symbol in newScope) { | ||
const targetVal = newScope[symbol].toString(); | ||
for (var sym in newScope) { | ||
const valStr = newScope[sym].toString(); | ||
const replaced = valStr.replace(symbol, targetVal); | ||
newScope[sym] = replaced; | ||
} | ||
} | ||
|
||
return newScope; | ||
} | ||
|
||
module.exports = substituteScope; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,50 +5,55 @@ const TreeSearch = {}; | |
// Returns a function that performs a preorder search on the tree for the given | ||
// simplification function | ||
TreeSearch.preOrder = function(simplificationFunction) { | ||
return function (node) { | ||
return search(simplificationFunction, node, true); | ||
return function (node, scope={}) { | ||
return search(simplificationFunction, node, true, scope); | ||
}; | ||
}; | ||
|
||
// Returns a function that performs a postorder search on the tree for the given | ||
// simplification function | ||
TreeSearch.postOrder = function(simplificationFunction) { | ||
return function (node) { | ||
return search(simplificationFunction, node, false); | ||
return function (node, scope={}) { | ||
return search(simplificationFunction, node, false, scope); | ||
}; | ||
}; | ||
|
||
// A helper function for performing a tree search with a function | ||
function search(simplificationFunction, node, preOrder) { | ||
function search(simplificationFunction, node, preOrder, scope={}) { | ||
let status; | ||
|
||
if (preOrder) { | ||
status = simplificationFunction(node); | ||
status = simplificationFunction(node, scope); | ||
if (status.hasChanged()) { | ||
return status; | ||
} | ||
} | ||
|
||
if (Node.Type.isConstant(node) || Node.Type.isSymbol(node)) { | ||
if (Node.Type.isConstant(node)) { | ||
return Node.Status.noChange(node); | ||
} | ||
// Moved isUnaryMinus check above Symbol check to address symbol nested inside unaryminus | ||
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. try to avoid saying things like "moved" since future people looking at this code will only know the current state, not the past and that'd be confusing! you could instead say something like "isUnaryMinus needs to be checked before Symbol because ..." Though I'm confused why it has to be first. What test would fail if you checked symbol first? A unary minus is not a symbol, and unary minus recurses on its child! (let me know if I'm being confusing, not sure if I'm explaining this well ^^) |
||
else if (Node.Type.isUnaryMinus(node)) { | ||
status = search(simplificationFunction, node.args[0], preOrder); | ||
status = search(simplificationFunction, node.args[0], preOrder, scope); | ||
if (status.hasChanged()) { | ||
return Node.Status.childChanged(node, status); | ||
} | ||
} | ||
// Break out isSymbol test and add a changeType for SUBSTITUTE_SYMBOL? | ||
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. don't need this comment (it's more like a commit message and less like a long term comment, like above) Though if you want to keep a comment here it'd probably be more like |
||
else if (Node.Type.isSymbol(node)) { | ||
return simplificationFunction(node, scope); | ||
} | ||
else if (Node.Type.isOperator(node) || Node.Type.isFunction(node)) { | ||
for (let i = 0; i < node.args.length; i++) { | ||
const child = node.args[i]; | ||
const childNodeStatus = search(simplificationFunction, child, preOrder); | ||
const childNodeStatus = search(simplificationFunction, child, preOrder, scope); | ||
if (childNodeStatus.hasChanged()) { | ||
return Node.Status.childChanged(node, childNodeStatus, i); | ||
} | ||
} | ||
} | ||
else if (Node.Type.isParenthesis(node)) { | ||
status = search(simplificationFunction, node.content, preOrder); | ||
status = search(simplificationFunction, node.content, preOrder, scope); | ||
if (status.hasChanged()) { | ||
return Node.Status.childChanged(node, status); | ||
} | ||
|
@@ -58,7 +63,7 @@ function search(simplificationFunction, node, preOrder) { | |
} | ||
|
||
if (!preOrder) { | ||
return simplificationFunction(node); | ||
return simplificationFunction(node, scope); | ||
} | ||
else { | ||
return Node.Status.noChange(node); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,9 @@ | ||
const math = require('mathjs'); | ||
const stepThrough = require('./stepThrough'); | ||
const Substitute = require('../Substitute'); | ||
|
||
function simplifyExpressionString(expressionString, debug=false) { | ||
function simplifyExpressionString(expressionString, debug=false, scope={}) { | ||
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'd be nice if debug was the last argument, though I know that's a lot of moving code around so let me know if you want help going through and editing it 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. also I think we should replace |
||
const newScope = Substitute(scope); | ||
let exprNode; | ||
try { | ||
exprNode = math.parse(expressionString); | ||
|
@@ -10,7 +12,7 @@ function simplifyExpressionString(expressionString, debug=false) { | |
return []; | ||
} | ||
if (exprNode) { | ||
return stepThrough(exprNode, debug); | ||
return stepThrough(exprNode, debug, newScope); | ||
} | ||
return []; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ const divisionSearch = require('./divisionSearch'); | |
const fractionsSearch = require('./fractionsSearch'); | ||
const functionsSearch = require('./functionsSearch'); | ||
const multiplyFractionsSearch = require('./multiplyFractionsSearch'); | ||
const substituteScopeSearch = require('./substituteScopeSearch'); | ||
|
||
const clone = require('../util/clone'); | ||
const flattenOperands = require('../util/flattenOperands'); | ||
|
@@ -19,7 +20,7 @@ const removeUnnecessaryParens = require('../util/removeUnnecessaryParens'); | |
|
||
// Given a mathjs expression node, steps through simplifying the expression. | ||
// Returns a list of details about each step. | ||
function stepThrough(node, debug=false) { | ||
function stepThrough(node, debug=false, scope={}) { | ||
if (debug) { | ||
// eslint-disable-next-line | ||
console.log('\n\nSimplifying: ' + print.ascii(node, false, true)); | ||
|
@@ -37,15 +38,15 @@ function stepThrough(node, debug=false) { | |
let iters = 0; | ||
|
||
// Now, step through the math expression until nothing changes | ||
nodeStatus = step(node); | ||
nodeStatus = step(node, scope); | ||
while (nodeStatus.hasChanged()) { | ||
if (debug) { | ||
logSteps(nodeStatus); | ||
} | ||
steps.push(removeUnnecessaryParensInStep(nodeStatus)); | ||
|
||
node = Status.resetChangeGroups(nodeStatus.newNode); | ||
nodeStatus = step(node); | ||
nodeStatus = step(node, scope); | ||
|
||
if (iters++ === MAX_STEP_COUNT) { | ||
// eslint-disable-next-line | ||
|
@@ -60,7 +61,7 @@ function stepThrough(node, debug=false) { | |
|
||
// Given a mathjs expression node, performs a single step to simplify the | ||
// expression. Returns a Node.Status object. | ||
function step(node) { | ||
function step(node, scope={}) { | ||
let nodeStatus; | ||
|
||
node = flattenOperands(node); | ||
|
@@ -69,6 +70,9 @@ function step(node) { | |
const simplificationTreeSearches = [ | ||
// Basic simplifications that we always try first e.g. (...)^0 => 1 | ||
basicsSearch, | ||
// Substitute symbols present in the optional scope object with | ||
// their respective expressions. | ||
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 add an example similar to the other simplification searches here? 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. Added an example but let me know if it needs to be reworded. 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. looks good to me! awesome |
||
substituteScopeSearch, | ||
// Simplify any division chains so there's at most one division operation. | ||
// e.g. 2/x/6 -> 2/(x*6) e.g. 2/(x/6) => 2 * 6/x | ||
divisionSearch, | ||
|
@@ -91,7 +95,7 @@ function step(node) { | |
]; | ||
|
||
for (let i = 0; i < simplificationTreeSearches.length; i++) { | ||
nodeStatus = simplificationTreeSearches[i](node); | ||
nodeStatus = simplificationTreeSearches[i](node, scope); | ||
// Always update node, since there might be changes that didn't count as | ||
// a step. Remove unnecessary parens, in case one a step results in more | ||
// parens than needed. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
const ChangeTypes = require('../../ChangeTypes'); | ||
const Node = require('../../node'); | ||
const TreeSearch = require('../../TreeSearch'); | ||
|
||
// Searches through the tree, prioritizing deeper nodes, and substitutes | ||
// in-scope values for their respective expressions on a symbol node if possible. | ||
// Returns a Node.Status object. | ||
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. awesome documentation :D |
||
const search = TreeSearch.postOrder(scopeSubstitution); | ||
|
||
function scopeSubstitution(node, scope) { | ||
if (Node.Type.isSymbol(node)) { | ||
return substituteAndSimplifySymbols(node, scope); | ||
} | ||
else { | ||
return Node.Status.noChange(node); | ||
} | ||
} | ||
|
||
// SUBSTITUTES | ||
// Returns a Node.Status object with substeps | ||
function substituteAndSimplifySymbols(node, scope) { | ||
if (!Node.Type.isSymbol(node)) { | ||
return Node.Status.noChange(node); | ||
} | ||
|
||
let symbolName; | ||
if (node.type === 'SymbolNode') { | ||
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 can use 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. WAIT does this return true for unary minus too? okay this makes sense https://github.com/socraticorg/mathsteps/blob/344d15b5d5e25251cef7d6976d6a73df16661d7d/lib/node/Type.js#L32 For any isSymbol thing you've added in this PR can you pass in Sorry, that's weird that we did that (though I'm assuming there's a reason I've now forgotten XD but feel free to investigate if you're curious or open an issue) 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. I left this alone for now since you followed up with the change to |
||
symbolName = node.name; | ||
} | ||
else if (Node.Type.isUnaryMinus(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. now that I updated the so you can take out |
||
symbolName = node.args[0].name; | ||
} | ||
|
||
if (scope.hasOwnProperty(symbolName)) { | ||
// when declared at top, kept getting TypeError: ___ is not a function | ||
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 explain a bit more about when the error happens? I can try replicating it on my computer and see what's up. Did it say |
||
const simplifyExpression = require('../../simplifyExpression'); | ||
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. so What you have here also means if you have My suggestion instead is:
I know it's a bit different from your idea, so we could chat more about this and make sure we're on the same page and both happy with whatever we decide to do. I think adding some tests that show multiple steps will also make it more clear what this changes. Let me know if you have more questions or comments about this! |
||
const substeps = simplifyExpression(scope[symbolName]); | ||
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. instead of simplifying the expression here, I think it's best to just do then any simplification of the value can happen in later steps instead of substeps and it makes this code less complicated! what do you think? 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. I'm a bit turned around here, perhaps due to the approach I took. Since simplifyExpression is where the scope substitution takes place, I'm concerned calling math.parse(scope[symbolName]) here won't be effective since it will not perform the scope substitution. That may be why you advised to put the substituteScope call deeper in the simplify function? Left as is in my recent (incremental) PR. |
||
if (substeps.length === 0) { | ||
const newNode = Node.Creator.constant(Number(scope[symbolName])); | ||
return Node.Status.nodeChanged( | ||
ChangeTypes.SUBSTITUTE_SCOPE_SYMBOL, node, newNode); | ||
} | ||
else { | ||
const newNode = substeps.slice(-1)[0].newNode; | ||
return Node.Status.nodeChanged( | ||
ChangeTypes.SUBSTITUTE_SCOPE_SYMBOL, node, newNode, false, substeps); | ||
} | ||
} | ||
else { | ||
return Node.Status.noChange(node); | ||
} | ||
} | ||
|
||
module.exports = search; |
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 keep the new line at the end of the file? Github seems to like it and puts a 🚫 if you don't, so I've adopted liking it too ^_^
I see it in a few other files, so just scroll this PR and you'll see where!
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.
Hopefully caught all these in my latest PR. Will pay attention to this going forward.