Files
cabrits-math/lib/problems/generators/ratio-problems.ts
2026-07-05 11:12:45 -04:00

163 lines
6.1 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { MathProblem, Difficulty } from "../types";
import { randomInt, randomChoice } from "@/lib/utils";
import { simplifyRatio, divideInRatio } from "@/lib/math/ratios";
let counter = 0;
function nextId() {
return `rp-${++counter}-${Date.now()}`;
}
export function generateDefineRatio(difficulty: Difficulty): MathProblem {
const contexts = [
{ a: "red", b: "blue", item: "counters in a bag" },
{ a: "boys", b: "girls", item: "in the class" },
{ a: "cats", b: "dogs", item: "at the shelter" },
{ a: "apples", b: "oranges", item: "in the basket" },
];
const ctx = randomChoice(contexts);
const g = randomChoice(difficulty === 1 ? [1, 2] : difficulty === 2 ? [2, 3, 4] : [3, 4, 5, 6]);
const x = randomInt(1, difficulty === 1 ? 6 : 8) * g;
const y = randomInt(1, difficulty === 1 ? 6 : 8) * g;
const simplified = simplifyRatio([x, y]);
return {
id: nextId(),
prompt: `\\begin{array}{l}\\text{There are ${x} ${ctx.a} and ${y} ${ctx.b} ${ctx.item}.}\\\\\\text{Write the ratio of ${ctx.a} to ${ctx.b} in its simplest form.}\\end{array}`,
answer: { kind: "ratio", parts: simplified },
hints: [
`Write it as ${x} : ${y} first.`,
"Divide both parts by their highest common factor.",
],
steps: [
{ explanation: `The ratio of ${ctx.a} to ${ctx.b} is ${x} : ${y}.` },
{ explanation: `In simplest form: ${simplified.join(" : ")}` },
],
};
}
export function generateFractionsAndRatios(difficulty: Difficulty): MathProblem {
const a = randomInt(1, difficulty === 1 ? 4 : 7);
const b = randomInt(1, difficulty === 1 ? 4 : 7);
const whole = a + b;
const which = randomChoice([0, 1] as const);
const part = which === 0 ? a : b;
const [n, d] = simplifyRatio([part, whole]);
return {
id: nextId(),
prompt: `\\begin{array}{l}\\text{Two amounts are shared in the ratio } ${a} : ${b}.\\\\\\text{What fraction of the whole is the ${which === 0 ? "first" : "second"} part?}\\end{array}`,
answer: { kind: "fraction", numerator: n, denominator: d },
hints: [
`The whole is ${a} + ${b} = ${whole} parts.`,
`The ${which === 0 ? "first" : "second"} part is ${part} out of ${whole}.`,
"Simplify the fraction.",
],
steps: [
{ explanation: `Total parts: ${a} + ${b} = ${whole}.` },
{ explanation: `Fraction = ${part}/${whole}, which simplifies to ${n}/${d}.` },
],
};
}
export function generateSimplifyRatio(difficulty: Difficulty): MathProblem {
const gcd = randomChoice(difficulty === 1 ? [2, 3, 4, 5] : difficulty === 2 ? [3, 4, 5, 6, 7] : [4, 6, 8, 9, 12]);
const a = randomInt(1, difficulty === 1 ? 5 : 10) * gcd;
const b = randomInt(1, difficulty === 1 ? 5 : 10) * gcd;
const simplified = simplifyRatio([a, b]);
return {
id: nextId(),
prompt: `\\text{Simplify the ratio } ${a}:${b}`,
answer: { kind: "ratio", parts: simplified },
hints: [
`Find the GCD of ${a} and ${b}`,
`The GCD is ${gcd}`,
`Divide both parts by ${gcd}`,
],
steps: [
{ explanation: `GCD of ${a} and ${b} is ${gcd}` },
{ explanation: `${a} ÷ ${gcd} = ${simplified[0]}, ${b} ÷ ${gcd} = ${simplified[1]}` },
{ explanation: `Simplified: ${simplified.join(":")}` },
],
};
}
export function generateDivideInRatio(difficulty: Difficulty): MathProblem {
const numParts = difficulty === 3 ? 3 : 2;
const parts: number[] = [];
for (let i = 0; i < numParts; i++) {
parts.push(randomInt(1, difficulty === 1 ? 5 : 9));
}
const sum = parts.reduce((a, b) => a + b, 0);
const multiplier = randomChoice(difficulty === 1 ? [2, 3, 4, 5] : [3, 4, 5, 6, 7, 8]);
const total = sum * multiplier;
const values = divideInRatio(total, parts);
const intValues = values.map(Math.round);
const names = ["first share", "second share", "third share"];
return {
id: nextId(),
prompt: `\\text{Share } ${total} \\text{ in the ratio } ${parts.join(":")}`,
answer: { kind: "ratio", parts: intValues },
hints: [
`Total parts: ${parts.join(" + ")} = ${sum}`,
`One part = ${total} ÷ ${sum} = ${multiplier}`,
`Multiply each ratio number by ${multiplier}`,
],
steps: [
{ explanation: `Total parts = ${parts.join(" + ")} = ${sum}` },
{ explanation: `One part = ${total} ÷ ${sum} = ${multiplier}` },
...parts.map((p, i) => ({
explanation: `${names[i]}: ${p} × ${multiplier} = ${intValues[i]}`,
})),
],
};
}
export function generateRatioWordProblem(difficulty: Difficulty): MathProblem {
const a = randomInt(2, 7);
const b = randomInt(2, 7);
const diff = Math.abs(a - b);
if (diff === 0) {
return generateDivideInRatio(difficulty);
}
const contexts = [
{ item: "sweets", nameA: "Josh", nameB: "Nathan" },
{ item: "marbles", nameA: "Amy", nameB: "Ben" },
{ item: "stickers", nameA: "Karen", nameB: "Natasha" },
];
const ctx = randomChoice(contexts);
const onePart = randomChoice(difficulty === 1 ? [5, 10, 12] : [6, 8, 9, 15, 20]);
const extraAmount = diff * onePart;
const totalParts = a + b;
const total = totalParts * onePart;
const bigger = a > b ? ctx.nameA : ctx.nameB;
const biggerRatio = Math.max(a, b);
const smallerRatio = Math.min(a, b);
return {
id: nextId(),
prompt: `\\begin{array}{l}\\text{${ctx.item.charAt(0).toUpperCase() + ctx.item.slice(1)} were shared between ${ctx.nameA} and ${ctx.nameB}}\\\\\\text{in the ratio ${a}:${b}. If ${bigger} received ${extraAmount} more ${ctx.item}, find the total shared.}\\end{array}`,
answer: { kind: "integer", value: total },
hints: [
`The difference in ratio parts is ${biggerRatio} - ${smallerRatio} = ${diff}`,
`${diff} parts = ${extraAmount}, so 1 part = ${onePart}`,
`Total parts = ${a} + ${b} = ${totalParts}`,
],
steps: [
{ explanation: `Difference in parts: ${biggerRatio} - ${smallerRatio} = ${diff}` },
{ explanation: `${diff} parts = ${extraAmount}, so 1 part = ${extraAmount} ÷ ${diff} = ${onePart}` },
{ explanation: `Total parts: ${a} + ${b} = ${totalParts}` },
{ explanation: `Total ${ctx.item}: ${totalParts} × ${onePart} = ${total}` },
],
};
}