mathsteps-experimental-fork
Version:
Step by step math solutions. Experimental Fork
255 lines (250 loc) • 12.2 kB
JavaScript
;
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
const expressionEqualsAndNormalization = require('../newServices/expressionEqualsAndNormalization.js');
const equationCache = require('./equationCache.js');
const findAttemptedOperationUse = require('./rules/stepEvaluationOnly/findAttemptedOperationUse.js');
const stepEvaluationCoreNextStepOptionsHelper = require('./stepEvaluationCoreNextStepOptionsHelper.js');
const stepEvaluationHelpers = require('./stepEvaluationHelpers.js');
const changeAndMistakeUtils = require('../types/changeType/changeAndMistakeUtils.js');
const ChangeTypes = require('../types/changeType/ChangeTypes.js');
const arrayUtils = require('../util/arrayUtils.js');
const cleanString = require('../util/cleanString.js');
const countZero = (str) => (str.match(/\b0\b/g) || []).length;
const expressionEquals = (exp0, exp1) => expressionEqualsAndNormalization.areExpressionEqual(exp0, exp1, equationCache.getValidStepEqCache());
const MAX_NEXT_STEPS = 110;
const MAX_STEP_DEPTH = 4;
const QUEUE_PRIORITY = ["REMOVE_ADDING_ZERO", "DIVISION_BY_ONE", "REMOVE_MULTIPLYING_BY_ONE", "SIMPLIFY_ARITHMETIC__SUBTRACT", "SIMPLIFY_ARITHMETIC__ADD", "KEMU_DISTRIBUTE_MUL_OVER_ADD", "SIMPLIFY_ARITHMETIC__MULTIPLY", "CANCEL_TERMS_FOR_FRACTION", "CANCEL_TERMS_FOR_ADDITION", "SIMPLIFY_ARITHMETIC__DIVIDE", "COLLECT_AND_COMBINE_LIKE_TERMS"];
const DEFERRED_CHANGE_TYPES = [...ChangeTypes.EQUATION_ADD_AND_REMOVE_TERMS];
const DEFERRED_DEPTH_THRESHOLD = 3;
function findAttachedMistakeThatEqualsValueToFind({ possibleStep, valueToFind, theProblem }) {
for (const mToStep of possibleStep.mTo || []) {
if (possibleStep.to && mToStep.to === possibleStep.to)
continue;
if (expressionEquals(mToStep.to, valueToFind)) {
const isStillCorrect = expressionEquals(stepEvaluationHelpers.getAnswerFromStep(theProblem), stepEvaluationHelpers.getAnswerFromStep(mToStep.to));
if (isStillCorrect)
continue;
return { ...mToStep, ...possibleStep, from: possibleStep.from, to: mToStep.to, attemptedToGetTo: possibleStep.to, attemptedChangeType: possibleStep.changeType, changeType: mToStep.changeType, isMistake: true };
}
}
return null;
}
function hasAlreadyAttempted(currentStep, triedSteps) {
if (triedSteps.has(currentStep))
return true;
for (const attempted of triedSteps) {
if (expressionEquals(attempted, currentStep))
return true;
}
return false;
}
function addStepToQueue(queue, step, changeType) {
const priorityIndex = QUEUE_PRIORITY.indexOf(changeType);
if (priorityIndex === -1)
queue.push(step);
else
queue.splice(priorityIndex, 0, step);
}
function checkForMatchingSteps({
possibleSteps,
valueToFind,
history,
theProblem,
isEquation,
stepCount,
start
}) {
for (const possibleStep of possibleSteps) {
const updatedHistory = [...history, possibleStep];
if (expressionEquals(possibleStep.to, valueToFind)) {
if (!possibleStep.isMistake) {
return { history: updatedHistory };
} else {
const isStillCorrect = expressionEquals(
stepEvaluationHelpers.getAnswerFromStep(theProblem),
stepEvaluationHelpers.getAnswerFromStep(possibleStep.to)
);
if (!isStillCorrect)
return { history: updatedHistory };
}
}
if (!isEquation) {
const foundAttachedMistake = findAttachedMistakeThatEqualsValueToFind({
possibleStep,
valueToFind,
theProblem
});
if (foundAttachedMistake) {
updatedHistory.pop();
updatedHistory.push(foundAttachedMistake);
return { history: updatedHistory };
}
}
}
if (stepCount === 0) {
const opUseFound = findAttemptedOperationUse.findAttemptedOperationUse({
from: start,
to: valueToFind,
expressionEquals
});
const isEquationAndNotAMistake = isEquation && !opUseFound?.isMistake;
if (opUseFound && (isEquationAndNotAMistake || !isEquation)) {
history.push({
...opUseFound,
allPossibleCorrectTos: possibleSteps.filter((step) => !step.isMistake).map((step) => step.to),
availableChangeTypes: possibleSteps.map((step) => step.changeType)
});
return { history };
}
}
return null;
}
function coreAssessUserStep(lastTwoUserSteps, otherSide = null) {
const isEquation = otherSide !== null;
const valueToFind = lastTwoUserSteps[1];
const theProblem = lastTwoUserSteps[0];
const triedSteps = /* @__PURE__ */ new Set();
let firstFoundNextStepOptions = [];
if (theProblem === valueToFind)
return { history: [], firstFoundNextStepOptions: stepEvaluationCoreNextStepOptionsHelper.findAllNextStepOptions(valueToFind, { history: [], otherSide }) };
const mainQueue = [{
start: theProblem,
history: [],
isDeferred: false,
deferDepth: 0
}];
let stepCount = 0;
let differed = [];
while ((mainQueue.length > 0 || differed.length > 0) && stepCount < MAX_NEXT_STEPS) {
let nextStep = function(step) {
triedSteps.add(step);
let allPossibleNextStep = stepEvaluationCoreNextStepOptionsHelper.findAllNextStepOptions(step, { history, otherSide }).sort((a, b) => countZero(b.to) - countZero(a.to));
differed.push(...allPossibleNextStep.filter((step2) => DEFERRED_CHANGE_TYPES.includes(step2.changeType)).map((step2) => ({
start: step2.to,
history: JSON.parse(JSON.stringify([...history, step2])),
depth: depth + 1,
isDeferred: true,
deferDepth: 0
})));
allPossibleNextStep = allPossibleNextStep.filter((step2) => !DEFERRED_CHANGE_TYPES.includes(step2.changeType));
if (depth === 0) {
firstFoundNextStepOptions = allPossibleNextStep.filter((step2) => !step2.isMistake);
}
const matchResult = checkForMatchingSteps({
possibleSteps: allPossibleNextStep,
valueToFind,
history,
theProblem,
isEquation,
stepCount,
start: cleanedStart
});
if (matchResult) {
return { ...matchResult, firstFoundNextStepOptions };
}
allPossibleNextStep.forEach((possibleStep) => {
addStepToQueue(mainQueue, {
start: possibleStep.to,
history: [...history, possibleStep],
isDeferred: DEFERRED_CHANGE_TYPES.includes(possibleStep.changeType),
deferDepth: 0
}, possibleStep.changeType);
});
stepCount++;
};
differed.forEach((differedStep) => differedStep.deferDepth = (differedStep.deferDepth ?? 0) + 1);
differed = differed.sort((a, b) => (b.deferDepth ?? 0) - (a.deferDepth ?? 0));
if (mainQueue.length === 0) {
const first = differed.shift();
if (first)
mainQueue.push(first);
}
const differedThatPasses = differed.filter((step) => (step?.deferDepth ?? 0) > DEFERRED_DEPTH_THRESHOLD);
mainQueue.push(...differedThatPasses);
differed = differed.filter((step) => (step?.deferDepth ?? 0) <= DEFERRED_DEPTH_THRESHOLD);
const currentStep = mainQueue.shift();
if (!currentStep)
continue;
const { start, history, isDeferred } = currentStep;
const cleanedStart = cleanString.cleanString(start);
const depth = history.length;
if (depth > MAX_STEP_DEPTH || hasAlreadyAttempted(cleanedStart, triedSteps))
continue;
if (isDeferred && expressionEquals(start, valueToFind))
return { history, firstFoundNextStepOptions };
const foundMatch = nextStep(cleanedStart);
if (foundMatch)
return foundMatch;
}
return { history: [], firstFoundNextStepOptions };
}
function correctChangeTypeSubtractToAddFix(changeType, mistakenChangeType) {
return mistakenChangeType && changeType && changeType === "SIMPLIFY_ARITHMETIC__SUBTRACT" && changeAndMistakeUtils.isAnAdditionChangeType(mistakenChangeType) ? changeAndMistakeUtils.convertAdditionToSubtractionErrorType(mistakenChangeType) : mistakenChangeType;
}
function processNoHistoryStep({ from, to, startingStepAnswer, attemptedToGetTo, attemptedChangeType, firstFoundNextStepOptions }) {
const firstChangeTypesLog = firstFoundNextStepOptions.map((step) => step.changeType);
const firstFoundToLog = firstFoundNextStepOptions.map((step) => step.to);
const firstAvailableChangeTypes = arrayUtils.filterUniqueValues(firstChangeTypesLog.flat());
const attemptedChangeTypeCorrected = attemptedChangeType || (firstAvailableChangeTypes.length === 1 ? firstAvailableChangeTypes[0] : "UNKNOWN");
const attemptedToGetToCorrected = attemptedToGetTo || (firstFoundToLog.length === 1 ? firstFoundToLog[0] : "UNKNOWN");
const reachesOriginalAnswer = expressionEquals(stepEvaluationHelpers.getAnswerFromStep(to), startingStepAnswer);
const sharedPart = { from, to, attemptedToGetTo: attemptedToGetToCorrected, reachesOriginalAnswer, attemptedChangeType: attemptedChangeTypeCorrected, availableChangeTypes: firstAvailableChangeTypes, allPossibleCorrectTos: firstFoundToLog };
return expressionEquals(from, to) ? [{ ...sharedPart, isValid: true, mistakenChangeType: "NO_CHANGE", attemptedChangeType: "NO_CHANGE" }] : [{ ...sharedPart, isValid: false, mistakenChangeType: "UNKNOWN" }];
}
function processStep(step, previousStep, startingStepAnswer, historyLength) {
const to = step.to;
const from = historyLength === 1 ? previousStep : step.from;
const attemptedChangeType = step.attemptedChangeType || step.changeType;
let fixedMistakeType = correctChangeTypeSubtractToAddFix(attemptedChangeType, step.changeType || step.attemptedChangeType);
const removeIfOneOfTheseForNow = [
"SIMPLIFY_ARITHMETIC__ADD",
"SIMPLIFY_ARITHMETIC__DIVIDE",
"SIMPLIFY_ARITHMETIC__MULTIPLY",
"SIMPLIFY_ARITHMETIC__SUBTRACT"
];
if (removeIfOneOfTheseForNow.includes(fixedMistakeType))
fixedMistakeType = "UNKNOWN";
const attemptedToGetTo = step.attemptedToGetTo || to;
const availableChangeTypes = arrayUtils.filterUniqueValues(step.availableChangeTypes);
const reachesOriginalAnswer = expressionEquals(stepEvaluationHelpers.getAnswerFromStep(to), startingStepAnswer);
const stepAsPartial = step;
if (step.isMistake)
return { ...stepAsPartial, isValid: false, reachesOriginalAnswer, from, to, attemptedToGetTo, attemptedChangeType, mistakenChangeType: fixedMistakeType, availableChangeTypes, allPossibleCorrectTos: step.allPossibleCorrectTos };
return { ...stepAsPartial, isValid: true, reachesOriginalAnswer, from, to, attemptedToGetTo, attemptedChangeType, mistakenChangeType: null, availableChangeTypes, allPossibleCorrectTos: step.allPossibleCorrectTos };
}
function processStepInfo(res, previousStep, userStep, startingStepAnswer) {
const history = res.history;
userStep = cleanString.cleanString(userStep);
previousStep = cleanString.cleanString(previousStep);
history.forEach((step) => {
step.to = cleanString.cleanString(step.to);
step.from = cleanString.cleanString(step.from);
});
return history.length === 0 ? processNoHistoryStep({ from: previousStep, to: userStep, startingStepAnswer, firstFoundNextStepOptions: res.firstFoundNextStepOptions }) : history.map((step) => processStep(step, previousStep, startingStepAnswer, history.length));
}
function assessUserStep(previousUserStep, userStep, startingStepAnswer = stepEvaluationHelpers.getAnswerFromStep(previousUserStep)) {
const rawAssessedStepOptionsRes = coreAssessUserStep([previousUserStep, userStep]);
return processStepInfo(rawAssessedStepOptionsRes, previousUserStep, userStep, startingStepAnswer);
}
function assessUserSteps(userSteps) {
if (userSteps.length === 0)
return [];
const assessedSteps = [];
let previousStep;
const startingStepAnswer = stepEvaluationHelpers.getAnswerFromStep(userSteps[0]);
for (const userStep of userSteps) {
if (!previousStep) {
previousStep = userStep;
continue;
}
const assessedStep = assessUserStep(previousStep, userStep, startingStepAnswer);
assessedSteps.push(assessedStep);
previousStep = userStep;
}
return assessedSteps;
}
exports.assessUserStep = assessUserStep;
exports.assessUserSteps = assessUserSteps;
exports.coreAssessUserStep = coreAssessUserStep;
exports.processNoHistoryStep = processNoHistoryStep;
exports.processStep = processStep;