|
|
|
|
@ -0,0 +1,124 @@
|
|
|
|
|
package com.mathgenerator.generator;
|
|
|
|
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.concurrent.ThreadLocalRandom;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* “安全”的小学题目生成器,确保运算过程中不产生负数。
|
|
|
|
|
* (最终版:已支持在保证无负数的前提下,智能地生成括号)
|
|
|
|
|
*/
|
|
|
|
|
public class SafePrimarySchoolGenerator implements QuestionGenerator {
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成一道小学难度的数学题目,确保结果非负且操作数在2-5个。
|
|
|
|
|
* @return 一个符合所有约束的小学题目字符串,可能包含括号。
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
public String generateSingleQuestion() {
|
|
|
|
|
int operandsTotalBudget = ThreadLocalRandom.current().nextInt(2, 6);
|
|
|
|
|
Term finalExpression = generateSafeExpression(operandsTotalBudget);
|
|
|
|
|
return String.join(" ", finalExpression.parts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据操作数预算,生成一个确保结果非负的(子)表达式。
|
|
|
|
|
* 这是实现括号递归生成的核心方法。
|
|
|
|
|
* @param operandBudget 该表达式可用的操作数数量。
|
|
|
|
|
* @return 一个包含表达式部分、计算值和已用操作数数量的Term对象。
|
|
|
|
|
*/
|
|
|
|
|
private Term generateSafeExpression(int operandBudget) {
|
|
|
|
|
ThreadLocalRandom random = ThreadLocalRandom.current();
|
|
|
|
|
List<String> fullExpressionParts = new ArrayList<>();
|
|
|
|
|
int operandsRemaining = operandBudget;
|
|
|
|
|
|
|
|
|
|
// 1. 生成第一个项
|
|
|
|
|
Term firstTerm = generateTerm(operandsRemaining);
|
|
|
|
|
fullExpressionParts.addAll(firstTerm.parts);
|
|
|
|
|
int currentResult = firstTerm.value;
|
|
|
|
|
operandsRemaining -= firstTerm.operandsUsed;
|
|
|
|
|
|
|
|
|
|
// 2. 循环生成后续的项,直到预算用完
|
|
|
|
|
while (operandsRemaining > 0) {
|
|
|
|
|
Term nextTerm = generateTerm(operandsRemaining);
|
|
|
|
|
boolean useAddition = random.nextBoolean();
|
|
|
|
|
if (!useAddition && currentResult < nextTerm.value) {
|
|
|
|
|
useAddition = true; // 强制改为加法以避免负数
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (useAddition) {
|
|
|
|
|
fullExpressionParts.add("+");
|
|
|
|
|
currentResult += nextTerm.value;
|
|
|
|
|
} else {
|
|
|
|
|
fullExpressionParts.add("-");
|
|
|
|
|
currentResult -= nextTerm.value;
|
|
|
|
|
}
|
|
|
|
|
fullExpressionParts.addAll(nextTerm.parts);
|
|
|
|
|
operandsRemaining -= nextTerm.operandsUsed;
|
|
|
|
|
}
|
|
|
|
|
return new Term(fullExpressionParts, currentResult, operandBudget - operandsRemaining);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成一个“项”(Term)。
|
|
|
|
|
* 一个项可以是简单的乘除法序列,也可以是一个带括号的子表达式。
|
|
|
|
|
*/
|
|
|
|
|
private Term generateTerm(int operandsRemaining) {
|
|
|
|
|
ThreadLocalRandom random = ThreadLocalRandom.current();
|
|
|
|
|
// 如果预算充足(>=2)且随机命中,则生成带括号的复杂项
|
|
|
|
|
if (operandsRemaining >= 2 && random.nextInt(10) < 3) { // 30%的概率生成括号
|
|
|
|
|
// 为括号内的子表达式分配预算(至少2个,最多为剩余预算)
|
|
|
|
|
int subExpressionBudget = random.nextInt(2, operandsRemaining + 1);
|
|
|
|
|
Term subExpression = generateSafeExpression(subExpressionBudget);
|
|
|
|
|
|
|
|
|
|
List<String> parts = new ArrayList<>();
|
|
|
|
|
parts.add("(");
|
|
|
|
|
parts.addAll(subExpression.parts);
|
|
|
|
|
parts.add(")");
|
|
|
|
|
|
|
|
|
|
return new Term(parts, subExpression.value, subExpression.operandsUsed);
|
|
|
|
|
} else {
|
|
|
|
|
// 否则,生成简单的乘除法项
|
|
|
|
|
return generateSimpleTerm(operandsRemaining);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成一个仅包含乘除法的简单项。
|
|
|
|
|
*/
|
|
|
|
|
private Term generateSimpleTerm(int operandsRemaining) {
|
|
|
|
|
ThreadLocalRandom random = ThreadLocalRandom.current();
|
|
|
|
|
List<String> parts = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
int termValue = random.nextInt(1, 21);
|
|
|
|
|
parts.add(String.valueOf(termValue));
|
|
|
|
|
int operandsUsed = 1;
|
|
|
|
|
|
|
|
|
|
if (operandsRemaining > 1 && random.nextBoolean()) {
|
|
|
|
|
if (random.nextBoolean()) {
|
|
|
|
|
parts.add("*");
|
|
|
|
|
int multiplier = random.nextInt(1, 10);
|
|
|
|
|
parts.add(String.valueOf(multiplier));
|
|
|
|
|
termValue *= multiplier;
|
|
|
|
|
} else {
|
|
|
|
|
parts.add("/");
|
|
|
|
|
List<Integer> divisors = getDivisors(termValue);
|
|
|
|
|
int divisor = divisors.get(random.nextInt(divisors.size()));
|
|
|
|
|
parts.add(String.valueOf(divisor));
|
|
|
|
|
termValue /= divisor;
|
|
|
|
|
}
|
|
|
|
|
operandsUsed++;
|
|
|
|
|
}
|
|
|
|
|
return new Term(parts, termValue, operandsUsed);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private record Term(List<String> parts, int value, int operandsUsed) {}
|
|
|
|
|
|
|
|
|
|
private List<Integer> getDivisors(int number) {
|
|
|
|
|
List<Integer> divisors = new ArrayList<>();
|
|
|
|
|
for (int i = 1; i <= number; i++) {
|
|
|
|
|
if (number % i == 0) divisors.add(i);
|
|
|
|
|
}
|
|
|
|
|
return divisors;
|
|
|
|
|
}
|
|
|
|
|
}
|