|
|
|
|
@ -1,13 +1,15 @@
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Collections;
|
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Random;
|
|
|
|
|
import java.util.Set;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
import java.util.stream.IntStream;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 抽象题目生成器(模板方法模式)。
|
|
|
|
|
* 实现了 IProblemGenerator 接口,提供了通用的题目生成逻辑,
|
|
|
|
|
* 如唯一性检查循环、操作数生成等,将具体的题目构建逻辑延迟到子类。
|
|
|
|
|
* 提供了生成题目的通用流程和创建基础四则运算的核心方法。
|
|
|
|
|
*/
|
|
|
|
|
public abstract class AbstractProblemGenerator implements IProblemGenerator {
|
|
|
|
|
|
|
|
|
|
@ -17,51 +19,153 @@ public abstract class AbstractProblemGenerator implements IProblemGenerator {
|
|
|
|
|
protected static final int MIN_OPERANDS_COUNT = 2;
|
|
|
|
|
protected static final int MAX_OPERANDS_COUNT = 5;
|
|
|
|
|
|
|
|
|
|
private static final Operator[] AVAILABLE_OPERATORS = {
|
|
|
|
|
Operator.ADD, Operator.SUBTRACT, Operator.MULTIPLY, Operator.DIVIDE
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public List<Equation> generate(int count, Set<String> existingProblems) {
|
|
|
|
|
public final List<Equation> generate(int count, Set<String> existingProblems) {
|
|
|
|
|
List<Equation> newProblems = new ArrayList<>(count);
|
|
|
|
|
// 使用一个临时的Set来防止在同一次生成任务中产生重复
|
|
|
|
|
Set<String> currentBatchSet = new HashSet<>();
|
|
|
|
|
|
|
|
|
|
while (newProblems.size() < count) {
|
|
|
|
|
Equation candidate = createProblem();
|
|
|
|
|
int maxTries = count * 100;
|
|
|
|
|
int tries = 0;
|
|
|
|
|
|
|
|
|
|
String canonicalForm = candidate.toCanonicalString();
|
|
|
|
|
while (newProblems.size() < count && tries < maxTries) {
|
|
|
|
|
Equation candidate = createProblem();
|
|
|
|
|
String canonicalForm = candidate.toString();
|
|
|
|
|
|
|
|
|
|
// 执行唯一性检查:既不能与历史重复,也不能与本次生成的其他题目重复
|
|
|
|
|
if (!existingProblems.contains(canonicalForm) && !currentBatchSet.contains(canonicalForm)) {
|
|
|
|
|
if (!existingProblems.contains(canonicalForm) && currentBatchSet.add(canonicalForm)) {
|
|
|
|
|
newProblems.add(candidate);
|
|
|
|
|
currentBatchSet.add(canonicalForm);
|
|
|
|
|
}
|
|
|
|
|
tries++;
|
|
|
|
|
}
|
|
|
|
|
return newProblems;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建一个候选数学题目的抽象方法(模板方法)。
|
|
|
|
|
* 子类必须实现此方法来定义具体难度的题目生成规则。
|
|
|
|
|
*
|
|
|
|
|
* @return 一个新创建的 Equation 对象
|
|
|
|
|
* 模板方法:创建一道具体问题的抽象定义,由子类实现。
|
|
|
|
|
*/
|
|
|
|
|
protected abstract Equation createProblem();
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成一个在指定范围内的随机整数。
|
|
|
|
|
* 创建一个基础的四则运算方程式。
|
|
|
|
|
* 此方法已被重构,将复杂逻辑委托给私有辅助方法。
|
|
|
|
|
*
|
|
|
|
|
* @param min 最小值(包含)
|
|
|
|
|
* @param max 最大值(包含)
|
|
|
|
|
* @return 随机整数
|
|
|
|
|
* @param nonNegativeOnly 如果为 true,则强制保证所有中间和最终结果非负。
|
|
|
|
|
* @return 一个 Equation 对象。
|
|
|
|
|
*/
|
|
|
|
|
protected int getRandomNumber(int min, int max) {
|
|
|
|
|
return random.nextInt(max - min + 1) + min;
|
|
|
|
|
protected Equation createBaseArithmeticProblem(boolean nonNegativeOnly) {
|
|
|
|
|
int numOperands = getRandomNumber(MIN_OPERANDS_COUNT, MAX_OPERANDS_COUNT);
|
|
|
|
|
List<String> operands = new ArrayList<>();
|
|
|
|
|
List<Operator> operators = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
int currentResult = getRandomNumber(MIN_OPERAND_VALUE, MAX_OPERAND_VALUE);
|
|
|
|
|
operands.add(String.valueOf(currentResult));
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < numOperands - 1; i++) {
|
|
|
|
|
if (nonNegativeOnly) {
|
|
|
|
|
currentResult = appendNonNegativeOperation(operands, operators, currentResult);
|
|
|
|
|
} else {
|
|
|
|
|
appendStandardOperation(operands, operators);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
addParentheses(operands, operators);
|
|
|
|
|
return new Equation(operands, operators);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* (Refactored Helper) 追加一个确保结果非负的运算步骤。
|
|
|
|
|
*
|
|
|
|
|
* @param operands 当前操作数列表
|
|
|
|
|
* @param operators 当前操作符列表
|
|
|
|
|
* @param currentResult 当前的累计计算结果
|
|
|
|
|
* @return 更新后的累计计算结果
|
|
|
|
|
*/
|
|
|
|
|
private int appendNonNegativeOperation(List<String> operands, List<Operator> operators, int currentResult) {
|
|
|
|
|
Operator operator = AVAILABLE_OPERATORS[random.nextInt(AVAILABLE_OPERATORS.length)];
|
|
|
|
|
int nextOperand;
|
|
|
|
|
int newResult = currentResult;
|
|
|
|
|
|
|
|
|
|
switch (operator) {
|
|
|
|
|
case ADD:
|
|
|
|
|
nextOperand = getRandomNumber(MIN_OPERAND_VALUE, MAX_OPERAND_VALUE);
|
|
|
|
|
newResult += nextOperand;
|
|
|
|
|
break;
|
|
|
|
|
case SUBTRACT:
|
|
|
|
|
nextOperand = getRandomNumber(MIN_OPERAND_VALUE, currentResult);
|
|
|
|
|
newResult -= nextOperand;
|
|
|
|
|
break;
|
|
|
|
|
case MULTIPLY:
|
|
|
|
|
nextOperand = getRandomNumber(MIN_OPERAND_VALUE, 10);
|
|
|
|
|
newResult *= nextOperand;
|
|
|
|
|
break;
|
|
|
|
|
case DIVIDE:
|
|
|
|
|
List<Integer> divisors = findDivisors(currentResult);
|
|
|
|
|
nextOperand = divisors.get(random.nextInt(divisors.size()));
|
|
|
|
|
newResult /= nextOperand;
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
// 为了编译安全,实际上不会执行到这里
|
|
|
|
|
throw new IllegalStateException("Unexpected value: " + operator);
|
|
|
|
|
}
|
|
|
|
|
operands.add(String.valueOf(nextOperand));
|
|
|
|
|
operators.add(operator);
|
|
|
|
|
return newResult;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 从给定的操作符数组中随机选择一个。
|
|
|
|
|
* @param availableOperators 可用的操作符数组
|
|
|
|
|
* @return 随机选择的操作符
|
|
|
|
|
* (Refactored Helper) 追加一个标准的运算步骤(允许负数)。
|
|
|
|
|
*
|
|
|
|
|
* @param operands 当前操作数列表
|
|
|
|
|
* @param operators 当前操作符列表
|
|
|
|
|
*/
|
|
|
|
|
protected Operator getRandomOperator(Operator[] availableOperators) {
|
|
|
|
|
return availableOperators[random.nextInt(availableOperators.length)];
|
|
|
|
|
private void appendStandardOperation(List<String> operands, List<Operator> operators) {
|
|
|
|
|
Operator operator = AVAILABLE_OPERATORS[random.nextInt(AVAILABLE_OPERATORS.length)];
|
|
|
|
|
int nextOperand;
|
|
|
|
|
|
|
|
|
|
if (operator == Operator.DIVIDE) {
|
|
|
|
|
// 为确保整除,反向构造
|
|
|
|
|
int divisor = getRandomNumber(1, 10);
|
|
|
|
|
int quotient = getRandomNumber(1, 20);
|
|
|
|
|
// 替换前一个操作数以确保可整除
|
|
|
|
|
operands.set(operands.size() - 1, String.valueOf(divisor * quotient));
|
|
|
|
|
nextOperand = divisor;
|
|
|
|
|
} else {
|
|
|
|
|
nextOperand = getRandomNumber(MIN_OPERAND_VALUE, MAX_OPERAND_VALUE);
|
|
|
|
|
}
|
|
|
|
|
operands.add(String.valueOf(nextOperand));
|
|
|
|
|
operators.add(operator);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private void addParentheses(List<String> operands, List<Operator> operators) {
|
|
|
|
|
if (operators.size() > 1 && random.nextBoolean()) {
|
|
|
|
|
int pos = random.nextInt(operators.size());
|
|
|
|
|
Operator op = operators.get(pos);
|
|
|
|
|
// 仅在优先级较低的运算符旁边添加括号才有视觉意义
|
|
|
|
|
if (op == Operator.ADD || op == Operator.SUBTRACT) {
|
|
|
|
|
operands.set(pos, "(" + operands.get(pos));
|
|
|
|
|
operands.set(pos + 1, operands.get(pos + 1) + ")");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private List<Integer> findDivisors(int number) {
|
|
|
|
|
if (number == 0) {
|
|
|
|
|
return Collections.singletonList(1); // 避免除以0
|
|
|
|
|
}
|
|
|
|
|
return IntStream.rangeClosed(1, Math.abs(number))
|
|
|
|
|
.filter(i -> number % i == 0)
|
|
|
|
|
.boxed()
|
|
|
|
|
.collect(Collectors.toList());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected int getRandomNumber(int min, int max) {
|
|
|
|
|
if (min > max) {
|
|
|
|
|
return max; // 安全处理,例如当currentResult变为1时
|
|
|
|
|
}
|
|
|
|
|
return random.nextInt(max - min + 1) + min;
|
|
|
|
|
}
|
|
|
|
|
}
|