|
|
|
|
@ -1,151 +1,230 @@
|
|
|
|
|
import java.util.Random;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 小学题目生成策略实现类。
|
|
|
|
|
*
|
|
|
|
|
* <p>随机生成包含加、减、乘、除运算的表达式,保证符合题目要求。
|
|
|
|
|
*/
|
|
|
|
|
public class ElementaryQuestionStrategy implements QuestionStrategy {
|
|
|
|
|
private final Random random = new Random();
|
|
|
|
|
private final String[] operators = {"+", "-", "*", "/"};
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String generateQuestion() {
|
|
|
|
|
int operandsCount = random.nextInt(4) + 2; // 操作数 2~5
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
|
|
|
|
|
// 生成运算符序列
|
|
|
|
|
String[] chosenOps = generateOperators(operandsCount);
|
|
|
|
|
|
|
|
|
|
// 随机决定括号
|
|
|
|
|
int[] parentheses = decideParentheses(operandsCount, chosenOps);
|
|
|
|
|
|
|
|
|
|
// 拼接表达式
|
|
|
|
|
buildExpression(sb, operandsCount, chosenOps, parentheses);
|
|
|
|
|
|
|
|
|
|
return sb.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成运算符序列
|
|
|
|
|
* 根据操作数数量,随机生成对应数量的运算符数组
|
|
|
|
|
*
|
|
|
|
|
* @param operandsCount 操作数数量
|
|
|
|
|
* @return 随机生成的运算符数组,长度为操作数数量-1
|
|
|
|
|
*/
|
|
|
|
|
private String[] generateOperators(int operandsCount) {
|
|
|
|
|
String[] chosenOps = new String[operandsCount - 1];
|
|
|
|
|
for (int i = 0; i < operandsCount - 1; i++) {
|
|
|
|
|
chosenOps[i] = operators[random.nextInt(operators.length)];
|
|
|
|
|
}
|
|
|
|
|
return chosenOps;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 决定是否需要括号,并返回括号的起始和结束位置
|
|
|
|
|
* 对于3个及以上操作数,随机决定是否添加括号,并检查括号是否有效
|
|
|
|
|
*
|
|
|
|
|
* @param operandsCount 操作数数量
|
|
|
|
|
* @param chosenOps 运算符数组
|
|
|
|
|
* @return 包含开括号和闭括号位置的数组,格式为[openParenIndex, closeParenIndex],-1表示不添加括号
|
|
|
|
|
*/
|
|
|
|
|
private int[] decideParentheses(int operandsCount, String[] chosenOps) {
|
|
|
|
|
int openParenIndex = -1;
|
|
|
|
|
int closeParenIndex = -1;
|
|
|
|
|
|
|
|
|
|
if (operandsCount > 2 && random.nextBoolean()) {
|
|
|
|
|
openParenIndex = random.nextInt(operandsCount - 1);
|
|
|
|
|
closeParenIndex = random.nextInt(operandsCount - openParenIndex - 1) + openParenIndex + 1;
|
|
|
|
|
|
|
|
|
|
// 如果括号包裹整个表达式,则不需要括号
|
|
|
|
|
if (openParenIndex == 0 && closeParenIndex == operandsCount - 1) {
|
|
|
|
|
openParenIndex = -1;
|
|
|
|
|
closeParenIndex = -1;
|
|
|
|
|
} else {
|
|
|
|
|
// 检查括号内的运算符优先级是否相同,如果相同则不需要括号
|
|
|
|
|
boolean samePrecedence = checkPrecedenceEquality(chosenOps, openParenIndex, closeParenIndex);
|
|
|
|
|
if (samePrecedence) {
|
|
|
|
|
openParenIndex = -1;
|
|
|
|
|
closeParenIndex = -1;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return new int[]{openParenIndex, closeParenIndex};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 判断括号内的运算符优先级是否相同
|
|
|
|
|
* 用于决定是否需要添加括号(如果优先级都相同,则括号是冗余的)
|
|
|
|
|
*
|
|
|
|
|
* @param chosenOps 运算符数组
|
|
|
|
|
* @param openParenIndex 开括号位置
|
|
|
|
|
* @param closeParenIndex 闭括号位置
|
|
|
|
|
* @return 如果括号内所有运算符优先级相同则返回true,否则返回false
|
|
|
|
|
*/
|
|
|
|
|
private boolean checkPrecedenceEquality(String[] chosenOps, int openParenIndex, int closeParenIndex) {
|
|
|
|
|
int precedence = getPrecedence(chosenOps[openParenIndex]);
|
|
|
|
|
for (int i = openParenIndex; i < closeParenIndex; i++) {
|
|
|
|
|
if (getPrecedence(chosenOps[i]) != precedence) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构建数学表达式
|
|
|
|
|
* 根据操作数、运算符和括号位置,拼接完整的数学表达式
|
|
|
|
|
*
|
|
|
|
|
* @param sb StringBuilder对象,用于构建表达式
|
|
|
|
|
* @param operandsCount 操作数数量
|
|
|
|
|
* @param chosenOps 运算符数组
|
|
|
|
|
* @param parentheses 括号位置数组,格式为[openParenIndex, closeParenIndex]
|
|
|
|
|
*/
|
|
|
|
|
private void buildExpression(StringBuilder sb, int operandsCount, String[] chosenOps, int[] parentheses) {
|
|
|
|
|
int prevNum = random.nextInt(100) + 1;
|
|
|
|
|
int openParenIndex = parentheses[0];
|
|
|
|
|
int closeParenIndex = parentheses[1];
|
|
|
|
|
|
|
|
|
|
// 处理第一个操作数可能的开括号
|
|
|
|
|
if (openParenIndex == 0) sb.append("(");
|
|
|
|
|
sb.append(prevNum);
|
|
|
|
|
|
|
|
|
|
// 处理最后一个操作数可能的闭括号
|
|
|
|
|
if (closeParenIndex == operandsCount - 1) sb.append(")");
|
|
|
|
|
|
|
|
|
|
// 构建剩余的操作数和运算符
|
|
|
|
|
for (int i = 1; i < operandsCount; i++) {
|
|
|
|
|
String op = chosenOps[i - 1];
|
|
|
|
|
sb.append(" ").append(op).append(" ");
|
|
|
|
|
|
|
|
|
|
// 根据运算符生成下一个操作数
|
|
|
|
|
int num = generateOperand(op, prevNum);
|
|
|
|
|
|
|
|
|
|
// 添加括号(如果需要)
|
|
|
|
|
if (i == openParenIndex) sb.append("(");
|
|
|
|
|
sb.append(num);
|
|
|
|
|
if (i == closeParenIndex) sb.append(")");
|
|
|
|
|
|
|
|
|
|
prevNum = num; // 更新上一个数字,供下一轮计算使用
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成每个操作数
|
|
|
|
|
* 确保生成的操作数符合题目要求,特别是减法不会导致负数结果
|
|
|
|
|
*
|
|
|
|
|
* @param op 当前运算符
|
|
|
|
|
* @param prevNum 前一个操作数的值
|
|
|
|
|
* @return 生成的操作数
|
|
|
|
|
*/
|
|
|
|
|
private int generateOperand(String op, int prevNum) {
|
|
|
|
|
int num = random.nextInt(100) + 1; // 默认生成1-100之间的随机数
|
|
|
|
|
|
|
|
|
|
// 如果是减法运算符,确保不会出现负数结果
|
|
|
|
|
if (op.equals("-") && num > prevNum) {
|
|
|
|
|
num = random.nextInt(prevNum) + 1; // 保证 num <= prevNum,避免负数结果
|
|
|
|
|
}
|
|
|
|
|
return num;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private int getPrecedence(String op) {
|
|
|
|
|
if (op.equals("+") || op.equals("-")) return 1;
|
|
|
|
|
if (op.equals("*") || op.equals("/")) return 2;
|
|
|
|
|
return 0;
|
|
|
|
|
private static final String[] OPERATORS = {"+", "-", "*", "/"};
|
|
|
|
|
private final Random random = new Random();
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String generateQuestion() {
|
|
|
|
|
int operandsCount = random.nextInt(4) + 2; // 操作数数量:2~5
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
|
|
|
|
|
// 生成运算符序列
|
|
|
|
|
String[] chosenOps = generateOperators(operandsCount);
|
|
|
|
|
|
|
|
|
|
// 随机决定括号位置
|
|
|
|
|
int[] parentheses = decideParentheses(operandsCount, chosenOps);
|
|
|
|
|
|
|
|
|
|
// 拼接表达式
|
|
|
|
|
buildExpression(sb, operandsCount, chosenOps, parentheses);
|
|
|
|
|
|
|
|
|
|
return sb.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据操作数数量,随机生成运算符数组。
|
|
|
|
|
*
|
|
|
|
|
* @param operandsCount 操作数数量
|
|
|
|
|
* @return 随机生成的运算符数组,长度为操作数数量 - 1
|
|
|
|
|
*/
|
|
|
|
|
private String[] generateOperators(int operandsCount) {
|
|
|
|
|
String[] chosenOps = new String[operandsCount - 1];
|
|
|
|
|
for (int i = 0; i < operandsCount - 1; i++) {
|
|
|
|
|
chosenOps[i] = OPERATORS[random.nextInt(OPERATORS.length)];
|
|
|
|
|
}
|
|
|
|
|
return chosenOps;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构建数学表达式。
|
|
|
|
|
*
|
|
|
|
|
* @param sb StringBuilder 对象
|
|
|
|
|
* @param operandsCount 操作数数量
|
|
|
|
|
* @param chosenOps 运算符数组
|
|
|
|
|
* @param parentheses 括号位置数组 [openParenIndex, closeParenIndex]
|
|
|
|
|
*/
|
|
|
|
|
private void buildExpression(
|
|
|
|
|
StringBuilder sb, int operandsCount, String[] chosenOps, int[] parentheses) {
|
|
|
|
|
int openParenIndex = parentheses[0];
|
|
|
|
|
int closeParenIndex = parentheses[1];
|
|
|
|
|
|
|
|
|
|
// 生成第一个操作数
|
|
|
|
|
int prevNum = random.nextInt(100) + 1;
|
|
|
|
|
|
|
|
|
|
// 如果开括号在第一个操作数之前
|
|
|
|
|
if (openParenIndex == 0) {
|
|
|
|
|
sb.append("(");
|
|
|
|
|
}
|
|
|
|
|
sb.append(prevNum);
|
|
|
|
|
|
|
|
|
|
// 如果闭括号在第一个操作数之后(即只包含第一个操作数)
|
|
|
|
|
if (closeParenIndex == 0) {
|
|
|
|
|
sb.append(")");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i < operandsCount; i++) {
|
|
|
|
|
String op = chosenOps[i - 1];
|
|
|
|
|
sb.append(" ").append(op).append(" ");
|
|
|
|
|
|
|
|
|
|
int num = generateOperand(op, prevNum);
|
|
|
|
|
|
|
|
|
|
// 如果开括号在当前操作数之前
|
|
|
|
|
if (i == openParenIndex) {
|
|
|
|
|
sb.append("(");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
sb.append(num);
|
|
|
|
|
|
|
|
|
|
// 如果闭括号在当前操作数之后
|
|
|
|
|
if (i == closeParenIndex) {
|
|
|
|
|
sb.append(")");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
prevNum = num;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 确保括号正确闭合(安全措施)
|
|
|
|
|
String expression = sb.toString();
|
|
|
|
|
int openCount = countChar(expression, '(');
|
|
|
|
|
int closeCount = countChar(expression, ')');
|
|
|
|
|
|
|
|
|
|
// 如果括号不匹配,重新生成表达式
|
|
|
|
|
if (openCount != closeCount) {
|
|
|
|
|
sb.setLength(0); // 清空 StringBuilder
|
|
|
|
|
// 递归调用生成新的表达式(或者去掉所有括号)
|
|
|
|
|
buildExpressionWithoutParentheses(sb, operandsCount, chosenOps);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构建没有括号的表达式(备用方法)。
|
|
|
|
|
*/
|
|
|
|
|
private void buildExpressionWithoutParentheses(
|
|
|
|
|
StringBuilder sb, int operandsCount, String[] chosenOps) {
|
|
|
|
|
int prevNum = random.nextInt(100) + 1;
|
|
|
|
|
sb.append(prevNum);
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i < operandsCount; i++) {
|
|
|
|
|
String op = chosenOps[i - 1];
|
|
|
|
|
sb.append(" ").append(op).append(" ");
|
|
|
|
|
|
|
|
|
|
int num = generateOperand(op, prevNum);
|
|
|
|
|
sb.append(num);
|
|
|
|
|
prevNum = num;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 计算字符串中特定字符的数量。
|
|
|
|
|
*
|
|
|
|
|
* @param str 输入字符串
|
|
|
|
|
* @param ch 要统计的字符
|
|
|
|
|
* @return 字符出现次数
|
|
|
|
|
*/
|
|
|
|
|
private int countChar(String str, char ch) {
|
|
|
|
|
int count = 0;
|
|
|
|
|
for (int i = 0; i < str.length(); i++) {
|
|
|
|
|
if (str.charAt(i) == ch) {
|
|
|
|
|
count++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return count;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 决定是否需要括号,并返回括号位置。 改进版本:确保括号位置有效。
|
|
|
|
|
*
|
|
|
|
|
* @param operandsCount 操作数数量
|
|
|
|
|
* @param chosenOps 运算符数组
|
|
|
|
|
* @return 括号位置数组 [openParenIndex, closeParenIndex]
|
|
|
|
|
*/
|
|
|
|
|
private int[] decideParentheses(int operandsCount, String[] chosenOps) {
|
|
|
|
|
if (operandsCount <= 2) {
|
|
|
|
|
return new int[] {-1, -1}; // 操作数太少,不需要括号
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!random.nextBoolean()) {
|
|
|
|
|
return new int[] {-1, -1}; // 随机决定不加括号
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 生成有效的括号位置
|
|
|
|
|
int openParenIndex = random.nextInt(operandsCount - 1);
|
|
|
|
|
int closeParenIndex =
|
|
|
|
|
random.nextInt(operandsCount - openParenIndex - 1) + openParenIndex + 1;
|
|
|
|
|
|
|
|
|
|
// 确保至少包含 2 个操作数
|
|
|
|
|
if (closeParenIndex - openParenIndex < 2) {
|
|
|
|
|
closeParenIndex = openParenIndex + 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果括号包裹整个表达式,则不需要括号
|
|
|
|
|
if (openParenIndex == 0 && closeParenIndex == operandsCount - 1) {
|
|
|
|
|
return new int[] {-1, -1};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查括号内运算符是否同一优先级,如果是则不需要括号
|
|
|
|
|
boolean samePrecedence = checkPrecedenceEquality(chosenOps, openParenIndex, closeParenIndex);
|
|
|
|
|
if (samePrecedence) {
|
|
|
|
|
return new int[] {-1, -1};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new int[] {openParenIndex, closeParenIndex};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 判断括号内的运算符是否具有相同优先级。
|
|
|
|
|
*
|
|
|
|
|
* @param chosenOps 运算符数组
|
|
|
|
|
* @param openParenIndex 开括号位置
|
|
|
|
|
* @param closeParenIndex 闭括号位置
|
|
|
|
|
* @return true 如果括号内运算符优先级相同
|
|
|
|
|
*/
|
|
|
|
|
private boolean checkPrecedenceEquality(
|
|
|
|
|
String[] chosenOps, int openParenIndex, int closeParenIndex) {
|
|
|
|
|
int precedence = getPrecedence(chosenOps[openParenIndex]);
|
|
|
|
|
for (int i = openParenIndex; i < closeParenIndex; i++) {
|
|
|
|
|
if (getPrecedence(chosenOps[i]) != precedence) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据运算符生成操作数。
|
|
|
|
|
*
|
|
|
|
|
* <p>特别处理减法,确保不会出现负数结果。
|
|
|
|
|
*
|
|
|
|
|
* @param op 当前运算符
|
|
|
|
|
* @param prevNum 上一个操作数
|
|
|
|
|
* @return 生成的操作数
|
|
|
|
|
*/
|
|
|
|
|
private int generateOperand(String op, int prevNum) {
|
|
|
|
|
int num = random.nextInt(100) + 1;
|
|
|
|
|
if (op.equals("-") && num > prevNum) {
|
|
|
|
|
num = random.nextInt(prevNum) + 1;
|
|
|
|
|
}
|
|
|
|
|
return num;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取运算符的优先级。
|
|
|
|
|
*
|
|
|
|
|
* @param op 运算符
|
|
|
|
|
* @return 优先级数值
|
|
|
|
|
*/
|
|
|
|
|
private int getPrecedence(String op) {
|
|
|
|
|
if (op.equals("+") || op.equals("-")) {
|
|
|
|
|
return 1;
|
|
|
|
|
}
|
|
|
|
|
if (op.equals("*") || op.equals("/")) {
|
|
|
|
|
return 2;
|
|
|
|
|
}
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|