UNPKG

mathsteps-experimental-fork

Version:

Step by step math solutions. Experimental Fork

255 lines (250 loc) 12.2 kB
'use strict'; 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;