UNPKG

mathsteps-experimental-fork

Version:

Step by step math solutions. Experimental Fork

270 lines (265 loc) 15 kB
'use strict'; Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); const expressionEqualsAndNormalization = require('../newServices/expressionEqualsAndNormalization.js'); const equationCache = require('./equationCache.js'); const stepEvaluationCore = require('./stepEvaluationCore.js'); const stepEvaluationHelpers = require('./stepEvaluationHelpers.js'); const changeAndMistakeUtils = require('../types/changeType/changeAndMistakeUtils.js'); const cleanString = require('../util/cleanString.js'); const expressionEquals = (exp0, exp1) => expressionEqualsAndNormalization.areExpressionEqual(exp0, exp1, equationCache.getValidStepEqCache()); function makeActionsValidOrInvalidBasedOnNonComplimentedAddOrRemove(leftRes, rightRes) { const leftChanges = leftRes.filter((step) => step.addedNumOp || step.removeNumberOp); const rightChanges = rightRes.filter((step) => step.addedNumOp || step.removeNumberOp); const largestLength = Math.max(leftChanges.length, rightChanges.length); for (let i = 0; i < largestLength; i++) { const getNumOp = (step) => step?.addedNumOp || step?.removeNumberOp; const leftChange = leftChanges?.[i]; const rightChange = rightChanges?.[i]; if (leftChange && !rightChange) { leftChange.isValid = false; continue; } if (rightChange && !leftChange) { rightChange.isValid = false; continue; } const leftNumOp = getNumOp(leftChange); const rightNumOp = getNumOp(rightChange); const isEqualFn = (lStepOp, rStepOp) => expressionEquals(lStepOp.number, rStepOp.number) && changeAndMistakeUtils.isOpEqual(lStepOp.op, rStepOp.op); let areChangesEqual = isEqualFn(leftNumOp, rightNumOp); if (!areChangesEqual) { const rStepOpNext = getNumOp(rightChanges?.[i + 1]); if (rStepOpNext) areChangesEqual = isEqualFn(leftNumOp, rightNumOp); } if (areChangesEqual) { leftChange.isValid = true; rightChange.isValid = true; } else { leftChange.isValid = false; rightChange.isValid = false; } } } function getEquationOperationsValidity(leftRes, rightRes) { const leftChange = leftRes.filter((step) => step.addedNumOp || step.removeNumberOp); const rightChange = rightRes.filter((step) => step.addedNumOp || step.removeNumberOp); const equationNumOpValidity = []; const largestLength = Math.max(leftChange.length, rightChange.length); for (let i = 0; i < largestLength; i++) { const getNumOp = (step) => step?.addedNumOp || step?.removeNumberOp; const lStepOp = getNumOp(leftChange?.[i]); const rStepOp = getNumOp(rightChange?.[i]); if (!lStepOp || !rStepOp) { equationNumOpValidity.push(false); continue; } const isEqualFn = (lStepOp2, rStepOp2) => expressionEquals(lStepOp2.number, rStepOp2.number) && changeAndMistakeUtils.isOpEqual(lStepOp2.op, rStepOp2.op); let areStepsEqual = isEqualFn(lStepOp, rStepOp); if (!areStepsEqual) { const rStepOpNext = getNumOp(rightChange?.[i + 1]); if (rStepOpNext) areStepsEqual = isEqualFn(lStepOp, rStepOpNext); } equationNumOpValidity.push(areStepsEqual); } return equationNumOpValidity; } function invalidProcedure(left, right, reachesOriginalAnswer) { const allAddedLeft = left.filter((step) => step.addedNumOp); const allRemovedLeft = left.filter((step) => step.removeNumberOp); const allAddedRight = right.filter((step) => step.addedNumOp); const allRemovedRight = right.filter((step) => step.removeNumberOp); const isRightRemovedFrom = allRemovedRight.length > 0; const isRightAddedTo = allAddedRight.length > 0; const isLeftAddedTo = allAddedLeft.length > 0; const isLeftRemovedFrom = allRemovedLeft.length > 0; const hasLeftAddedAndRightRemoved = allAddedLeft.length > 0 && allRemovedRight.length > 0; const hasRightAddedAndLeftRemoved = allAddedRight.length > 0 && allRemovedLeft.length > 0; function makeError(left2, right2, attemptedEquationChangeType, equationErrorType) { return { left: left2, right: right2, attemptedEquationChangeType, equationErrorType, reachesOriginalAnswer }; } function makeDefaultError(leftRes, rightRes, error) { return makeError(leftRes, rightRes, "EQ_ATMPT_OP_BOTH_SIDES", error); } if (isLeftRemovedFrom && isRightRemovedFrom) return makeDefaultError(left, right, "EQ_ATMPT_REMOVAL_BOTH_SIDES"); if (isLeftAddedTo && isRightAddedTo) return makeDefaultError(left, right, "EQ_ADDED_DIFF_TERMS_TO_BOTH_SIDES"); if (isLeftRemovedFrom && !isRightAddedTo) return makeDefaultError(left, right, "EQ_PLACED_LEFT_SIDE_ONLY"); if (isLeftAddedTo && !isRightRemovedFrom) return makeDefaultError(left, right, "EQ_PLACED_LEFT_SIDE_ONLY"); if (isRightRemovedFrom && !isLeftAddedTo) return makeDefaultError(left, right, "EQ_PLACED_RIGHT_SIDE_ONLY"); if (isRightAddedTo && !isLeftRemovedFrom) return makeDefaultError(left, right, "EQ_PLACED_RIGHT_SIDE_ONLY"); if (hasLeftAddedAndRightRemoved && allAddedLeft.length !== allRemovedRight.length || hasRightAddedAndLeftRemoved && allAddedRight.length !== allRemovedLeft.length) { return makeDefaultError(left, right, "EQ_NOT_SAME_OP_PERFORMED"); } else if (hasLeftAddedAndRightRemoved || hasRightAddedAndLeftRemoved) { const getHasInvalidEquationOp = (step) => !step.isValid && (step.addedNumOp || step.removeNumberOp); if (left.some((step) => getHasInvalidEquationOp(step)) || right.some((step) => getHasInvalidEquationOp(step))) { return makeDefaultError(left, right, "EQ_NOT_SAME_OP_PERFORMED"); } } return null; } function processEquationInfo(equation, previousStep, userStep, startingStepAnswer, startingStepAnswerForEquation) { const leftFrom = cleanString.cleanString(previousStep.split("=")[0]); const rightFrom = cleanString.cleanString(previousStep.split("=")[1]); const leftTo = cleanString.cleanString(userStep.split("=")[0]); const rightTo = cleanString.cleanString(userStep.split("=")[1]); const reachesOriginalAnswer = (() => { const leftOgAnswer = startingStepAnswerForEquation.split("=")[0]; const rightOgAnswer = startingStepAnswerForEquation.split("=")[1]; const leftNewAnswer = stepEvaluationHelpers.getAnswerFromEquation(userStep).split("=")[0]; const rightNewAnswer = stepEvaluationHelpers.getAnswerFromEquation(userStep).split("=")[1]; const normalIsEqual = expressionEquals(leftOgAnswer, leftNewAnswer) && expressionEquals(rightOgAnswer, rightNewAnswer); const flippedIsEqual = expressionEquals(leftOgAnswer, rightNewAnswer) && expressionEquals(rightOgAnswer, leftNewAnswer); return normalIsEqual || flippedIsEqual; })(); if (equation.equationChangeType === "EQ_SWAP_SIDES") { const leftHistory = equation.lhs.history.map((step) => stepEvaluationCore.processStep(step, leftFrom, startingStepAnswer, equation.lhs.history.length)); const rightHistory = equation.rhs.history.map((step) => stepEvaluationCore.processStep(step, rightFrom, startingStepAnswer, equation.rhs.history.length)); leftHistory[0].attemptedChangeType = equation.equationChangeType; leftHistory[0].equationActionType = equation.equationChangeType; rightHistory[0].attemptedChangeType = equation.equationChangeType; rightHistory[0].equationActionType = equation.equationChangeType; leftHistory[0].isValid = true; rightHistory[0].isValid = true; return { left: leftHistory, right: rightHistory, attemptedEquationChangeType: equation.equationChangeType, reachesOriginalAnswer }; } const rightRes = equation.rhs.history.length === 0 || equation.equationChangeType === "EQ_NO_CHANGE" || equation.equationChangeType === "EQ_SIMPLIFY_LHS" ? stepEvaluationCore.processNoHistoryStep({ from: rightFrom, to: rightTo, startingStepAnswer, attemptedToGetTo: "UNKNOWN", attemptedChangeType: "UNKNOWN", firstFoundNextStepOptions: equation.rhs.firstFoundNextStepOptions }) : equation.rhs.history.map((step) => stepEvaluationCore.processStep(step, rightFrom, startingStepAnswer, equation.rhs.history.length)); const leftRes = equation.lhs.history.length === 0 || equation.equationChangeType === "EQ_NO_CHANGE" || equation.equationChangeType === "EQ_SIMPLIFY_RHS" ? stepEvaluationCore.processNoHistoryStep({ from: leftFrom, to: leftTo, startingStepAnswer, attemptedToGetTo: "UNKNOWN", attemptedChangeType: "UNKNOWN", firstFoundNextStepOptions: equation.rhs.firstFoundNextStepOptions }) : equation.lhs.history.map((step) => stepEvaluationCore.processStep(step, leftFrom, startingStepAnswer, equation.lhs.history.length)); if (equation.equationChangeType === "EQ_SIMPLIFY_BOTH" || equation.equationChangeType === "EQ_SIMPLIFY_LHS" || equation.equationChangeType === "EQ_SIMPLIFY_RHS") { return { left: leftRes, right: rightRes, attemptedEquationChangeType: equation.equationChangeType, reachesOriginalAnswer }; } const isValidEquationOperations = getEquationOperationsValidity(leftRes, rightRes).every((val) => val); makeActionsValidOrInvalidBasedOnNonComplimentedAddOrRemove(leftRes, rightRes); if (!isValidEquationOperations) { const invalidRes = invalidProcedure(leftRes, rightRes, reachesOriginalAnswer); if (invalidRes) return invalidRes; else throw new Error("Invalid procedure did not return anything"); } return { left: leftRes, right: rightRes, attemptedEquationChangeType: equation.equationChangeType || "EQ_ATMPT_OP_BOTH_SIDES", reachesOriginalAnswer }; } function assessUserEquationStep(previousUserStep, userStep, startingStepAnswerForEquation) { const rawAssessedStepOptionsRes = coreAssessUserStepEquation([previousUserStep, userStep]); return processEquationInfo(rawAssessedStepOptionsRes, previousUserStep, userStep, null, startingStepAnswerForEquation); } function assessUserEquationSteps(userSteps) { if (userSteps.length === 0) return []; const assessedSteps = []; let previousStep; const startingStepAnswerForEquation = stepEvaluationHelpers.getAnswerFromEquation(userSteps[0]); for (const userStep of userSteps) { if (!previousStep) { previousStep = userStep; continue; } const assessedStep = assessUserEquationStep(previousStep, userStep, startingStepAnswerForEquation); assessedSteps.push(assessedStep); previousStep = userStep; } return assessedSteps; } function coreAssessUserStepEquation([previousEquationStr, userEquationStr]) { const previousEquation = { lhs: cleanString.cleanString(previousEquationStr.split("=")[0]), rhs: cleanString.cleanString(previousEquationStr.split("=")[1]) }; const userEquation = { lhs: cleanString.cleanString(userEquationStr.split("=")[0]), rhs: cleanString.cleanString(userEquationStr.split("=")[1]) }; const lhsChanged = !expressionEquals(previousEquation.lhs, userEquation.lhs); const lhsUnchanged = !lhsChanged; const rhsChanged = !expressionEquals(previousEquation.rhs, userEquation.rhs); const rhsUnchanged = !rhsChanged; const lhsOnlyChanged = lhsChanged && !rhsChanged; const rhsOnlyChanged = rhsChanged && !lhsChanged; const lhsRes = lhsOnlyChanged ? stepEvaluationCore.coreAssessUserStep([previousEquation.lhs, userEquation.lhs]) : stepEvaluationCore.coreAssessUserStep([previousEquation.lhs, userEquation.lhs], previousEquation.rhs); const rhsRes = rhsOnlyChanged ? stepEvaluationCore.coreAssessUserStep([previousEquation.rhs, userEquation.rhs]) : stepEvaluationCore.coreAssessUserStep([previousEquation.rhs, userEquation.rhs], previousEquation.lhs); if (lhsUnchanged && rhsUnchanged) { return { lhs: lhsRes, rhs: rhsRes, equationChangeType: "EQ_NO_CHANGE" }; } if (lhsChanged && rhsChanged && expressionEquals(previousEquation.lhs, userEquation.rhs) && expressionEquals(previousEquation.rhs, userEquation.lhs)) { return { lhs: { history: [ { to: previousEquation.rhs, attemptedToGetTo: previousEquation.rhs, from: previousEquation.lhs, changeType: "EQ_SWAP_SIDES", attemptedChangeType: "EQ_SWAP_SIDES", isMistake: false, availableChangeTypes: lhsRes.firstFoundNextStepOptions?.[0]?.availableChangeTypes || [], allPossibleCorrectTos: lhsRes.firstFoundNextStepOptions.map((step) => step.to) } ], firstFoundNextStepOptions: lhsRes.firstFoundNextStepOptions }, rhs: { history: [ { to: previousEquation.lhs, attemptedToGetTo: previousEquation.lhs, from: previousEquation.rhs, changeType: "EQ_SWAP_SIDES", attemptedChangeType: "EQ_SWAP_SIDES", isMistake: false, availableChangeTypes: rhsRes.firstFoundNextStepOptions?.[0]?.availableChangeTypes || [], allPossibleCorrectTos: rhsRes.firstFoundNextStepOptions.map((step) => step.to) } ], firstFoundNextStepOptions: rhsRes.firstFoundNextStepOptions }, equationChangeType: "EQ_SWAP_SIDES" }; } if (lhsOnlyChanged) { return { lhs: lhsRes, rhs: rhsRes, equationChangeType: "EQ_SIMPLIFY_LHS" }; } else if (rhsOnlyChanged) { return { lhs: lhsRes, rhs: rhsRes, equationChangeType: "EQ_SIMPLIFY_RHS" }; } else { const hasMultiplyBothSidesByNegativeOneLeft = lhsRes.history.some((step) => step.changeType === "EQ_MULTIPLY_BOTH_SIDES_BY_NEGATIVE_ONE"); const hasMultiplyBothSidesByNegativeOneRight = rhsRes.history.some((step) => step.changeType === "EQ_MULTIPLY_BOTH_SIDES_BY_NEGATIVE_ONE"); if (hasMultiplyBothSidesByNegativeOneLeft || hasMultiplyBothSidesByNegativeOneRight) { return { lhs: lhsRes, rhs: rhsRes, equationChangeType: "EQ_MULTIPLY_BOTH_SIDES_BY_NEGATIVE_ONE" }; } const hasCrossMultiplyLeft = lhsRes.history.some((step) => step.changeType === "EQ_CROSS_MULTIPLY"); const hasCrossMultiplyRight = rhsRes.history.some((step) => step.changeType === "EQ_CROSS_MULTIPLY"); if (hasCrossMultiplyLeft || hasCrossMultiplyRight) { return { lhs: lhsRes, rhs: rhsRes, equationChangeType: "EQ_CROSS_MULTIPLY" }; } const hasAddedTerms = rhsRes.history.some((step) => changeAndMistakeUtils.isAddTermChangeType(step.changeType)) || lhsRes.history.some((step) => changeAndMistakeUtils.isAddTermChangeType(step.changeType)); const hasRemovedTerms = rhsRes.history.some((step) => changeAndMistakeUtils.isRemoveTermChangeType(step.changeType)) || lhsRes.history.some((step) => changeAndMistakeUtils.isRemoveTermChangeType(step.changeType)); if (hasAddedTerms || hasRemovedTerms) { return { lhs: lhsRes, rhs: rhsRes, equationChangeType: void 0 // TBD later }; } else { return { lhs: lhsRes, rhs: rhsRes, equationChangeType: "EQ_SIMPLIFY_BOTH" }; } } } exports.assessUserEquationStep = assessUserEquationStep; exports.assessUserEquationSteps = assessUserEquationSteps; exports.coreAssessUserStepEquation = coreAssessUserStepEquation;