UNPKG

mathsteps-experimental-fork

Version:

Step by step math solutions. Experimental Fork

122 lines (119 loc) 6.72 kB
import { myNodeToString } from '../../../newServices/nodeServices/myNodeToString.js'; import { getAddedAndRemovedTerms, flattenAndIndexTrackAST, combineNumberVarTimesTerms, combineMakeMinusNegativeTerms, makeCountTerms } from '../../../newServices/nodeServices/nodeHelpers.js'; import { parseText } from '../../../newServices/nodeServices/parseText.js'; import { removeUnnecessaryParentheses } from '../../../newServices/nodeServices/removeUnnessaryParenthesis.js'; import { getAnswerFromStep } from '../../stepEvaluationHelpers.js'; import { getSimplifyChangeTypeByOp } from '../../../types/changeType/changeAndMistakeUtils.js'; function applyOpFixBasedOnTermRemovals(removed1, removed2, removedOp) { const is1Negative = removed1.includes("-"); const is2Negative = removed2.includes("-"); const isNeitherNegative = !is1Negative && !is2Negative; const isBothNegative = is1Negative && is2Negative; const isOnly1Negative = is1Negative && !is2Negative || !is1Negative && is2Negative; let opPerformed = null; if (removedOp === "*" || removedOp === "/" || isNeitherNegative) opPerformed = removedOp; else if (removedOp === "+" && isBothNegative) opPerformed = "+"; else if (removedOp === "+" && isOnly1Negative) opPerformed = "-"; else throw new Error(`Unexpected state. Operator: ${removedOp}`); return opPerformed; } function _parseExpression(expr) { const ast = parseText(expr); const flattenedTerms = flattenAndIndexTrackAST(ast).sort((a, b) => a.index - b.index); const combinedTerms = combineNumberVarTimesTerms(flattenedTerms); const normalizedTerms = combineMakeMinusNegativeTerms(combinedTerms); return makeCountTerms(normalizedTerms); } function earlyIsValidOperation(fromTerms, toTerms) { const fromHasDivisionOp = fromTerms.some((term) => term.type === "operator" && term.value === "/"); const toHasDivisionOp = toTerms.some((term) => term.type === "operator" && term.value === "/"); const fromHasMultOp = fromTerms.some((term) => term.type === "operator" && term.value === "*"); const toHasMultOp = toTerms.some((term) => term.type === "operator" && term.value === "*"); if (!fromHasDivisionOp && toHasDivisionOp) return false; if (!fromHasMultOp && toHasMultOp) return false; const fromNoOps = fromTerms.filter((term) => term.type === "term"); const toNoOps = toTerms.filter((term) => term.type === "term"); if (fromNoOps.length !== toNoOps.length + 1) return false; return true; } function isValidOperation(useOperators, removedNoOpTerms, addedNoOpTerms, removedOperators) { const hasDivisionOp = useOperators.some((op) => op.term.value === "/"); const hasVariables = removedNoOpTerms.some((term) => /[a-zA-Z]/.test(term.term.value)); const hasDivisionOrVariables = hasDivisionOp || hasVariables; if (addedNoOpTerms.length > 1 || removedOperators.length > 1 && !hasDivisionOrVariables || removedOperators.length > 2 && hasDivisionOrVariables || hasVariables && removedNoOpTerms.length > 3 || !hasVariables && removedNoOpTerms.length > 2 || removedNoOpTerms.length === 0) { return false; } else { return true; } } function findAttemptedOperationUseCore(from, to) { const fromStr = typeof from === "string" ? from : myNodeToString(from); const toStr = typeof to === "string" ? to : myNodeToString(to); const strLengthDiff = Math.abs(fromStr.length - toStr.length); if (strLengthDiff > 7) return null; const fromTerms = _parseExpression(fromStr); const toTerms = _parseExpression(toStr); const fromStrRemovedUnnecessaryParen = myNodeToString(removeUnnecessaryParentheses(fromStr)); const toStrRemovedUnnecessaryParen = myNodeToString(removeUnnecessaryParentheses(toStr)); const fromParenthesisCount = fromStrRemovedUnnecessaryParen.match(/\(/g)?.length || 0; const toParenthesisCount = toStrRemovedUnnecessaryParen.match(/\(/g)?.length || 0; if (fromParenthesisCount !== toParenthesisCount) return null; if (!earlyIsValidOperation(fromTerms, toTerms)) return null; const { removed, added, opFound = null } = getAddedAndRemovedTerms(fromTerms, toTerms); const removedNoOpTerms = removed.filter((item) => item.term.type === "term").sort((a, b) => a.term.index - b.term.index) || []; const addedNoOpTerms = added?.filter((item) => item.term.type === "term").sort((a, b) => a.term.index - b.term.index) || []; const removedOperators = removed?.filter((item) => item.term.type === "operator").sort((a, b) => a.term.index - b.term.index) || []; const useOperators = [opFound, ...removedOperators].filter((op) => op); if (!isValidOperation(useOperators, removedNoOpTerms, addedNoOpTerms, removedOperators)) return null; const removedTerm1 = removedNoOpTerms[0].term; const removedTerm2 = removedNoOpTerms[1]?.term ?? removedTerm1; const resultTerm = addedNoOpTerms[0].term; const useOp = useOperators[0].term; const termOperationFromSystem = getAnswerFromStep(`${removedTerm1.value} ${useOp.value} ${removedTerm2.value}`); let fullAttemptedOpResult = toTerms.map((term) => { return term.type === "term" && term.value === resultTerm.value && resultTerm.index === term.index ? { ...term, value: termOperationFromSystem } : term; }).map((term) => term.value).join(""); fullAttemptedOpResult = fullAttemptedOpResult.replace(/^--/g, "-"); return { opResult: resultTerm.value, removedTerms: [removedTerm1.value, removedTerm2.value], fullAttemptedOpResult, // Fix operators we use based on num + - num specifically for getting the changeType. changeType: getSimplifyChangeTypeByOp(applyOpFixBasedOnTermRemovals(removedTerm1.value, removedTerm2.value, useOp.value)) }; } function findAttemptedOperationUse({ from, to, expressionEquals }) { const fromStr = typeof from === "string" ? from : myNodeToString(from); const toStr = typeof to === "string" ? to : myNodeToString(to); const findWhatOpRes = findAttemptedOperationUseCore(fromStr, toStr); if (!findWhatOpRes) return null; const { changeType, fullAttemptedOpResult } = findWhatOpRes; const isMistake = !expressionEquals(fullAttemptedOpResult, toStr); const fromReachesSameAsAttempted = expressionEquals(getAnswerFromStep(fromStr), getAnswerFromStep(fullAttemptedOpResult)); return { removedTerms: findWhatOpRes.removedTerms, from: fromStr, to: toStr, changeType, attemptedToGetTo: fromReachesSameAsAttempted ? fullAttemptedOpResult : void 0, /* mistakenChangeType: 'UNKNOWN', */ isMistake, // mTo: [], // availableChangeTypes: allPossibleNextStep.map(step => step.changeType), attemptedChangeType: changeType // allPossibleCorrectTos, }; } export { findAttemptedOperationUse, findAttemptedOperationUseCore };