重构题目生成器(新需求,小学题目不能有负数),增加添加括号功能

main
Teptao 5 months ago
parent 115d4b0ee1
commit f2970a1ee2

@ -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;
while (newProblems.size() < count && tries < maxTries) {
Equation candidate = createProblem();
String canonicalForm = candidate.toCanonicalString();
// 执行唯一性检查:既不能与历史重复,也不能与本次生成的其他题目重复
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;
}
}

@ -128,6 +128,7 @@ public class Application {
System.out.println("正在生成 " + count + " 道不重复的新题目...");
IProblemGenerator generator = sessionManager.getCurrentGenerator();
List<Equation> newProblems = generator.generate(count, history);
// 命令行显示生成题目
for (int i = 0; i < newProblems.size(); i++) {
String problemLine = (i + 1) + ". " + newProblems.get(i).toString();

@ -1,45 +1,12 @@
import java.util.ArrayList;
import java.util.List;
/**
*
* 2-5
*
*/
public class ElementaryProblemGenerator extends AbstractProblemGenerator {
private static final Operator[] AVAILABLE_OPERATORS = {
Operator.ADD, Operator.SUBTRACT, Operator.MULTIPLY, Operator.DIVIDE
};
@Override
protected Equation createProblem() {
int numOperands = getRandomNumber(MIN_OPERANDS_COUNT, MAX_OPERANDS_COUNT);
List<Integer> operands = new ArrayList<>();
List<Operator> operators = new ArrayList<>();
// 生成第一个操作数
operands.add(getRandomNumber(MIN_OPERAND_VALUE, MAX_OPERAND_VALUE));
for (int i = 0; i < numOperands - 1; i++) {
Operator operator = getRandomOperator(AVAILABLE_OPERATORS);
int nextOperand;
// 特殊处理除法确保除数不为0且能整除避免产生小数
if (operator == Operator.DIVIDE) {
// 为了简化,我们让被除数是新生成的随机数的倍数
int divisor = getRandomNumber(1, 10); // 将除数范围缩小以增加整除概率
int quotient = getRandomNumber(1, 10);
int dividend = operands.get(operands.size() - 1); // 获取前一个结果或操作数
operands.set(operands.size() - 1, divisor * quotient); // 替换前一个操作数确保能整除
nextOperand = divisor;
} else {
nextOperand = getRandomNumber(MIN_OPERAND_VALUE, MAX_OPERAND_VALUE);
}
operands.add(nextOperand);
operators.add(operator);
}
return new Equation(operands, operators, true);
// 调用父类的核心方法,并传入 true要求启用“无负数”约束。
return createBaseArithmeticProblem(true);
}
}

@ -1,77 +1,82 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
*
*
*
* 使 String
*/
public class Equation {
private final List<String> operands; // 使用String以支持如 "sqrt(16)" 的形式
private final List<String> operands;
private final List<Operator> operators;
/**
*
*
* @param operands
* @param operators
*/
public Equation(List<String> operands, List<Operator> operators) {
this.operands = new ArrayList<>(operands);
this.operators = new ArrayList<>(operators);
}
// 为了方便,提供一个只接受整数操作数的构造函数
public Equation(List<Integer> intOperands, List<Operator> operators, boolean isSimple) {
this.operands = intOperands.stream().map(String::valueOf).collect(Collectors.toList());
this.operators = new ArrayList<>(operators);
this.operands = Collections.unmodifiableList(operands);
this.operators = Collections.unmodifiableList(operators);
}
/**
*
*
* @return
*/
public List<String> getOperands() {
return Collections.unmodifiableList(operands);
return operands;
}
/**
*
*
* @return
*/
public List<Operator> getOperators() {
return Collections.unmodifiableList(operators);
return operators;
}
/**
*
* : "操作数1 运算符1 操作数2 = 结果"
*
* : "操作数1 运算符1 操作数2 = ?"
*
* @return
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(operands.get(0));
for (int i = 0; i < operators.size(); i++) {
sb.append(" ").append(operators.get(i).getSymbol()).append(" ");
sb.append(operands.get(i + 1));
}
sb.append(" = ").append("?");
// 使用 IntStream 来优雅地处理操作数和操作符的交替拼接
IntStream.range(0, operators.size())
.forEach(i -> sb.append(operands.get(i))
.append(" ")
.append(operators.get(i).getSymbol())
.append(" "));
sb.append(operands.get(operands.size() - 1)); // 追加最后一个操作数
sb.append(" = ?");
return sb.toString();
}
/**
*
* "3 + 5" "5 + 3"
*
* ( "3 + 5") ("3 + 5")
*
*
* @return
*/
public String toCanonicalString() {
if (operators.size() == 1 && operators.get(0).isCommutative()) {
// 仅处理最简单的二元运算场景
String[] sortedOperands = operands.toArray(new String[0]);
Arrays.sort(sortedOperands);
return sortedOperands[0] + " " + operators.get(0).getSymbol() + " " + sortedOperands[1];
// 简化处理:仅对两个操作数且操作符可交换的情况进行规范化
if (operands.size() == 2 && operators.size() == 1 && operators.get(0).isCommutative()) {
List<String> sortedOperands = operands.stream().sorted().collect(Collectors.toList());
return sortedOperands.get(0) + " " + operators.get(0).getSymbol() + " " + sortedOperands.get(1);
}
// 对于更复杂的或不可交换的运算,直接按顺序拼接
StringBuilder sb = new StringBuilder();
sb.append(operands.get(0));
for (int i = 0; i < operators.size(); i++) {
sb.append(" ").append(operators.get(i).getSymbol()).append(" ");
sb.append(operands.get(i + 1));
}
return sb.toString();
// 对于其他所有复杂情况,直接按原样拼接
return toString().replace(" = ?", "");
}
}

@ -3,52 +3,34 @@ import java.util.List;
/**
*
* /
* /
* 使
*/
public class HighSchoolProblemGenerator extends MiddleSchoolProblemGenerator {
// 预定义一些常见的三角函数角度
private static final int[] PREDEFINED_ANGLES = {0, 30, 45, 60, 90};
private static final String[] TRIG_FUNCTIONS = {"sin", "cos", "tan"};
@Override
protected Equation createProblem() {
// 首先调用父类MiddleSchoolProblemGenerator的方法
// 生成一个已经包含平方或开方运算的题目结构。
// 1. 调用父类(已修正的 MiddleSchoolProblemGenerator的方法
Equation middleSchoolEquation = super.createProblem();
// 直接获取父类生成的可变列表进行再次修改
List<String> operands = new ArrayList<>(middleSchoolEquation.getOperands());
List<Operator> operators = new ArrayList<>(middleSchoolEquation.getOperators());
// 随机选择一个操作数,用三角函数表达式替换。
// 这可能替换一个普通数字,也可能替换掉父类生成的 sqrt(x) 或 (x^2) 表达式,增加了多样性。
// 2. 随机替换一个操作数为三角函数
int modifyIndex = random.nextInt(operands.size());
int angle = PREDEFINED_ANGLES[random.nextInt(PREDEFINED_ANGLES.length)];
String funcType = TRIG_FUNCTIONS[random.nextInt(TRIG_FUNCTIONS.length)];
// 随机选择一个三角函数
int funcType = random.nextInt(3);
String trigExpression;
switch (funcType) {
case 0: // sin
trigExpression = "sin(" + angle + ")";
break;
case 1: // cos
trigExpression = "cos(" + angle + ")";
break;
default: // tan
if (angle == 90) {
angle = 45; // 避免tan(90)无定义的情况
}
trigExpression = "tan(" + angle + ")";
break;
if ("tan".equals(funcType) && angle == 90) {
angle = 45; // 避免 tan(90)
}
// 用三角函数的表达式字符串替换随机选择的操作数
String trigExpression = String.format("%s(%d)", funcType, angle);
operands.set(modifyIndex, trigExpression);
// 用修改后的操作数和原有的操作符列表创建最终的高中题目
return new Equation(operands, operators);
}
}

@ -1,37 +1,34 @@
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
*
*
*
*/
public class MiddleSchoolProblemGenerator extends ElementaryProblemGenerator {
public class MiddleSchoolProblemGenerator extends AbstractProblemGenerator {
@Override
protected Equation createProblem() {
// 首先,借用父类方法生成一个基础的四则运算结构
Equation basicEquation = super.createProblem();
// 1. 调用父类的核心方法,并传入 false允许生成包含负数的标准表达式。
Equation basicEquation = createBaseArithmeticProblem(false);
// 将父类生成的操作数Integer转换为本类需要的 String 列表
List<String> operands = basicEquation.getOperands().stream()
.map(String::valueOf)
.collect(Collectors.toList());
// 2. 将基础表达式元素转为可修改的列表
List<String> operands = new ArrayList<>(basicEquation.getOperands());
List<Operator> operators = new ArrayList<>(basicEquation.getOperators());
// 随机选择一个操作数进行变形
// 3. 随机选择一个操作数进行变形
int modifyIndex = random.nextInt(operands.size());
int originalValue = Integer.parseInt(operands.get(modifyIndex));
String targetOperand = operands.get(modifyIndex).replace("(", "").replace(")", "");
// 随机决定是进行平方还是开方运算
// 4. 随机决定是进行平方还是开方运算
if (random.nextBoolean()) {
// 平方运算:生成 "(originalValue^2)" 格式的字符串
String squaredExpression = String.format("(%d^2)", originalValue);
// 平方运算
String squaredExpression = String.format("(%s^2)", targetOperand);
operands.set(modifyIndex, squaredExpression);
} else {
// 开方运算:生成 "sqrt(value)" 格式的字符串
// 开方运算
int base = getRandomNumber(2, 10);
int valueToRoot = base * base; // 确保可以完美开方
int valueToRoot = base * base;
String sqrtExpression = String.format("sqrt(%d)", valueToRoot);
operands.set(modifyIndex, sqrtExpression);
}

@ -1,54 +1,42 @@
import java.util.function.BinaryOperator;
/**
*
*
*
*
*/
public enum Operator {
ADD('+', (a, b) -> a + b, true),
SUBTRACT('-', (a, b) -> a - b, false),
MULTIPLY('*', (a, b) -> a * b, true),
DIVIDE('/', (a, b) -> a / b, false);
ADD('+', true),
SUBTRACT('-', false),
MULTIPLY('*', true),
DIVIDE('/', false);
private final char symbol;
private final BinaryOperator<Integer> operation;
private final boolean isCommutative; // 标记是否为可交换运算符(如加法、乘法)
/**
*
*
* @param symbol
* @param operation
* @param isCommutative
*/
Operator(char symbol, BinaryOperator<Integer> operation, boolean isCommutative) {
Operator(char symbol, boolean isCommutative) {
this.symbol = symbol;
this.operation = operation;
this.isCommutative = isCommutative;
}
/**
*
*
* @return
*/
public char getSymbol() {
return symbol;
}
public boolean isCommutative() {
return isCommutative;
}
/**
*
*
*
* @param a
* @param b
* @return
* @return true
*/
public int apply(int a, int b) {
// 为除法增加安全检查,防止除以零
if (this == DIVIDE && b == 0) {
// 在实际生成中应避免生成除数为0的题目这里作为最后防线
throw new ArithmeticException("Division by zero.");
}
return operation.apply(a, b);
public boolean isCommutative() {
return isCommutative;
}
}
Loading…
Cancel
Save