괄호 앞 음수 분배 스테이지 추가
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import {
|
||||
generateProblem,
|
||||
generateSession,
|
||||
gradeAnswer,
|
||||
} from "./parenthesis-sign";
|
||||
|
||||
describe("parenthesis sign problem engine", () => {
|
||||
it("generates a problem within the configured ranges", () => {
|
||||
for (let index = 0; index < 100; index += 1) {
|
||||
const problem = generateProblem();
|
||||
|
||||
expect(Number.isInteger(problem.multiplier)).toBe(true);
|
||||
expect(problem.multiplier).toBeGreaterThanOrEqual(-9);
|
||||
expect(problem.multiplier).toBeLessThanOrEqual(-1);
|
||||
expect(problem.a).toBeGreaterThanOrEqual(1);
|
||||
expect(problem.a).toBeLessThanOrEqual(9);
|
||||
expect(problem.b).toBeGreaterThanOrEqual(1);
|
||||
expect(problem.b).toBeLessThanOrEqual(9);
|
||||
expect(problem.a).not.toBe(problem.b);
|
||||
expect(["+", "-"]).toContain(problem.operator);
|
||||
}
|
||||
});
|
||||
|
||||
it("distributes the multiplier correctly for both operators", () => {
|
||||
for (let index = 0; index < 100; index += 1) {
|
||||
const problem = generateProblem();
|
||||
const signedB = problem.operator === "-" ? -problem.b : problem.b;
|
||||
|
||||
expect(problem.distributedA).toBe(problem.multiplier * problem.a);
|
||||
expect(problem.distributedB).toBe(problem.multiplier * signedB);
|
||||
}
|
||||
});
|
||||
|
||||
it("grades correct and incorrect answers", () => {
|
||||
const problem = generateProblem();
|
||||
|
||||
expect(
|
||||
gradeAnswer(problem, {
|
||||
a: problem.distributedA,
|
||||
b: problem.distributedB,
|
||||
}).correct,
|
||||
).toBe(true);
|
||||
|
||||
expect(
|
||||
gradeAnswer(problem, {
|
||||
a: problem.distributedB,
|
||||
b: problem.distributedA,
|
||||
}).correct,
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it("generates a session with unique problem keys", () => {
|
||||
const problems = generateSession(10);
|
||||
const keys = new Set(problems.map((problem) => problem.key));
|
||||
|
||||
expect(problems).toHaveLength(10);
|
||||
expect(keys.size).toBe(10);
|
||||
});
|
||||
|
||||
it("rejects impossible session sizes", () => {
|
||||
expect(() => generateSession(0)).toThrow();
|
||||
expect(() => generateSession(145)).toThrow();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,118 @@
|
||||
import type {
|
||||
GradeResult,
|
||||
ParenthesisSignProblem,
|
||||
UserAnswer,
|
||||
} from "./types";
|
||||
|
||||
const MIN_MULTIPLIER = -9;
|
||||
const MAX_MULTIPLIER = -1;
|
||||
const MIN_TERM = 1;
|
||||
const MAX_TERM = 9;
|
||||
const MAX_PROBLEM_COUNT = 9 * 8 * 2;
|
||||
|
||||
type ProblemSeed = Pick<
|
||||
ParenthesisSignProblem,
|
||||
"multiplier" | "a" | "operator" | "b"
|
||||
>;
|
||||
|
||||
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 ? "-" : "+";
|
||||
|
||||
return `${first} ${operator} ${Math.abs(second)}`;
|
||||
}
|
||||
|
||||
function createProblem({ multiplier, a, operator, b }: ProblemSeed) {
|
||||
const distributedA = multiplier * a;
|
||||
const distributedB = operator === "-" ? multiplier * -b : multiplier * b;
|
||||
const key = `${multiplier}:${a}:${operator}:${b}`;
|
||||
|
||||
return {
|
||||
id: `parenthesis-sign:${key}`,
|
||||
key,
|
||||
multiplier,
|
||||
a,
|
||||
operator,
|
||||
b,
|
||||
distributedA,
|
||||
distributedB,
|
||||
latexBefore: `${multiplier}\\left(${a} ${operator} ${b}\\right)`,
|
||||
latexAfter: formatLatexTermPair(distributedA, distributedB),
|
||||
};
|
||||
}
|
||||
|
||||
function createAllProblemSeeds() {
|
||||
const seeds: ProblemSeed[] = [];
|
||||
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return seeds;
|
||||
}
|
||||
|
||||
function shuffle<T>(items: T[]) {
|
||||
const shuffled = [...items];
|
||||
|
||||
for (let index = shuffled.length - 1; index > 0; index -= 1) {
|
||||
const targetIndex = randomInt(0, index);
|
||||
const current = shuffled[index];
|
||||
shuffled[index] = shuffled[targetIndex];
|
||||
shuffled[targetIndex] = current;
|
||||
}
|
||||
|
||||
return shuffled;
|
||||
}
|
||||
|
||||
export function generateProblem(): ParenthesisSignProblem {
|
||||
const a = randomInt(MIN_TERM, MAX_TERM);
|
||||
let b = randomInt(MIN_TERM, MAX_TERM);
|
||||
|
||||
while (a === b) {
|
||||
b = randomInt(MIN_TERM, MAX_TERM);
|
||||
}
|
||||
|
||||
return createProblem({
|
||||
multiplier: randomInt(MIN_MULTIPLIER, MAX_MULTIPLIER),
|
||||
a,
|
||||
operator: Math.random() < 0.5 ? "+" : "-",
|
||||
b,
|
||||
});
|
||||
}
|
||||
|
||||
export function generateSession(count = 10): 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}.`);
|
||||
}
|
||||
|
||||
return shuffle(createAllProblemSeeds()).slice(0, count).map(createProblem);
|
||||
}
|
||||
|
||||
export function gradeAnswer(
|
||||
problem: ParenthesisSignProblem,
|
||||
answer: UserAnswer,
|
||||
): GradeResult {
|
||||
return {
|
||||
correct:
|
||||
answer.a === problem.distributedA && answer.b === problem.distributedB,
|
||||
correctAnswer: [problem.distributedA, problem.distributedB],
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
export interface ParenthesisSignProblem {
|
||||
id: string;
|
||||
key: string;
|
||||
multiplier: number;
|
||||
a: number;
|
||||
operator: "+" | "-";
|
||||
b: number;
|
||||
distributedA: number;
|
||||
distributedB: number;
|
||||
latexBefore: string;
|
||||
latexAfter: string;
|
||||
}
|
||||
|
||||
export interface UserAnswer {
|
||||
a: number;
|
||||
b: number;
|
||||
}
|
||||
|
||||
export interface GradeResult {
|
||||
correct: boolean;
|
||||
correctAnswer: [number, number];
|
||||
}
|
||||
Reference in New Issue
Block a user