괄호 안의 항을 2, 3, 4 항으로 설정하는 기능 추가
This commit is contained in:
@@ -63,8 +63,19 @@ describe("parenthesis sign problem engine", () => {
|
||||
expect(keys.size).toBe(10);
|
||||
});
|
||||
|
||||
it("generates the selected number of terms inside parentheses", () => {
|
||||
const problem = generateProblem(4);
|
||||
|
||||
expect(problem.terms).toHaveLength(4);
|
||||
expect(problem.operators).toHaveLength(3);
|
||||
expect(problem.distributedTerms).toHaveLength(4);
|
||||
expect(
|
||||
gradeAnswer(problem, { terms: problem.distributedTerms }).correct,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("rejects impossible session sizes", () => {
|
||||
expect(() => generateSession(0)).toThrow();
|
||||
expect(() => generateSession(145)).toThrow();
|
||||
expect(() => generateSession(1297)).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
+150
-39
@@ -8,61 +8,138 @@ const MIN_MULTIPLIER = -9;
|
||||
const MAX_MULTIPLIER = -1;
|
||||
const MIN_TERM = 1;
|
||||
const MAX_TERM = 9;
|
||||
const MAX_PROBLEM_COUNT = 9 * 8 * 2;
|
||||
const MAX_TERM_COUNT = 4;
|
||||
|
||||
type ProblemSeed = Pick<
|
||||
ParenthesisSignProblem,
|
||||
"multiplier" | "a" | "operator" | "b"
|
||||
>;
|
||||
export type ParenthesisTermCount = 2 | 3 | 4;
|
||||
|
||||
interface ProblemSeed {
|
||||
multiplier: number;
|
||||
terms: number[];
|
||||
operators: ("+" | "-")[];
|
||||
}
|
||||
|
||||
function randomInt(min: number, max: number) {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
}
|
||||
|
||||
function formatLatexTermPair(first: number, second: number) {
|
||||
const operator = second < 0 ? "-" : "+";
|
||||
function formatLatexTerms(terms: number[]) {
|
||||
return terms
|
||||
.map((term, index) => {
|
||||
if (index === 0) return `${term}`;
|
||||
|
||||
return `${first} ${operator} ${Math.abs(second)}`;
|
||||
const operator = term < 0 ? "-" : "+";
|
||||
return `${operator} ${Math.abs(term)}`;
|
||||
})
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function formatParenthesisTerms(
|
||||
terms: number[],
|
||||
operators: ("+" | "-")[],
|
||||
) {
|
||||
return terms
|
||||
.map((term, index) => {
|
||||
if (index === 0) return `${term}`;
|
||||
|
||||
return `${operators[index - 1]} ${term}`;
|
||||
})
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function createProblem({
|
||||
multiplier,
|
||||
a,
|
||||
operator,
|
||||
b,
|
||||
terms,
|
||||
operators,
|
||||
}: ProblemSeed): ParenthesisSignProblem {
|
||||
const distributedA = multiplier * a;
|
||||
const distributedB = operator === "-" ? multiplier * -b : multiplier * b;
|
||||
const key = `${multiplier}:${a}:${operator}:${b}`;
|
||||
const signedTerms = terms.map((term, index) => {
|
||||
if (index === 0) return term;
|
||||
|
||||
return operators[index - 1] === "-" ? -term : term;
|
||||
});
|
||||
const distributedTerms = signedTerms.map((term) => multiplier * term);
|
||||
const [a, b] = terms;
|
||||
const [operator] = operators;
|
||||
const [distributedA, distributedB] = distributedTerms;
|
||||
const key = [
|
||||
multiplier,
|
||||
...terms.flatMap((term, index) =>
|
||||
index === 0 ? [`${term}`] : [operators[index - 1], `${term}`],
|
||||
),
|
||||
].join(":");
|
||||
|
||||
return {
|
||||
id: `parenthesis-sign:${key}`,
|
||||
key,
|
||||
multiplier,
|
||||
terms,
|
||||
operators,
|
||||
a,
|
||||
operator,
|
||||
b,
|
||||
distributedTerms,
|
||||
distributedA,
|
||||
distributedB,
|
||||
latexBefore: `${multiplier}\\left(${a} ${operator} ${b}\\right)`,
|
||||
latexAfter: formatLatexTermPair(distributedA, distributedB),
|
||||
latexBefore: `${multiplier}\\left(${formatParenthesisTerms(
|
||||
terms,
|
||||
operators,
|
||||
)}\\right)`,
|
||||
latexAfter: formatLatexTerms(distributedTerms),
|
||||
};
|
||||
}
|
||||
|
||||
function createAllProblemSeeds() {
|
||||
function createTermCombinations(termCount: ParenthesisTermCount) {
|
||||
const combinations: number[][] = [];
|
||||
|
||||
function addTerms(nextTerms: number[]) {
|
||||
if (nextTerms.length === termCount) {
|
||||
combinations.push(nextTerms);
|
||||
return;
|
||||
}
|
||||
|
||||
for (let term = MIN_TERM; term <= MAX_TERM; term += 1) {
|
||||
if (nextTerms.includes(term)) continue;
|
||||
|
||||
addTerms([...nextTerms, term]);
|
||||
}
|
||||
}
|
||||
|
||||
addTerms([]);
|
||||
|
||||
return combinations;
|
||||
}
|
||||
|
||||
function createOperatorCombinations(termCount: ParenthesisTermCount) {
|
||||
const combinations: ("+" | "-")[][] = [];
|
||||
const operatorCount = termCount - 1;
|
||||
|
||||
function addOperators(nextOperators: ("+" | "-")[]) {
|
||||
if (nextOperators.length === operatorCount) {
|
||||
combinations.push(nextOperators);
|
||||
return;
|
||||
}
|
||||
|
||||
addOperators([...nextOperators, "+"]);
|
||||
addOperators([...nextOperators, "-"]);
|
||||
}
|
||||
|
||||
addOperators([]);
|
||||
|
||||
return combinations;
|
||||
}
|
||||
|
||||
function createAllProblemSeeds(termCount: ParenthesisTermCount) {
|
||||
const seeds: ProblemSeed[] = [];
|
||||
const termCombinations = createTermCombinations(termCount);
|
||||
const operatorCombinations = createOperatorCombinations(termCount);
|
||||
|
||||
for (
|
||||
let multiplier = MIN_MULTIPLIER;
|
||||
multiplier <= MAX_MULTIPLIER;
|
||||
multiplier += 1
|
||||
) {
|
||||
for (let a = MIN_TERM; a <= MAX_TERM; a += 1) {
|
||||
for (let b = MIN_TERM; b <= MAX_TERM; b += 1) {
|
||||
if (a === b) continue;
|
||||
|
||||
seeds.push({ multiplier, a, operator: "+", b });
|
||||
seeds.push({ multiplier, a, operator: "-", b });
|
||||
for (const terms of termCombinations) {
|
||||
for (const operators of operatorCombinations) {
|
||||
seeds.push({ multiplier, terms, operators });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -70,7 +147,14 @@ function createAllProblemSeeds() {
|
||||
return seeds;
|
||||
}
|
||||
|
||||
const ALL_PROBLEM_SEEDS = createAllProblemSeeds();
|
||||
const ALL_PROBLEM_SEEDS_BY_TERM_COUNT: Record<
|
||||
ParenthesisTermCount,
|
||||
ProblemSeed[]
|
||||
> = {
|
||||
2: createAllProblemSeeds(2),
|
||||
3: createAllProblemSeeds(3),
|
||||
4: createAllProblemSeeds(4),
|
||||
};
|
||||
|
||||
function shuffle<T>(items: T[]) {
|
||||
const shuffled = [...items];
|
||||
@@ -85,42 +169,69 @@ function shuffle<T>(items: T[]) {
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
export function generateProblem(): ParenthesisSignProblem {
|
||||
const a = randomInt(MIN_TERM, MAX_TERM);
|
||||
let b = randomInt(MIN_TERM, MAX_TERM);
|
||||
function assertTermCount(termCount: number): asserts termCount is ParenthesisTermCount {
|
||||
if (!Number.isInteger(termCount) || termCount < 2 || termCount > MAX_TERM_COUNT) {
|
||||
throw new Error("termCount must be 2, 3, or 4.");
|
||||
}
|
||||
}
|
||||
|
||||
while (a === b) {
|
||||
b = randomInt(MIN_TERM, MAX_TERM);
|
||||
export function generateProblem(
|
||||
termCount: ParenthesisTermCount = 2,
|
||||
): ParenthesisSignProblem {
|
||||
assertTermCount(termCount);
|
||||
|
||||
const terms: number[] = [];
|
||||
|
||||
while (terms.length < termCount) {
|
||||
const term = randomInt(MIN_TERM, MAX_TERM);
|
||||
|
||||
if (!terms.includes(term)) {
|
||||
terms.push(term);
|
||||
}
|
||||
}
|
||||
|
||||
return createProblem({
|
||||
multiplier: randomInt(MIN_MULTIPLIER, MAX_MULTIPLIER),
|
||||
a,
|
||||
operator: Math.random() < 0.5 ? "+" : "-",
|
||||
b,
|
||||
terms,
|
||||
operators: Array.from({ length: termCount - 1 }, () =>
|
||||
Math.random() < 0.5 ? "+" : "-",
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
export function generateSession(count = 10): ParenthesisSignProblem[] {
|
||||
export function generateSession(
|
||||
count = 10,
|
||||
termCount: ParenthesisTermCount = 2,
|
||||
): ParenthesisSignProblem[] {
|
||||
if (!Number.isInteger(count) || count < 1) {
|
||||
throw new Error("count must be a positive integer.");
|
||||
}
|
||||
|
||||
if (count > MAX_PROBLEM_COUNT) {
|
||||
throw new Error(`count cannot exceed ${MAX_PROBLEM_COUNT}.`);
|
||||
assertTermCount(termCount);
|
||||
|
||||
const seeds = ALL_PROBLEM_SEEDS_BY_TERM_COUNT[termCount];
|
||||
|
||||
if (count > seeds.length) {
|
||||
throw new Error(`count cannot exceed ${seeds.length}.`);
|
||||
}
|
||||
|
||||
return shuffle(ALL_PROBLEM_SEEDS).slice(0, count).map(createProblem);
|
||||
return shuffle(seeds).slice(0, count).map(createProblem);
|
||||
}
|
||||
|
||||
export function gradeAnswer(
|
||||
problem: ParenthesisSignProblem,
|
||||
answer: UserAnswer,
|
||||
): GradeResult {
|
||||
const userAnswer =
|
||||
answer.terms ?? (answer.a !== undefined && answer.b !== undefined
|
||||
? [answer.a, answer.b]
|
||||
: []);
|
||||
|
||||
return {
|
||||
correct:
|
||||
answer.a === problem.distributedA && answer.b === problem.distributedB,
|
||||
userAnswer: [answer.a, answer.b],
|
||||
correctAnswer: [problem.distributedA, problem.distributedB],
|
||||
userAnswer.length === problem.distributedTerms.length &&
|
||||
userAnswer.every((value, index) => value === problem.distributedTerms[index]),
|
||||
userAnswer,
|
||||
correctAnswer: problem.distributedTerms,
|
||||
};
|
||||
}
|
||||
|
||||
+8
-4
@@ -2,9 +2,12 @@ export interface ParenthesisSignProblem {
|
||||
id: string;
|
||||
key: string;
|
||||
multiplier: number;
|
||||
terms: number[];
|
||||
operators: ("+" | "-")[];
|
||||
a: number;
|
||||
operator: "+" | "-";
|
||||
b: number;
|
||||
distributedTerms: number[];
|
||||
distributedA: number;
|
||||
distributedB: number;
|
||||
latexBefore: string;
|
||||
@@ -12,12 +15,13 @@ export interface ParenthesisSignProblem {
|
||||
}
|
||||
|
||||
export interface UserAnswer {
|
||||
a: number;
|
||||
b: number;
|
||||
terms?: number[];
|
||||
a?: number;
|
||||
b?: number;
|
||||
}
|
||||
|
||||
export interface GradeResult {
|
||||
correct: boolean;
|
||||
userAnswer: [number, number];
|
||||
correctAnswer: [number, number];
|
||||
userAnswer: number[];
|
||||
correctAnswer: number[];
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import type { VariableParenthesisProblem } from "./variable-types";
|
||||
|
||||
function findCorrectChoiceIds(problem: VariableParenthesisProblem) {
|
||||
return problem.correctTerms.map((term, index) => {
|
||||
const choices = problem.choices[index as 0 | 1];
|
||||
const choices = problem.choices[index];
|
||||
const choice = choices.find(
|
||||
(item) =>
|
||||
item.coefficient === term.coefficient && item.variable === term.variable,
|
||||
@@ -19,7 +19,7 @@ function findCorrectChoiceIds(problem: VariableParenthesisProblem) {
|
||||
if (!choice) throw new Error("correct choice not found.");
|
||||
|
||||
return choice.id;
|
||||
}) as [string, string];
|
||||
});
|
||||
}
|
||||
|
||||
describe("variable parenthesis engine", () => {
|
||||
@@ -97,6 +97,21 @@ describe("variable parenthesis engine", () => {
|
||||
expect(keys.size).toBe(10);
|
||||
});
|
||||
|
||||
it("generates the selected number of variable choices", () => {
|
||||
const problem = generateVariableParenthesisProblem(4);
|
||||
|
||||
expect(problem.terms).toHaveLength(4);
|
||||
expect(problem.operators).toHaveLength(3);
|
||||
expect(problem.correctTerms).toHaveLength(4);
|
||||
expect(problem.choices).toHaveLength(4);
|
||||
expect(problem.terms.filter((term) => term.kind === "variable")).toHaveLength(1);
|
||||
expect(
|
||||
gradeVariableParenthesisAnswer(problem, {
|
||||
selectedChoiceIds: findCorrectChoiceIds(problem),
|
||||
}).correct,
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it("formats expressions with variable terms", () => {
|
||||
expect(
|
||||
formatVariableExpressionLatex([
|
||||
|
||||
@@ -11,13 +11,14 @@ const MIN_MULTIPLIER = -9;
|
||||
const MAX_MULTIPLIER = -1;
|
||||
const MIN_COEFFICIENT = 1;
|
||||
const MAX_COEFFICIENT = 9;
|
||||
const MAX_PROBLEM_COUNT = 9 * 9 * 9 * 2 * 2;
|
||||
const MAX_TERM_COUNT = 4;
|
||||
|
||||
export type VariableParenthesisTermCount = 2 | 3 | 4;
|
||||
|
||||
interface VariableProblemSeed {
|
||||
multiplier: number;
|
||||
operator: "+" | "-";
|
||||
firstTerm: VariableParenthesisTerm;
|
||||
secondTerm: VariableParenthesisTerm;
|
||||
terms: VariableParenthesisTerm[];
|
||||
operators: ("+" | "-")[];
|
||||
}
|
||||
|
||||
function randomInt(min: number, max: number) {
|
||||
@@ -52,6 +53,26 @@ function createVariableTerm(coefficient: number): VariableParenthesisTerm {
|
||||
};
|
||||
}
|
||||
|
||||
function createUniqueConstantTerms(
|
||||
baseCoefficient: number,
|
||||
count: number,
|
||||
): VariableParenthesisTerm[] {
|
||||
const used = new Set<number>();
|
||||
const terms: VariableParenthesisTerm[] = [];
|
||||
let candidate = baseCoefficient;
|
||||
|
||||
while (terms.length < count) {
|
||||
const coef = ((candidate - 1) % MAX_COEFFICIENT) + 1;
|
||||
if (!used.has(coef)) {
|
||||
used.add(coef);
|
||||
terms.push(createConstantTerm(coef));
|
||||
}
|
||||
candidate += 1;
|
||||
}
|
||||
|
||||
return terms;
|
||||
}
|
||||
|
||||
function termKey(term: VariableParenthesisTerm) {
|
||||
return `${term.kind}:${term.coefficient}`;
|
||||
}
|
||||
@@ -79,18 +100,21 @@ function formatParenthesisTermLatex(term: VariableParenthesisTerm) {
|
||||
}
|
||||
|
||||
export function formatVariableExpressionLatex(
|
||||
terms: [DistributedVariableTerm, DistributedVariableTerm],
|
||||
terms: DistributedVariableTerm[],
|
||||
) {
|
||||
const [firstTerm, secondTerm] = terms;
|
||||
const operator = secondTerm.coefficient < 0 ? "-" : "+";
|
||||
const positiveSecondTerm = {
|
||||
...secondTerm,
|
||||
coefficient: Math.abs(secondTerm.coefficient),
|
||||
};
|
||||
return terms
|
||||
.map((term, index) => {
|
||||
if (index === 0) return term.latex;
|
||||
|
||||
return `${firstTerm.latex} ${operator} ${formatVariableTermLatex(
|
||||
positiveSecondTerm,
|
||||
)}`;
|
||||
const operator = term.coefficient < 0 ? "-" : "+";
|
||||
const positiveTerm = {
|
||||
...term,
|
||||
coefficient: Math.abs(term.coefficient),
|
||||
};
|
||||
|
||||
return `${operator} ${formatVariableTermLatex(positiveTerm)}`;
|
||||
})
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function distributeTerm(
|
||||
@@ -110,7 +134,7 @@ function distributeTerm(
|
||||
|
||||
function createChoice(
|
||||
key: string,
|
||||
termIndex: 0 | 1,
|
||||
termIndex: number,
|
||||
term: DistributedVariableTerm,
|
||||
coefficient: number,
|
||||
): VariableTermChoice {
|
||||
@@ -126,7 +150,7 @@ function createChoice(
|
||||
|
||||
function createChoices(
|
||||
key: string,
|
||||
termIndex: 0 | 1,
|
||||
termIndex: number,
|
||||
term: DistributedVariableTerm,
|
||||
) {
|
||||
const absoluteCoefficient = Math.abs(term.coefficient);
|
||||
@@ -139,40 +163,69 @@ function createChoices(
|
||||
}
|
||||
|
||||
function createProblem(seed: VariableProblemSeed): VariableParenthesisProblem {
|
||||
const { multiplier, operator, firstTerm, secondTerm } = seed;
|
||||
const { multiplier, terms, operators } = seed;
|
||||
const [firstTerm, secondTerm] = terms;
|
||||
const [operator] = operators;
|
||||
const key = [
|
||||
multiplier,
|
||||
termKey(firstTerm),
|
||||
operator,
|
||||
termKey(secondTerm),
|
||||
...terms.flatMap((term, index) =>
|
||||
index === 0 ? [termKey(term)] : [operators[index - 1], termKey(term)],
|
||||
),
|
||||
].join(":");
|
||||
const correctTerms: [DistributedVariableTerm, DistributedVariableTerm] = [
|
||||
distributeTerm(multiplier, firstTerm, 1),
|
||||
distributeTerm(multiplier, secondTerm, operator === "-" ? -1 : 1),
|
||||
];
|
||||
const choices: [VariableTermChoice[], VariableTermChoice[]] = [
|
||||
createChoices(key, 0, correctTerms[0]),
|
||||
createChoices(key, 1, correctTerms[1]),
|
||||
];
|
||||
const correctTerms = terms.map((term, index) =>
|
||||
distributeTerm(
|
||||
multiplier,
|
||||
term,
|
||||
index === 0 || operators[index - 1] === "+" ? 1 : -1,
|
||||
),
|
||||
);
|
||||
const choices = correctTerms.map((term, index) =>
|
||||
createChoices(key, index, term),
|
||||
);
|
||||
|
||||
return {
|
||||
id: `variable-parenthesis:${key}`,
|
||||
key,
|
||||
multiplier,
|
||||
terms,
|
||||
operators,
|
||||
operator,
|
||||
firstTerm,
|
||||
secondTerm,
|
||||
correctTerms,
|
||||
choices,
|
||||
latexBefore: `${multiplier}\\left(${formatParenthesisTermLatex(
|
||||
firstTerm,
|
||||
)} ${operator} ${formatParenthesisTermLatex(secondTerm)}\\right)`,
|
||||
latexBefore: `${multiplier}\\left(${terms
|
||||
.map((term, index) => {
|
||||
if (index === 0) return formatParenthesisTermLatex(term);
|
||||
|
||||
return `${operators[index - 1]} ${formatParenthesisTermLatex(term)}`;
|
||||
})
|
||||
.join(" ")}\\right)`,
|
||||
latexAfter: formatVariableExpressionLatex(correctTerms),
|
||||
};
|
||||
}
|
||||
|
||||
function createAllProblemSeeds() {
|
||||
function createOperatorCombinations(termCount: VariableParenthesisTermCount) {
|
||||
const combinations: ("+" | "-")[][] = [];
|
||||
|
||||
function addOperators(nextOperators: ("+" | "-")[]) {
|
||||
if (nextOperators.length === termCount - 1) {
|
||||
combinations.push(nextOperators);
|
||||
return;
|
||||
}
|
||||
|
||||
addOperators([...nextOperators, "+"]);
|
||||
addOperators([...nextOperators, "-"]);
|
||||
}
|
||||
|
||||
addOperators([]);
|
||||
|
||||
return combinations;
|
||||
}
|
||||
|
||||
function createAllProblemSeeds(termCount: VariableParenthesisTermCount) {
|
||||
const seeds: VariableProblemSeed[] = [];
|
||||
const operatorCombinations = createOperatorCombinations(termCount);
|
||||
|
||||
for (
|
||||
let multiplier = MIN_MULTIPLIER;
|
||||
@@ -189,19 +242,22 @@ function createAllProblemSeeds() {
|
||||
variableCoefficient <= MAX_COEFFICIENT;
|
||||
variableCoefficient += 1
|
||||
) {
|
||||
for (const operator of ["+", "-"] as const) {
|
||||
seeds.push({
|
||||
multiplier,
|
||||
operator,
|
||||
firstTerm: createVariableTerm(variableCoefficient),
|
||||
secondTerm: createConstantTerm(constant),
|
||||
});
|
||||
seeds.push({
|
||||
multiplier,
|
||||
operator,
|
||||
firstTerm: createConstantTerm(constant),
|
||||
secondTerm: createVariableTerm(variableCoefficient),
|
||||
});
|
||||
for (let variableIndex = 0; variableIndex < termCount; variableIndex += 1) {
|
||||
const constantTerms = createUniqueConstantTerms(constant, termCount - 1);
|
||||
let constantTermIndex = 0;
|
||||
const terms = Array.from({ length: termCount }, (_, index) =>
|
||||
index === variableIndex
|
||||
? createVariableTerm(variableCoefficient)
|
||||
: constantTerms[constantTermIndex++],
|
||||
);
|
||||
|
||||
for (const operators of operatorCombinations) {
|
||||
seeds.push({
|
||||
multiplier,
|
||||
terms,
|
||||
operators,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -210,43 +266,70 @@ function createAllProblemSeeds() {
|
||||
return seeds;
|
||||
}
|
||||
|
||||
const ALL_PROBLEM_SEEDS = createAllProblemSeeds();
|
||||
const ALL_PROBLEM_SEEDS_BY_TERM_COUNT: Record<
|
||||
VariableParenthesisTermCount,
|
||||
VariableProblemSeed[]
|
||||
> = {
|
||||
2: createAllProblemSeeds(2),
|
||||
3: createAllProblemSeeds(3),
|
||||
4: createAllProblemSeeds(4),
|
||||
};
|
||||
|
||||
function assertTermCount(
|
||||
termCount: number,
|
||||
): asserts termCount is VariableParenthesisTermCount {
|
||||
if (!Number.isInteger(termCount) || termCount < 2 || termCount > MAX_TERM_COUNT) {
|
||||
throw new Error("termCount must be 2, 3, or 4.");
|
||||
}
|
||||
}
|
||||
|
||||
export function generateVariableParenthesisProblem(
|
||||
termCount: VariableParenthesisTermCount = 2,
|
||||
): VariableParenthesisProblem {
|
||||
assertTermCount(termCount);
|
||||
|
||||
export function generateVariableParenthesisProblem(): VariableParenthesisProblem {
|
||||
const constant = randomInt(MIN_COEFFICIENT, MAX_COEFFICIENT);
|
||||
const variableCoefficient = randomInt(MIN_COEFFICIENT, MAX_COEFFICIENT);
|
||||
const variableFirst = Math.random() < 0.5;
|
||||
const operator = Math.random() < 0.5 ? "+" : "-";
|
||||
const variableIndex = randomInt(0, termCount - 1);
|
||||
|
||||
const constantTerms = createUniqueConstantTerms(constant, termCount - 1);
|
||||
let constantTermIndex = 0;
|
||||
|
||||
return createProblem({
|
||||
multiplier: randomInt(MIN_MULTIPLIER, MAX_MULTIPLIER),
|
||||
operator,
|
||||
firstTerm: variableFirst
|
||||
? createVariableTerm(variableCoefficient)
|
||||
: createConstantTerm(constant),
|
||||
secondTerm: variableFirst
|
||||
? createConstantTerm(constant)
|
||||
: createVariableTerm(variableCoefficient),
|
||||
terms: Array.from({ length: termCount }, (_, index) =>
|
||||
index === variableIndex
|
||||
? createVariableTerm(variableCoefficient)
|
||||
: constantTerms[constantTermIndex++],
|
||||
),
|
||||
operators: Array.from({ length: termCount - 1 }, () =>
|
||||
Math.random() < 0.5 ? "+" : "-",
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
export function generateVariableParenthesisSession(
|
||||
count = 10,
|
||||
termCount: VariableParenthesisTermCount = 2,
|
||||
): VariableParenthesisProblem[] {
|
||||
if (!Number.isInteger(count) || count < 1) {
|
||||
throw new Error("count must be a positive integer.");
|
||||
}
|
||||
|
||||
if (count > MAX_PROBLEM_COUNT) {
|
||||
throw new Error(`count cannot exceed ${MAX_PROBLEM_COUNT}.`);
|
||||
assertTermCount(termCount);
|
||||
|
||||
const seeds = ALL_PROBLEM_SEEDS_BY_TERM_COUNT[termCount];
|
||||
|
||||
if (count > seeds.length) {
|
||||
throw new Error(`count cannot exceed ${seeds.length}.`);
|
||||
}
|
||||
|
||||
return shuffle(ALL_PROBLEM_SEEDS).slice(0, count).map(createProblem);
|
||||
return shuffle(seeds).slice(0, count).map(createProblem);
|
||||
}
|
||||
|
||||
function findSelectedChoice(
|
||||
problem: VariableParenthesisProblem,
|
||||
termIndex: 0 | 1,
|
||||
termIndex: number,
|
||||
choiceId: string | null,
|
||||
): DistributedVariableTerm | null {
|
||||
if (!choiceId) return null;
|
||||
@@ -267,16 +350,14 @@ export function gradeVariableParenthesisAnswer(
|
||||
problem: VariableParenthesisProblem,
|
||||
answer: VariableUserAnswer,
|
||||
): VariableGradeResult {
|
||||
const userAnswer: [
|
||||
DistributedVariableTerm | null,
|
||||
DistributedVariableTerm | null,
|
||||
] = [
|
||||
findSelectedChoice(problem, 0, answer.selectedChoiceIds[0]),
|
||||
findSelectedChoice(problem, 1, answer.selectedChoiceIds[1]),
|
||||
];
|
||||
const userAnswer = problem.correctTerms.map((_, index) =>
|
||||
findSelectedChoice(problem, index, answer.selectedChoiceIds[index] ?? null),
|
||||
);
|
||||
const correct =
|
||||
userAnswer[0]?.coefficient === problem.correctTerms[0].coefficient &&
|
||||
userAnswer[1]?.coefficient === problem.correctTerms[1].coefficient;
|
||||
userAnswer.length === problem.correctTerms.length &&
|
||||
userAnswer.every(
|
||||
(term, index) => term?.coefficient === problem.correctTerms[index].coefficient,
|
||||
);
|
||||
|
||||
return {
|
||||
correct,
|
||||
|
||||
@@ -24,21 +24,23 @@ export interface VariableParenthesisProblem {
|
||||
id: string;
|
||||
key: string;
|
||||
multiplier: number;
|
||||
terms: VariableParenthesisTerm[];
|
||||
operators: ("+" | "-")[];
|
||||
operator: "+" | "-";
|
||||
firstTerm: VariableParenthesisTerm;
|
||||
secondTerm: VariableParenthesisTerm;
|
||||
correctTerms: [DistributedVariableTerm, DistributedVariableTerm];
|
||||
choices: [VariableTermChoice[], VariableTermChoice[]];
|
||||
correctTerms: DistributedVariableTerm[];
|
||||
choices: VariableTermChoice[][];
|
||||
latexBefore: string;
|
||||
latexAfter: string;
|
||||
}
|
||||
|
||||
export interface VariableUserAnswer {
|
||||
selectedChoiceIds: [string | null, string | null];
|
||||
selectedChoiceIds: (string | null)[];
|
||||
}
|
||||
|
||||
export interface VariableGradeResult {
|
||||
correct: boolean;
|
||||
userAnswer: [DistributedVariableTerm | null, DistributedVariableTerm | null];
|
||||
correctAnswer: [DistributedVariableTerm, DistributedVariableTerm];
|
||||
userAnswer: (DistributedVariableTerm | null)[];
|
||||
correctAnswer: DistributedVariableTerm[];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user