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.
math-learing/src/main/java/com/personalproject/service/ExamService.java

159 lines
5.6 KiB

package com.personalproject.service;
import com.personalproject.generator.QuestionGenerator;
import com.personalproject.model.DifficultyLevel;
import com.personalproject.model.ExamSession;
import com.personalproject.model.QuizQuestion;
import com.personalproject.storage.QuestionStorageService;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
/**
* 用于管理考试会话和生成测验题目的服务类.
*/
public final class ExamService {
private static final int OPTIONS_COUNT = 4;
private final Map<DifficultyLevel, QuestionGenerator> generators;
private final Random random = new Random();
private final QuestionGenerationService questionGenerationService;
private final QuestionStorageService questionStorageService;
/**
* 创建新的考试服务.
*
* @param generatorMap 难度级别到题目生成器的映射
* @param questionGenerationService 题目生成服务
* @param questionStorageService 题目存储服务
*/
public ExamService(
Map<DifficultyLevel, QuestionGenerator> generatorMap,
QuestionGenerationService questionGenerationService,
QuestionStorageService questionStorageService) {
this.generators = new EnumMap<>(DifficultyLevel.class);
this.generators.putAll(generatorMap);
this.questionGenerationService = questionGenerationService;
this.questionStorageService = questionStorageService;
}
/**
* 使用指定参数创建新的考试会话.
*
* @param username 考生用户名
* @param difficultyLevel 考试难度级别
* @param questionCount 考试中包含的题目数量
* @return 新的考试会话
*/
public ExamSession createExamSession(
String username, DifficultyLevel difficultyLevel, int questionCount) {
if (questionCount < 10 || questionCount > 30) {
throw new IllegalArgumentException("题目数量必须在10到30之间");
}
// 根据难度级别生成题目
if (!generators.containsKey(difficultyLevel)) {
throw new IllegalArgumentException("找不到难度级别的生成器: " + difficultyLevel);
}
try {
Set<String> existingQuestions = questionStorageService.loadExistingQuestions(username);
List<String> uniqueQuestions = questionGenerationService.generateUniqueQuestions(
difficultyLevel, questionCount, existingQuestions);
List<QuizQuestion> quizQuestions = new ArrayList<>();
for (String questionText : uniqueQuestions) {
quizQuestions.add(buildQuizQuestion(questionText));
}
questionStorageService.saveQuestions(username, uniqueQuestions);
return new ExamSession(username, difficultyLevel, quizQuestions);
} catch (IOException exception) {
throw new IllegalStateException("生成考试题目失败", exception);
}
}
/**
* 使用预定义题目创建考试会话(用于测试).
*
* @param username 考生用户名
* @param difficultyLevel 考试难度级别
* @param questions 预定义题目列表
* @return 新的考试会话
*/
public ExamSession createExamSession(
String username, DifficultyLevel difficultyLevel, List<QuizQuestion> questions) {
if (questions.size() < 10 || questions.size() > 30) {
throw new IllegalArgumentException("题目数量必须在10到30之间");
}
return new ExamSession(username, difficultyLevel, questions);
}
private QuizQuestion buildQuizQuestion(String questionText) {
try {
double correctAnswer = MathExpressionEvaluator.evaluate(questionText);
String formattedCorrectAnswer = formatAnswer(correctAnswer);
List<String> options = buildOptions(correctAnswer, formattedCorrectAnswer);
int correctAnswerIndex = options.indexOf(formattedCorrectAnswer);
if (correctAnswerIndex < 0) {
options.set(0, formattedCorrectAnswer);
correctAnswerIndex = 0;
}
return new QuizQuestion(questionText, options, correctAnswerIndex);
} catch (RuntimeException exception) {
List<String> fallbackOptions = new ArrayList<>();
for (int i = 1; i <= OPTIONS_COUNT; i++) {
fallbackOptions.add("选项" + i);
}
return new QuizQuestion(questionText, fallbackOptions, 0);
}
}
private List<String> buildOptions(double correctAnswer, String formattedCorrectAnswer) {
Set<String> optionSet = new LinkedHashSet<>();
optionSet.add(formattedCorrectAnswer);
int attempts = 0;
while (optionSet.size() < OPTIONS_COUNT && attempts < 100) {
double offset = generateOffset(correctAnswer);
double candidateValue = correctAnswer + offset;
if (Double.isNaN(candidateValue) || Double.isInfinite(candidateValue)) {
attempts++;
continue;
}
String candidate = formatAnswer(candidateValue);
if (!candidate.equals(formattedCorrectAnswer)) {
optionSet.add(candidate);
}
attempts++;
}
while (optionSet.size() < OPTIONS_COUNT) {
optionSet.add(formatAnswer(correctAnswer + optionSet.size() * 1.5));
}
List<String> options = new ArrayList<>(optionSet);
Collections.shuffle(options, random);
return options;
}
private double generateOffset(double base) {
double scale = Math.max(1.0, Math.abs(base) / 2.0);
double offset = random.nextGaussian() * scale;
if (Math.abs(offset) < 0.5) {
offset += offset >= 0 ? 1.5 : -1.5;
}
return offset;
}
private String formatAnswer(double value) {
return String.format("%.2f", value);
}
}