You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
MathLearningSoftware/src/com/mathlearning/model/QuestionGenerator.java

701 lines
22 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

package com.mathlearning.model;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
public class QuestionGenerator {
private Random random = new Random();
public List<Question> generateQuestions(String level, int count) {
List<Question> questions = new ArrayList<>();
Set<String> questionTexts = new HashSet<>(); // 防止重复题目
for (int i = 0; i < count; i++) {
Question question;
int attempt = 0;
do {
question = generateQuestion(level, i);
attempt++;
} while (questionTexts.contains(question.getQuestionText()) && attempt < 10);
if (question != null) {
questionTexts.add(question.getQuestionText());
questions.add(question);
}
}
return questions;
}
private Question generateQuestion(String level, int index) {
switch (level) {
case "小学":
return generatePrimaryQuestion(index);
case "初中":
return generateMiddleSchoolQuestion(index);
case "高中":
return generateHighSchoolQuestion(index);
default:
return generatePrimaryQuestion(index);
}
}
private Question generatePrimaryQuestion(int index) {
int operandCount = random.nextInt(4) + 2; // 2-5个操作数
int[] operands = new int[operandCount];
int[] operations = new int[operandCount - 1];
String questionText;
double answer;
do {
// 生成操作数
for (int i = 0; i < operandCount; i++) {
operands[i] = random.nextInt(100) + 1; // 1-100
}
// 生成操作符 (0: +, 1: -, 2: *, 3: /)
for (int i = 0; i < operations.length; i++) {
operations[i] = random.nextInt(4);
}
// 改进的括号逻辑:在可能改变计算顺序的地方加括号
boolean useParentheses = random.nextDouble() < 0.3 && operandCount >= 3;
int parenStart = -1;
int parenEnd = -1;
if (useParentheses) {
// 寻找适合加括号的位置(加减法周围)
parenStart = findSuitableParenthesesPosition(operations);
if (parenStart != -1) {
// 括号包含2-3个操作数
parenEnd = parenStart + 1;
if (parenEnd < operands.length - 1 && random.nextBoolean()) {
parenEnd++;
}
}
}
questionText = buildPrimaryQuestionText(operands, operations, parenStart, parenEnd);
answer = calculatePrimaryAnswerWithParentheses(operands, operations, parenStart, parenEnd);
} while (answer < 0 || Double.isNaN(answer) || Double.isInfinite(answer));
return generateOptions(questionText, answer, "小学");
}
private Question generateMiddleSchoolQuestion(int index) {
int operandCount = random.nextInt(5) + 1; // 1-5个操作数
int[] operands = new int[operandCount];
int[] operations = new int[Math.max(0, operandCount - 1)]; // 操作符数组
boolean[] hasSpecialOp = new boolean[operandCount]; // 标记哪些操作数有平方或开根号
int[] specialOpTypes = new int[operandCount]; // 0:平方, 1:开根号
String questionText;
double answer;
// 确保至少有一个平方或开根号运算符
boolean hasSpecialOperation = false;
for (int i = 0; i < operandCount; i++) {
hasSpecialOp[i] = random.nextDouble() < 0.6; // 60%的概率有特殊运算
if (hasSpecialOp[i]) {
hasSpecialOperation = true;
specialOpTypes[i] = random.nextInt(2); // 随机选择平方或开根号
}
}
// 如果没有特殊运算,强制至少一个
if (!hasSpecialOperation && operandCount > 0) {
int idx = random.nextInt(operandCount);
hasSpecialOp[idx] = true;
specialOpTypes[idx] = random.nextInt(2);
}
// 生成合适的题目
do {
// 生成操作数
for (int i = 0; i < operandCount; i++) {
operands[i] = random.nextInt(100) + 1; // 1-100
}
// 生成操作符 (0: +, 1: -, 2: *, 3: /)
for (int i = 0; i < operations.length; i++) {
operations[i] = random.nextInt(4);
}
// 构建题目并计算答案
questionText = buildMiddleSchoolQuestionText(operands, operations, hasSpecialOp, specialOpTypes);
answer = calculateMiddleSchoolAnswer(operands, operations, hasSpecialOp, specialOpTypes);
} while (Double.isNaN(answer) || Double.isInfinite(answer) || Math.abs(answer) > 10000);
return generateOptions(questionText, answer, "初中");
}
private Question generateHighSchoolQuestion(int index) {
int operandCount = random.nextInt(5) + 1; // 1-5个操作数
int[] operands = new int[operandCount];
int[] operations = new int[Math.max(0, operandCount - 1)]; // 操作符数组
boolean[] hasTrigOp = new boolean[operandCount]; // 标记哪些操作数有三角函数
int[] trigFunctions = new int[operandCount]; // 0:sin, 1:cos, 2:tan
String questionText;
double answer;
// 确保至少有一个三角函数运算符
boolean hasTrigOperation = false;
for (int i = 0; i < operandCount; i++) {
hasTrigOp[i] = random.nextDouble() < 0.6; // 60%的概率有三角函数
if (hasTrigOp[i]) {
hasTrigOperation = true;
trigFunctions[i] = random.nextInt(3); // 随机选择三角函数
}
}
// 如果没有三角函数,强制至少一个
if (!hasTrigOperation && operandCount > 0) {
int idx = random.nextInt(operandCount);
hasTrigOp[idx] = true;
trigFunctions[idx] = random.nextInt(3);
}
// 生成合适的题目
do {
// 生成操作数
for (int i = 0; i < operandCount; i++) {
if (hasTrigOp[i]) {
// 三角函数使用特殊角度值
int[] specialAngles = {0, 15, 30, 45, 60, 75, 90};
operands[i] = specialAngles[random.nextInt(specialAngles.length)];
} else {
// 普通操作数使用1-100的随机数
operands[i] = random.nextInt(100) + 1;
}
}
// 生成操作符 (0: +, 1: -, 2: *, 3: /)
for (int i = 0; i < operations.length; i++) {
operations[i] = random.nextInt(4);
}
// 构建题目并计算答案
questionText = buildHighSchoolQuestionText(operands, operations, hasTrigOp, trigFunctions);
answer = calculateHighSchoolAnswer(operands, operations, hasTrigOp, trigFunctions);
} while (Double.isNaN(answer) || Double.isInfinite(answer) || Math.abs(answer) > 10000);
return generateOptions(questionText, answer, "高中");
}
// 小学题目相关方法
private int findSuitableParenthesesPosition(int[] operations) {
// 寻找加减法操作符的位置,在这些地方加括号才有意义
List<Integer> suitablePositions = new ArrayList<>();
for (int i = 0; i < operations.length; i++) {
// 如果是加减法,且不是第一个操作符
if ((operations[i] == 0 || operations[i] == 1) && i > 0) {
suitablePositions.add(i);
}
}
if (suitablePositions.isEmpty()) {
// 如果没有找到合适的加减法位置,随机选择一个位置
return operations.length > 1 ? random.nextInt(operations.length - 1) : -1;
}
// 从合适的位置中随机选择一个
return suitablePositions.get(random.nextInt(suitablePositions.size()));
}
private String buildPrimaryQuestionText(int[] operands, int[] operations, int parenStart, int parenEnd) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < operands.length; i++) {
// 在操作数前添加左括号
if (i == parenStart) {
sb.append("(");
}
// 添加操作数
sb.append(operands[i]);
// 在操作数后添加右括号(但要在操作符之前)
if (i == parenEnd) {
sb.append(")");
}
// 添加操作符(如果不是最后一个操作数)
if (i < operations.length) {
switch (operations[i]) {
case 0: sb.append(" + "); break;
case 1: sb.append(" - "); break;
case 2: sb.append(" × "); break;
case 3: sb.append(" ÷ "); break;
}
}
}
sb.append(" = ?");
return sb.toString();
}
private double calculatePrimaryAnswerWithParentheses(int[] operands, int[] operations, int parenStart, int parenEnd) {
// 如果没有括号,使用原来的计算方法
if (parenStart == -1 || parenEnd == -1) {
return calculatePrimaryAnswer(operands, operations);
}
// 复制操作数和操作符
List<Integer> numbers = new ArrayList<>();
List<Integer> ops = new ArrayList<>();
for (int operand : operands) {
numbers.add(operand);
}
for (int operation : operations) {
ops.add(operation);
}
// 计算括号内的表达式
double parenthesesResult = calculateParenthesesContent(numbers, ops, parenStart, parenEnd);
// 验证括号计算结果是否有效
if (parenthesesResult < 0 || Double.isNaN(parenthesesResult) || Double.isInfinite(parenthesesResult)) {
return -1;
}
// 用括号计算结果替换括号内的所有元素
replaceWithParenthesesResult(numbers, ops, parenStart, parenEnd, parenthesesResult);
// 计算剩余表达式
return calculateWithoutParentheses(numbers, ops);
}
private double calculateParenthesesContent(List<Integer> numbers, List<Integer> ops, int start, int end) {
// 提取括号内的数字和操作符
List<Integer> parenNumbers = new ArrayList<>();
List<Integer> parenOps = new ArrayList<>();
for (int i = start; i <= end; i++) {
parenNumbers.add(numbers.get(i));
}
for (int i = start; i < end; i++) {
parenOps.add(ops.get(i));
}
// 计算括号内的表达式(精确计算)
return calculateWithoutParentheses(parenNumbers, parenOps);
}
private void replaceWithParenthesesResult(List<Integer> numbers, List<Integer> ops,
int start, int end, double result) {
// 计算需要移除的元素数量
int numCountToRemove = end - start + 1;
int opCountToRemove = end - start;
// 移除括号范围内的数字
for (int i = 0; i < numCountToRemove; i++) {
numbers.remove(start);
}
// 移除括号范围内的操作符
for (int i = 0; i < opCountToRemove; i++) {
ops.remove(start);
}
// 插入括号计算结果
numbers.add(start, (int) Math.round(result));
}
private double calculateWithoutParentheses(List<Integer> numbers, List<Integer> ops) {
// 转换为double列表进行计算精确计算
List<Double> doubleNumbers = new ArrayList<>();
for (Integer num : numbers) {
doubleNumbers.add(num.doubleValue());
}
// 先处理乘除法(精确计算)
for (int i = 0; i < ops.size(); i++) {
int op = ops.get(i);
if (op == 2 || op == 3) {
double left = doubleNumbers.get(i);
double right = doubleNumbers.get(i + 1);
double result;
if (op == 2) {
result = left * right;
} else {
if (right == 0) return Double.NaN;
result = left / right;
}
doubleNumbers.set(i, result);
doubleNumbers.remove(i + 1);
ops.remove(i);
i--;
}
}
// 处理加减法(精确计算)
double result = doubleNumbers.get(0);
for (int i = 0; i < ops.size(); i++) {
int op = ops.get(i);
double nextNum = doubleNumbers.get(i + 1);
if (op == 0) {
result += nextNum;
} else {
if (result < nextNum) return -1;
result -= nextNum;
}
}
// 只在最后结果保留2位小数
return Math.round(result * 100) / 100.0;
}
private double calculatePrimaryAnswer(int[] operands, int[] operations) {
// 转换为double列表精确计算
List<Double> numbers = new ArrayList<>();
List<Integer> ops = new ArrayList<>();
for (int operand : operands) {
numbers.add((double) operand);
}
for (int operation : operations) {
ops.add(operation);
}
// 处理乘除法(精确计算)
for (int i = 0; i < ops.size(); i++) {
int op = ops.get(i);
if (op == 2 || op == 3) {
double left = numbers.get(i);
double right = numbers.get(i + 1);
double result;
if (op == 2) {
result = left * right;
} else {
if (right == 0) return Double.NaN;
result = left / right;
}
numbers.set(i, result);
numbers.remove(i + 1);
ops.remove(i);
i--;
}
}
// 处理加减法(精确计算)
double result = numbers.get(0);
for (int i = 0; i < ops.size(); i++) {
int op = ops.get(i);
double nextNum = numbers.get(i + 1);
if (op == 0) {
result += nextNum;
} else {
if (result < nextNum) return -1;
result -= nextNum;
}
}
// 只在最后结果保留2位小数
return Math.round(result * 100) / 100.0;
}
// 初中题目相关方法
private String buildMiddleSchoolQuestionText(int[] operands, int[] operations, boolean[] hasSpecialOp, int[] specialOpTypes) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < operands.length; i++) {
if (i > 0) {
switch (operations[i - 1]) {
case 0: sb.append(" + "); break;
case 1: sb.append(" - "); break;
case 2: sb.append(" × "); break;
case 3: sb.append(" ÷ "); break;
}
}
if (hasSpecialOp[i]) {
if (specialOpTypes[i] == 0) {
// 平方
sb.append(operands[i]).append("²");
} else {
// 开根号
sb.append("√").append(operands[i]);
}
} else {
sb.append(operands[i]);
}
}
sb.append(" = ?");
return sb.toString();
}
private double calculateMiddleSchoolAnswer(int[] operands, int[] operations, boolean[] hasSpecialOp, int[] specialOpTypes) {
// 先处理特殊运算(平方和开根号)- 精确计算
double[] processedValues = new double[operands.length];
for (int i = 0; i < operands.length; i++) {
if (hasSpecialOp[i]) {
if (specialOpTypes[i] == 0) {
// 平方
processedValues[i] = operands[i] * operands[i];
} else {
// 开根号
processedValues[i] = Math.sqrt(operands[i]);
}
} else {
processedValues[i] = operands[i];
}
}
// 然后按照小学的计算逻辑处理(精确计算)
List<Double> numbers = new ArrayList<>();
List<Integer> ops = new ArrayList<>();
for (double value : processedValues) {
numbers.add(value);
}
for (int operation : operations) {
ops.add(operation);
}
// 处理乘除法(精确计算)
for (int i = 0; i < ops.size(); i++) {
int op = ops.get(i);
if (op == 2 || op == 3) {
double left = numbers.get(i);
double right = numbers.get(i + 1);
double result;
if (op == 2) {
result = left * right;
} else {
if (right == 0) return Double.NaN;
result = left / right;
}
numbers.set(i, result);
numbers.remove(i + 1);
ops.remove(i);
i--;
}
}
// 处理加减法(精确计算)
double result = numbers.get(0);
for (int i = 0; i < ops.size(); i++) {
int op = ops.get(i);
double nextNum = numbers.get(i + 1);
if (op == 0) {
result += nextNum;
} else {
result -= nextNum;
}
}
// 只在最后结果保留2位小数
return Math.round(result * 100) / 100.0;
}
// 高中题目相关方法
private String buildHighSchoolQuestionText(int[] operands, int[] operations, boolean[] hasTrigOp, int[] trigFunctions) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < operands.length; i++) {
if (i > 0) {
switch (operations[i - 1]) {
case 0: sb.append(" + "); break;
case 1: sb.append(" - "); break;
case 2: sb.append(" × "); break;
case 3: sb.append(" ÷ "); break;
}
}
if (hasTrigOp[i]) {
switch (trigFunctions[i]) {
case 0: sb.append("sin(").append(operands[i]).append("°)"); break;
case 1: sb.append("cos(").append(operands[i]).append("°)"); break;
case 2: sb.append("tan(").append(operands[i]).append("°)"); break;
}
} else {
sb.append(operands[i]);
}
}
sb.append(" = ?");
return sb.toString();
}
private double calculateHighSchoolAnswer(int[] operands, int[] operations, boolean[] hasTrigOp, int[] trigFunctions) {
// 先处理三角函数运算(精确计算)
double[] processedValues = new double[operands.length];
for (int i = 0; i < operands.length; i++) {
if (hasTrigOp[i]) {
double radians = Math.toRadians(operands[i]);
switch (trigFunctions[i]) {
case 0:
processedValues[i] = Math.sin(radians);
break;
case 1:
processedValues[i] = Math.cos(radians);
break;
case 2:
// 避免tan(90°)等无效值
if (operands[i] % 180 == 90 && operands[i] % 360 != 270) {
return Double.NaN;
}
processedValues[i] = Math.tan(radians);
break;
default:
processedValues[i] = 0;
}
} else {
processedValues[i] = operands[i];
}
}
// 然后按照常规计算逻辑处理(精确计算)
List<Double> numbers = new ArrayList<>();
List<Integer> ops = new ArrayList<>();
for (double value : processedValues) {
numbers.add(value);
}
for (int operation : operations) {
ops.add(operation);
}
// 处理乘除法(精确计算)
for (int i = 0; i < ops.size(); i++) {
int op = ops.get(i);
if (op == 2 || op == 3) {
double left = numbers.get(i);
double right = numbers.get(i + 1);
double result;
if (op == 2) {
result = left * right;
} else {
if (right == 0) return Double.NaN;
result = left / right;
}
numbers.set(i, result);
numbers.remove(i + 1);
ops.remove(i);
i--;
}
}
// 处理加减法(精确计算)
double result = numbers.get(0);
for (int i = 0; i < ops.size(); i++) {
int op = ops.get(i);
double nextNum = numbers.get(i + 1);
if (op == 0) {
result += nextNum;
} else {
result -= nextNum;
}
}
// 只在最后结果保留2位小数
return Math.round(result * 100) / 100.0;
}
// 生成选项的通用方法
private Question generateOptions(String questionText, double answer, String level) {
String[] options = new String[4];
Set<String> usedValues = new HashSet<>();
// 检查答案是否为整数
boolean isIntegerAnswer = (answer == (int) answer);
// 格式化正确答案
String correctAnswer = formatAnswer(answer);
options[0] = correctAnswer;
usedValues.add(correctAnswer);
// 生成错误答案
for (int i = 1; i < 4; i++) {
String wrongAnswer;
int attempts = 0;
do {
// 基于正确答案生成错误答案
double wrongValue;
double offset = (random.nextDouble() * 5) + 1; // 1-6的偏移量
if (isIntegerAnswer) {
// 如果答案是整数,生成整数错误答案
int intAnswer = (int) answer;
int intOffset = random.nextInt(10) + 1; // 1-10的整数偏移
if (random.nextBoolean()) {
wrongValue = intAnswer + intOffset;
} else {
wrongValue = Math.max(1, intAnswer - intOffset);
}
// 确保是整数
wrongValue = (int) wrongValue;
} else {
// 如果答案是小数,生成小数错误答案
if (random.nextBoolean()) {
wrongValue = answer + offset;
} else {
wrongValue = answer - offset;
}
// 保留2位小数
wrongValue = Math.round(wrongValue * 100) / 100.0;
}
wrongAnswer = formatAnswer(wrongValue);
attempts++;
} while (usedValues.contains(wrongAnswer) && attempts < 20);
options[i] = wrongAnswer;
usedValues.add(wrongAnswer);
}
// 打乱选项顺序
shuffleArray(options);
// 找到正确答案的新位置
int correctIndex = 0;
for (int i = 0; i < options.length; i++) {
if (options[i].equals(correctAnswer)) {
correctIndex = i;
break;
}
}
return new Question(questionText, options, correctIndex, level);
}
private String formatAnswer(double answer) {
if (answer == (int) answer) {
return String.valueOf((int) answer); // 整数不显示小数
} else {
return String.format("%.2f", answer); // 小数保留2位
}
}
private void shuffleArray(String[] array) {
for (int i = array.length - 1; i > 0; i--) {
int j = random.nextInt(i + 1);
String temp = array[i];
array[i] = array[j];
array[j] = temp;
}
}
}