mathsteps-experimental-fork
Version:
Step by step math solutions. Experimental Fork
210 lines (190 loc) • 6.31 kB
JavaScript
;
const myNodeToString = require('../newServices/nodeServices/myNodeToString.js');
const index = require('../node/index.js');
/**
* @param {{ args: import('mathjs').MathNode[]; op: string; }} node
* @param {string} op
*/
function _explodeNodeArgs(node, op) {
// [2x, a, b] gives [2, x, a, b]
/** @type {any[]} */
let rv = [];
node.args.forEach((oneArg) => {
if (oneArg.op === op)
rv = rv.concat(_explodeNodeArgs(oneArg, op));
else
rv.push(oneArg);
});
return rv
}
/**
* @param {import('mathjs').MathNode} node
* @return {number}
*/
function _getPow(node) {
let rv = 0;
if (index.default.Type.isOperator(node)) {
if ((node.op === '^') && index.default.Type.isSymbol(node.args[0])) {
// x^n -> the effective power is n
// Possible improvement: Handle bignumber exponents?
rv = Number.parseInt(node.args[1].value); // -- Grayson. Suprised this works? Isn't value an object?
}
else {
// x op y - parse each argument recursively.
// Example: 2 * x * y^3
node.args.forEach((oneChild) => {
rv += _getPow(oneChild);
});
}
}
else if (index.default.Type.isSymbol(node)) {
// x - the effective power is 1
rv = 1;
}
else if (index.default.Type.isFunction(node)) {
// Dirty workaround to move sqrt(2) like terms to the right.
// Example: 2 + sqrt(2)
rv = -1;
}
return rv
}
/**
* @param {import('mathjs').MathNode[]} nodes
* @mutates nodes - sorts nodes by power.
*/
function _sortNodesByPow(nodes) {
nodes.sort((a, b) => {
const powA = _getPow(a);
const powB = _getPow(b);
if (powA > powB)
return -1
else if (powA < powB)
return 1
else
return 0
});
}
/**
* @param {import('mathjs').MathNode[]} nodes
* @mutates nodes - sorts nodes by power.
*/
function _sortNodesByName(nodes) {
nodes.sort((nodeA, nodeB) => {
let a = myNodeToString.myNodeToString(nodeA);
let b = myNodeToString.myNodeToString(nodeB);
// Dirty workaround to move sqrt(2) like factors to the right.
if (index.default.Type.isFunction(nodeA))
a = `|fct|${a}`;
if (index.default.Type.isFunction(nodeB))
b = `|fct|${b}`;
// Compare nodes as text.
if (a < b)
return -1
else if (a > b)
return 1
else
return 0
});
}
/**
* @param {any} node
* @mutates node - sorts node arguments.
* @param {boolean} isVerySort - added to also do _sortNodesByName everywhere
* @param {boolean} sortConstants - flag to enable/disable sorting of constants
*/
function _kemuSortArgs(node, isVerySort = false, sortConstants = false) {
if (!node._kemuIsSorted) {
// Avoid sorting the same node twice.
node._kemuIsSorted = true;
if (node.args) {
// Sort args recursively.
node.args.forEach((/** @type {any} */ oneChild) => {
_kemuSortArgs(oneChild, isVerySort, sortConstants);
});
switch (node.op) {
case '*': {
/** @type {Array<import('mathjs').MathNode>} */
const numbers = [];
/** @type {Array<import('mathjs').MathNode>} */
const other = [];
// Flatten nested multiply e.g. (3*x)*y =>3*x*y
node.args = _explodeNodeArgs(node, '*');
// Divide child into 3 groups:
// - number constants e.g. 3.14,
// - other nodes e.g. x + a/4
node.args.forEach((/** @type {any} */ oneChild) => {
if (index.default.Type.isConstantOrConstantFraction(oneChild)) {
// Constant number node e.g. 3.14
numbers.push(oneChild);
}
else if (index.default.Type.isUnaryMinus(oneChild)
&& index.default.Type.isConstantOrConstantFraction(oneChild.args[0])) {
// Constant negated number node e.g. -3.14
// Possible improvement: Handle negated symbols (-x).
numbers.push(oneChild);
}
else {
// Other node e.g. (x + a/4)
other.push(oneChild);
}
});
// Sort nodes within groups.
_sortNodesByName(other);
if (isVerySort) // Grayson
_sortNodesByName(numbers);
// Join node groups in the following order:
// | unsorted constants | sorted complex nodes |
/** @type {import('mathjs').MathNode[]} */
let newArgs = [];
newArgs = newArgs.concat(numbers);
newArgs = newArgs.concat(other);
// Apply new args order.
node.args = newArgs;
break
}
case '+': {
// Flatten nested addition e.g. (3+x)+y => 3 + x + y
node.args = _explodeNodeArgs(node, '+');
if (sortConstants) {
/** @type {Array<import('mathjs').MathNode>} */
const constants = [];
/** @type {Array<import('mathjs').MathNode>} */
const other = [];
// Separate constants from other nodes
node.args.forEach((oneChild) => {
if (index.default.Type.isConstantOrConstantFraction(oneChild)) {
constants.push(oneChild);
}
else {
other.push(oneChild);
}
});
// Sort constants numerically if the flag is enabled
if (sortConstants) {
constants.sort((a, b) => {
const valueA = Number(a.value);
const valueB = Number(b.value);
return valueA - valueB
});
}
// Apply sorted constants and other nodes back
node.args = constants.concat(other);
// Sort nodes by power
_sortNodesByPow(node.args);
if (isVerySort) // Grayson
_sortNodesByName(node.args);
break
}
else {
_sortNodesByPow(node.args);
if (isVerySort) // Grayson
_sortNodesByName(node.args);
break
}
}
}
}
}
return node
}
module.exports = _kemuSortArgs;