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.
Partner_Project/src/model/QuestionMaker.java

415 lines
13 KiB

package model;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
/**
* 题目生成器,负责生成不同难度的数学题目。
*/
public class QuestionMaker {
private static final int MAX_ATTEMPTS = 100;
private static final String QUESTIONS_DIR = "questions";
private static final String FILE_SEPARATOR = File.separator;
private static final int OPTION_COUNT = 4;
private Student student;
private final Random random;
public QuestionMaker() {
this.random = new Random();
}
/**
* 生成指定数量和难度的题目。
*
* @param grade 题目难度等级
* @param sum 题目数量
* @param email 用户邮箱
* @return 题目列表
*/
public List<Question> makeQuestions(Grade grade, int sum, String email) {
initializeStudent(grade, email);
List<Question> questions = new ArrayList<>();
Set<String> existingQuestions = loadQuestions(email);
Set<String> currentQuestions = new HashSet<>();
int attempt = 0;
while (questions.size() < sum && attempt < MAX_ATTEMPTS) {
String expression = student.makeOneQuestion();
if (isUniqueExpression(expression, existingQuestions, currentQuestions)) {
tryAddQuestion(expression, grade, questions, existingQuestions,
currentQuestions);
}
attempt++;
}
saveQuestions(email, new ArrayList<>(currentQuestions));
return questions;
}
private void initializeStudent(Grade grade, String email) {
String path = QUESTIONS_DIR + FILE_SEPARATOR + email;
switch (grade) {
case primary:
student = new PrimaryMaker("primary", "", path);
break;
case middle:
student = new MiddleMaker("middle", "", path);
break;
case high:
student = new HighMaker("high", "", path);
break;
default:
throw new IllegalArgumentException("Unknown grade: " + grade);
}
}
private boolean isUniqueExpression(String expression,
Set<String> existingQuestions, Set<String> currentQuestions) {
return !existingQuestions.contains(expression)
&& !currentQuestions.contains(expression);
}
private void tryAddQuestion(String expression, Grade grade,
List<Question> questions, Set<String> existingQuestions,
Set<String> currentQuestions) {
try {
double result = evaluateExpression(expression, grade);
Question question = createQuestion(expression, result);
questions.add(question);
existingQuestions.add(expression);
currentQuestions.add(expression);
} catch (Exception e) {
// Skip invalid expressions
}
}
private double evaluateExpression(String expression, Grade grade) {
String processedExpression = preprocessExpression(expression, grade);
return parseAndEvaluate(processedExpression);
}
private String preprocessExpression(String expression, Grade grade) {
String result = expression.replace(" ", "");
if (grade == Grade.middle) {
result = preprocessMiddleSchoolExpression(result);
} else if (grade == Grade.high) {
result = preprocessHighSchoolExpression(result);
}
return result;
}
private String preprocessMiddleSchoolExpression(String expression) {
// Handle squares: (5)^2 -> (5*5) and 5^2 -> (5*5)
String result = expression.replaceAll("\\((\\d+)\\)\\^2", "($1*$1)");
result = result.replaceAll("(\\d+)\\^2", "($1*$1)");
// Handle square roots: √16 -> Math.sqrt(16)
result = result.replaceAll("√(\\d+)", "Math.sqrt($1)");
return result;
}
private String preprocessHighSchoolExpression(String expression) {
String result = expression.replaceAll("sin\\((\\d+)\\)",
"Math.sin(Math.toRadians($1))");
result = result.replaceAll("cos\\((\\d+)\\)",
"Math.cos(Math.toRadians($1))");
result = result.replaceAll("tan\\((\\d+)\\)",
"Math.tan(Math.toRadians($1))");
return result;
}
private double parseAndEvaluate(String expression) {
return new ExpressionParser(expression).parse();
}
private static class ExpressionParser {
private final String expression;
private int pos = -1;
private int ch;
ExpressionParser(String expression) {
this.expression = expression;
}
double parse() {
nextChar();
double result = parseExpression();
if (pos < expression.length()) {
throw new RuntimeException("Unexpected: " + (char) ch);
}
return result;
}
private void nextChar() {
ch = (++pos < expression.length()) ? expression.charAt(pos) : -1;
}
private boolean eat(int charToEat) {
while (ch == ' ') {
nextChar();
}
if (ch == charToEat) {
nextChar();
return true;
}
return false;
}
private double parseExpression() {
double x = parseTerm();
while (true) {
if (eat('+')) {
x += parseTerm();
} else if (eat('-')) {
x -= parseTerm();
} else {
return x;
}
}
}
private double parseTerm() {
double x = parseFactor();
while (true) {
if (eat('*')) {
x *= parseFactor();
} else if (eat('/')) {
x /= parseFactor();
} else {
return x;
}
}
}
private double parseFactor() {
if (eat('+')) {
return parseFactor();
}
if (eat('-')) {
return -parseFactor();
}
int startPos = pos;
if (eat('(')) {
return parseParenthesizedExpression();
} else if (isDigit()) {
return parseNumber(startPos);
} else if (isUpperCaseLetter()) {
return parseMathFunction(startPos);
} else if (isLowerCaseLetter()) {
return parseCustomFunction(startPos);
} else {
throw new RuntimeException("Unexpected: " + (char) ch);
}
}
private boolean isDigit() {
return (ch >= '0' && ch <= '9') || ch == '.';
}
private boolean isUpperCaseLetter() {
return ch >= 'A' && ch <= 'Z';
}
private boolean isLowerCaseLetter() {
return ch >= 'a' && ch <= 'z';
}
private double parseParenthesizedExpression() {
double x = parseExpression();
eat(')');
return x;
}
private double parseNumber(int startPos) {
while (isDigit()) {
nextChar();
}
return Double.parseDouble(expression.substring(startPos, pos));
}
private double parseMathFunction(int startPos) {
while ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch == '.') {
nextChar();
}
String func = expression.substring(startPos, pos);
eat('(');
double x = parseExpression();
eat(')');
return applyMathFunction(func, x);
}
private double parseCustomFunction(int startPos) {
while (ch >= 'a' && ch <= 'z') {
nextChar();
}
String function = expression.substring(startPos, pos);
double x = parseFactor();
return applyCustomFunction(function, x);
}
private double applyMathFunction(String func, double value) {
switch (func) {
case "Math.sqrt":
return Math.sqrt(value);
case "Math.sin":
return Math.sin(value);
case "Math.cos":
return Math.cos(value);
case "Math.tan":
return Math.tan(value);
case "Math.toRadians":
return Math.toRadians(value);
default:
throw new RuntimeException("Unknown function: " + func);
}
}
private double applyCustomFunction(String function, double value) {
switch (function) {
case "sqrt":
return Math.sqrt(value);
case "sin":
return Math.sin(value);
case "cos":
return Math.cos(value);
case "tan":
return Math.tan(value);
default:
throw new RuntimeException("Unknown function: " + function);
}
}
}
private Question createQuestion(String expression, double result) {
String text = "计算: " + expression + " = ? ";
double[] options = generateOptions(result);
int correctIndex = random.nextInt(OPTION_COUNT);
options[correctIndex] = result;
String[] optionStrings = formatOptions(options);
return new Question(expression, text, optionStrings, correctIndex);
}
private double[] generateOptions(double correctAnswer) {
double[] options = new double[OPTION_COUNT];
for (int i = 0; i < OPTION_COUNT; i++) {
double offset = random.nextDouble() * 20 - 10;
if (Math.abs(offset) < 1) {
offset = random.nextBoolean() ? 5 : -5;
}
options[i] = offset + correctAnswer;
}
return options;
}
private String[] formatOptions(double[] options) {
String[] optionStrings = new String[OPTION_COUNT];
for (int i = 0; i < OPTION_COUNT; i++) {
optionStrings[i] = String.format("%c. %.2f",
(char) ('A' + i), options[i]);
}
return optionStrings;
}
private Set<String> loadQuestions(String email) {
Set<String> questions = new HashSet<>();
File userDir = getUserDirectory(email);
if (!userDir.exists() || !userDir.isDirectory()) {
return questions;
}
File[] files = userDir.listFiles((dir, name) -> name.endsWith(".txt"));
if (files == null) {
return questions;
}
for (File file : files) {
loadQuestionsFromFile(file, questions);
}
return questions;
}
private void loadQuestionsFromFile(File file, Set<String> questions) {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
while ((line = reader.readLine()) != null) {
String question = extractQuestion(line);
if (question != null) {
questions.add(question);
}
}
} catch (IOException e) {
System.err.println("读取文件失败: " + e.getMessage());
}
}
private String extractQuestion(String line) {
line = line.trim();
if (line.matches("\\d+\\.\\s+.*")) {
return line.substring(line.indexOf('.') + 1).trim();
}
return null;
}
private void saveQuestions(String email, List<String> questions) {
File userDir = getUserDirectory(email);
ensureDirectoryExists(userDir);
String fileName = generateFileName();
File file = new File(userDir, fileName);
writeQuestionsToFile(file, questions);
}
private File getUserDirectory(String email) {
return new File(QUESTIONS_DIR + FILE_SEPARATOR + email);
}
private void ensureDirectoryExists(File directory) {
if (!directory.exists()) {
if (!directory.mkdirs()) {
System.err.println("创建目录失败");
}
}
}
private String generateFileName() {
LocalDateTime now = LocalDateTime.now();
return now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) + ".txt";
}
private void writeQuestionsToFile(File file, List<String> questions) {
try (PrintWriter writer = new PrintWriter(new FileWriter(file))) {
for (int i = 0; i < questions.size(); i++) {
writer.println((i + 1) + ". " + questions.get(i));
if (i < questions.size() - 1) {
writer.println();
}
}
} catch (IOException e) {
System.err.println("保存文件失败: " + e.getMessage());
}
}
}