mathsteps-experimental-fork
Version:
Step by step math solutions. Experimental Fork
91 lines (88 loc) • 4.58 kB
JavaScript
import { isConstantNode, isOperatorNode } from '../../config.js';
import { myNodeToString } from './myNodeToString.js';
import { parseText } from './parseText.js';
import { getReverseOp, getAddRemoveTermTypeBasedOnOp } from '../../types/changeType/changeAndMistakeUtils.js';
import { filterUniqueValues } from '../../util/arrayUtils.js';
import { cleanString } from '../../util/cleanString.js';
function isNegative(node) {
if (isOperatorNode(node) && node.isUnary()) {
return true;
}
if (isOperatorNode(node) && node.op === "*") {
return node.args.some(
(arg) => isConstantNode(arg) && Number.parseFloat(arg.value.toString()) < 0
);
}
if (isConstantNode(node)) {
return Number.parseFloat(node.value.toString()) < 0;
}
return false;
}
function generateExpressions(node, options = { combinedTermLimit: 2 }) {
const countTermsFn = (node2) => isOperatorNode(node2) && node2.args ? node2.args.reduce((acc, arg) => acc + countTermsFn(arg), 0) : 1;
let dfsNodeId = 0;
function core(node2) {
let expressions = [];
const operations = ["+", "-", "*", "/", "+-"];
if (isOperatorNode(node2) && operations.includes(node2.op) && node2.args.length >= 2) {
const secondOp = node2.op;
let firstOp = node2.op;
if (node2.args.length === 2 && firstOp === "-") {
firstOp = "+";
}
expressions.push({
op: firstOp,
node: node2.args[0],
term: myNodeToString(node2.args[0]),
dfsNodeId: dfsNodeId++
});
expressions.push({
op: secondOp,
node: node2.args[1],
term: myNodeToString(node2.args[1]),
dfsNodeId: dfsNodeId++
});
}
if (isOperatorNode(node2) && node2.args) {
for (const arg of node2.args)
expressions = expressions.concat(core(arg));
}
return expressions;
}
const filterResult = filterUniqueValues(core(node), (a, b) => a.term === b.term && a.op === b.op);
if (filterResult.length === 0) {
if (isConstantNode(node)) {
return isNegative(node) ? [{ op: "+-", term: myNodeToString(node), node, dfsNodeId: 0 }] : [{ op: "+", term: myNodeToString(node), node, dfsNodeId: 0 }];
}
}
return filterResult.filter((item) => countTermsFn(item.node) <= options.combinedTermLimit);
}
function findAllOperationsThatCanBeRemoved(fromNode_, history, options = {}) {
const unFlattenNodeFn = (node) => parseText(myNodeToString(node));
const getNumberOpFn = (step) => options.isAdded ? step?.addedNumOp || step?.removeNumberOp : step?.removeNumberOp || step?.addedNumOp;
const fromNodeNode = typeof fromNode_ === "string" ? parseText(fromNode_) : unFlattenNodeFn(fromNode_);
const fromNodeString = typeof fromNode_ === "string" ? cleanString(fromNode_) : myNodeToString(fromNodeNode);
const previousRemovedNumOps = history.filter((step) => getNumberOpFn(step)).map((step) => getNumberOpFn(step));
const flippedPreviousReverseNumOps = previousRemovedNumOps.map((numOp) => ({ number: numOp.number, op: getReverseOp(numOp.op) }));
let termOps = generateExpressions(fromNodeNode);
termOps = filterUniqueValues(termOps, (a, b) => a.term === b.term && a.op === b.op);
termOps = termOps.filter((term) => !flippedPreviousReverseNumOps.some((prev) => prev.number === term.term && prev.op === term.op));
termOps = termOps.filter((term) => term.term !== "0");
termOps = filterUniqueValues(termOps, (a, b) => a.term === b.term && a.op === b.op);
const newTermStrings = termOps.map((termOp) => {
const fn = (str) => ["+", "-", "*", "/", "^", "(", ")"].some((op) => str.includes(op));
const tempFrom = fn(fromNodeString) ? `(${fromNodeString})` : fromNodeString;
const tempTerm = fn(termOp.term) ? `(${termOp.term})` : termOp.term;
return `${tempFrom} ${getReverseOp(termOp.op)} ${tempTerm}`;
});
return termOps.map((term, index) => ({ removeNumberOp: { number: term.term, op: term.op, dfsNodeId: term.dfsNodeId }, newExpression: newTermStrings[index] })).filter((term) => term.removeNumberOp.number !== void 0 && term.removeNumberOp.op !== void 0 && term.newExpression !== void 0).map((term) => ({
// map to the correct format
from: fromNodeString,
to: term.newExpression,
changeType: getAddRemoveTermTypeBasedOnOp(getReverseOp(term.removeNumberOp.op), options.isAdded ? "add" : "remove"),
isMistake: false,
removeNumberOp: { ...term.removeNumberOp, op: getReverseOp(term.removeNumberOp.op), number: term.removeNumberOp.number },
availableChangeTypes: ["EQ_REMOVE_TERM"]
}));
}
export { findAllOperationsThatCanBeRemoved };