mathsteps-experimental-fork
Version:
Step by step math solutions. Experimental Fork
270 lines (265 loc) • 15 kB
JavaScript
;
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;