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.
415 lines
13 KiB
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());
|
|
}
|
|
}
|
|
} |