-
Notifications
You must be signed in to change notification settings - Fork 278
WIP: Substitute symbols for expressions from optional scope #212
base: master
Are you sure you want to change the base?
Changes from all 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 |
---|---|---|
|
@@ -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, options={}, debug=false) { | ||
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, options); | ||
while (nodeStatus.hasChanged()) { | ||
if (debug) { | ||
logSteps(nodeStatus); | ||
} | ||
steps.push(removeUnnecessaryParensInStep(nodeStatus)); | ||
|
||
node = Status.resetChangeGroups(nodeStatus.newNode); | ||
nodeStatus = step(node); | ||
nodeStatus = step(node, options); | ||
|
||
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, options={}) { | ||
let nodeStatus; | ||
|
||
node = flattenOperands(node); | ||
|
@@ -69,6 +70,10 @@ 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 |
||
// e.g., given options: { scope: { x: 10, y: 20 } }, x + y => 10 + 20 | ||
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 +96,7 @@ function step(node) { | |
]; | ||
|
||
for (let i = 0; i < simplificationTreeSearches.length; i++) { | ||
nodeStatus = simplificationTreeSearches[i](node); | ||
nodeStatus = simplificationTreeSearches[i](node, options); | ||
// 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,60 @@ | ||
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, options={}) { | ||
if (Node.Type.isSymbol(node)) { | ||
return substituteAndSimplifySymbols(node, options); | ||
} | ||
else { | ||
return Node.Status.noChange(node); | ||
} | ||
} | ||
|
||
// SUBSTITUTES | ||
// Returns a Node.Status object with substeps | ||
function substituteAndSimplifySymbols(node, options={}) { | ||
if (!options.hasOwnProperty('scope')) { | ||
return Node.Status.noChange(node); | ||
} | ||
|
||
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; | ||
} | ||
|
||
const scope = options.scope; | ||
if (scope.hasOwnProperty(symbolName)) { | ||
// when declared at top, kept getting | ||
// TypeError: <nameIGaveToFunction> 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. what is 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. ...this was my way of trying to communicate that, no matter what name I gave to the function that made this call, it would fail with that error message. 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. which is the function making the call? is it in the PR here? what's it called right now? There's so many functions in the code haha so confused which one you're referring to! |
||
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]); | ||
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; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
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. just copying from before so it's easier to keep track of what's left (no worries for not getting to it yet)
|
||
// create a copy of the provided scope | ||
const newScope = Object.assign({}, scope); | ||
|
||
// iterate over symbols | ||
for (var symbol in newScope) { | ||
// store the symbol's current value in a variable | ||
const targetVal = newScope[symbol].toString(); | ||
// iterate through scope (again) | ||
for (var sym in newScope) { | ||
// extract the value of a symbol as a string | ||
const valStr = newScope[sym].toString(); | ||
// within the stored value, replace symbol (from the outer loop) | ||
// with its respective value | ||
const replaced = valStr.replace(symbol, targetVal); | ||
// store the substituted value in the new scope under the same symbol | ||
newScope[sym] = replaced; | ||
} | ||
} | ||
|
||
return newScope; | ||
} | ||
|
||
module.exports = substituteScope; |
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.
I think with my unary minus change, it doesn't have to be checked first - can you switch it back and see if that works? Before, symbols with unary minuses were counted as symbols too by default, but now it
Node.Type.isSymbol(node)
on a unary minus node should return false by default :D