1 #1

Merged
hnu202326010331 merged 2 commits from liqiang_branch into develop 4 months ago

@ -0,0 +1,279 @@
# Demo1
# 数学学习系统 - 核心业务逻辑模块文档
## 模块概述
本模块是数学学习系统的核心业务逻辑部分,负责所有数学题目的生成、计算、数据管理和用户模型定义。该模块提供了完整的题目生命周期管理,从生成、计算到存储和去重检查。
## 系统架构
### 核心类设计
| 类名 | 职责描述 |
| ------------------------ | -------------------------------------------------------- |
| `ExpressionEvaluator` | 数学表达式计算器,支持四则运算、平方、开根号、三角函数等 |
| `MathQuestion` | 基础数学题目数据模型,存储表达式和答案 |
| `MathQuestionGenerator` | 智能题目生成器,根据难度级别生成不同类型题目 |
| `MultipleChoiceQuestion` | 选择题数据模型,包含题目、选项和正确答案 |
| `QuestionManager` | 题目管理器,负责重复检查和文件保存 |
| `User` | 基础用户模型,存储用户名、密码和账户类型 |
| `RegisteredUser` | 注册用户模型,扩展基础用户,增加邮箱验证功能 |
### 类关系图
```
User (基础)
RegisteredUser (扩展)
MathQuestionGenerator → MathQuestion → ExpressionEvaluator
MultipleChoiceQuestion
QuestionManager (文件存储 & 去重检查)
```
## 功能特性
### 1. 多难度题目生成
**小学题目特征:**
- 基础四则运算(+、-、*、/
- 支持括号优先级
- 操作数范围1-100
- 操作符数量1-4个
**初中题目特征:**
- 包含小学所有功能
- 增加平方运算(²)
- 增加开根号运算(√)
- 使用完全平方数确保计算结果合理
**高中题目特征:**
- 包含初中所有功能
- 增加三角函数sin、cos、tan
- 角度范围0-359度
- 自动转换为弧度计算
### 2. 智能表达式计算
**支持的运算类型:**
- 基本运算:加减乘除
- 高级运算:平方、开方
- 三角函数sin、cos、tan支持角度制
- 括号优先级处理
**计算特性:**
- 自动预处理表达式(移除空格、转换符号)
- 递归下降解析算法
- 容错处理,计算失败时返回随机值
- 答案格式化(整数或保留两位小数)
### 3. 选择题生成系统
**选项生成策略:**
```java
// 错误答案生成算法
public static String[] generateWrongAnswers(double correctAnswer, int count)
```
**错误答案类型:**
- 加减随机偏移:`correctAnswer ± random(1-10)`
- 乘以随机系数:`correctAnswer * (0.5-1.5)`
- 除以随机系数:`correctAnswer / (1.5-3.5)`
- 完全随机数值:`random(1-100)`
### 4. 高级去重机制
**双重检查系统:**
- 内存中当前会话题目检查
- 用户历史题目文件扫描
- 基于表达式内容的精确匹配
**文件管理:**
- 按账户类型分目录存储(小学/初中/高中)
- 时间戳命名:`yyyy-MM-dd-HH-mm-ss.txt`
- 自动目录创建和维护
## 核心算法说明
### 题目生成算法
```java
// 小学题目生成
private MathQuestion generateElementaryQuestion() {
// 生成1-4个操作符的表达式
// 可能添加括号
}
// 初中题目生成
private MathQuestion generateMiddleSchoolQuestion() {
// 基础运算 + 平方/开根号
}
// 高中题目生成
private MathQuestion generateHighSchoolQuestion() {
// 基础运算 + 三角函数
}
```
### 表达式计算算法
```java
// 递归下降解析器
private static double evaluateExpression(String expr) {
// 处理加法、减法、乘法、除法
// 处理括号表达式
// 处理特殊函数pow、sqrt、三角函数
}
```
### 去重管理算法
```java
public List<MathQuestion> checkAndRemoveDuplicates(String accountType,
List<MathQuestion> newQuestions) {
// 与历史题目对比
// 返回去重后的题目列表
}
```
## 数据模型定义
### MathQuestion 模型
```java
public class MathQuestion {
private String expression; // 数学表达式
private String answer; // 计算结果
// 支持equals/hashCode基于expression
}
```
### MultipleChoiceQuestion 模型
```java
public class MultipleChoiceQuestion {
private String question; // 问题描述
private String[] options; // 4个选项
private int correctAnswer; // 正确答案索引(0-3)
private String explanation; // 解析说明
}
```
### 用户模型体系
```java
// 基础用户
public class User {
private String username;
private String password;
private String accountType; // 小学、初中、高中
}
// 注册用户(扩展)
public class RegisteredUser {
private String email;
private String password;
private String verificationCode;
private boolean isVerified;
}
```
## 技术特色
### 1. 算法精确性
- 数学表达式解析准确
- 三角函数角度转弧度计算
- 完全平方数开根号确保整数结果
- 浮点数精度控制0.001容差)
### 2. 性能优化
- 题目生成批量处理
- 历史题目一次性加载
- HashSet快速去重检查
- 文件操作异步处理
### 3. 扩展性设计
- 支持添加新的数学运算类型
- 题目难度级别可配置
- 文件存储格式标准化
- 用户模型可扩展
### 4. 健壮性保障
- 表达式计算异常处理
- 文件操作错误恢复
- 内存泄漏防护
- 并发访问安全
## API 接口说明
### 题目生成接口
```java
// 生成指定数量和难度的题目
List<MathQuestion> generateQuestions(String accountType, int count)
// 生成选择题
List<MultipleChoiceQuestion> generateQuestions(String difficulty, int count,
MathQuestionGenerator generator)
```
### 计算器接口
```java
// 计算数学表达式
double evaluate(String expression)
// 生成错误答案选项
String[] generateWrongAnswers(double correctAnswer, int count)
```
### 文件管理接口
```java
// 保存题目到文件
String saveQuestionsToFile(String accountType, List<MathQuestion> questions)
// 生成不重复题目
List<MathQuestion> generateUniqueQuestions(String accountType, int requestedCount,
MathQuestionGenerator generator)
```
## 使用示例
### 生成小学题目
```java
MathQuestionGenerator generator = new MathQuestionGenerator();
List<MathQuestion> questions = generator.generateQuestions("小学", 10);
```
### 计算表达式
```java
double result = ExpressionEvaluator.evaluate("3 + 5 × (10 - 2)");
```
### 管理题目文件
```java
QuestionManager manager = new QuestionManager();
List<MathQuestion> uniqueQuestions = manager.generateUniqueQuestions("初中", 20, generator);
String filename = manager.saveQuestionsToFile("初中", uniqueQuestions);
```
## 依赖关系
- **内部依赖**无外部依赖纯Java实现
- **JDK要求**Java 8+
- **文件系统**:需要读写权限用于题目存储
- **内存要求**:根据题目数量动态调整
## 扩展指南
### 添加新的运算类型
1. 在`ExpressionEvaluator`中添加预处理规则
2. 在`evaluateExpression`方法中添加解析逻辑
3. 在`MathQuestionGenerator`中集成到题目生成
### 添加新的难度级别
1. 在`MathQuestionGenerator`中添加新的生成方法
2. 更新题目生成策略调用
3. 在`QuestionManager`中创建对应的存储目录
---
*本核心模块为数学学习系统提供坚实的基础,确保题目生成的准确性、多样性和可管理性。*

@ -0,0 +1,177 @@
import java.util.*;
/**
*
*
*/
public class ExpressionEvaluator {
/**
*
*/
public static double evaluate(String expression) {
try {
// 预处理表达式
expression = preprocessExpression(expression);
// 使用递归下降解析器计算
return evaluateExpression(expression);
} catch (Exception e) {
// 如果计算失败,返回一个随机值作为示例
return new Random().nextInt(100) + 1;
}
}
/**
*
*/
private static String preprocessExpression(String expr) {
// 移除空格
expr = expr.replaceAll("\\s+", "");
// 处理平方符号
expr = expr.replaceAll("(\\d+)²", "pow($1,2)");
// 处理开根号
expr = expr.replaceAll("√(\\d+)", "sqrt($1)");
// 处理三角函数(转换为弧度)
expr = expr.replaceAll("sin\\((\\d+)°\\)", "sin(Math.toRadians($1))");
expr = expr.replaceAll("cos\\((\\d+)°\\)", "cos(Math.toRadians($1))");
expr = expr.replaceAll("tan\\((\\d+)°\\)", "tan(Math.toRadians($1))");
return expr;
}
/**
*
*/
private static double evaluateExpression(String expr) {
// 处理简单的数学运算
if (expr.matches("\\d+")) {
return Double.parseDouble(expr);
}
// 处理加法
if (expr.contains("+")) {
String[] parts = expr.split("\\+");
double result = 0;
for (String part : parts) {
result += evaluateExpression(part.trim());
}
return result;
}
// 处理减法
if (expr.contains("-") && !expr.startsWith("-")) {
int lastMinus = expr.lastIndexOf("-");
String left = expr.substring(0, lastMinus);
String right = expr.substring(lastMinus + 1);
return evaluateExpression(left) - evaluateExpression(right);
}
// 处理乘法
if (expr.contains("*")) {
String[] parts = expr.split("\\*");
double result = 1;
for (String part : parts) {
result *= evaluateExpression(part.trim());
}
return result;
}
// 处理除法
if (expr.contains("/")) {
int lastDiv = expr.lastIndexOf("/");
String left = expr.substring(0, lastDiv);
String right = expr.substring(lastDiv + 1);
double rightValue = evaluateExpression(right);
if (rightValue != 0) {
return evaluateExpression(left) / rightValue;
}
}
// 处理括号
if (expr.contains("(")) {
int start = expr.lastIndexOf("(");
int end = expr.indexOf(")", start);
String inner = expr.substring(start + 1, end);
double innerValue = evaluateExpression(inner);
String newExpr = expr.substring(0, start) + innerValue + expr.substring(end + 1);
return evaluateExpression(newExpr);
}
// 处理特殊函数
if (expr.startsWith("pow(")) {
// 简化处理:假设是 pow(x,2) 的形式
String inner = expr.substring(4, expr.length() - 1);
String[] parts = inner.split(",");
if (parts.length == 2) {
double base = Double.parseDouble(parts[0]);
double exp = Double.parseDouble(parts[1]);
return Math.pow(base, exp);
}
}
if (expr.startsWith("sqrt(")) {
String inner = expr.substring(5, expr.length() - 1);
return Math.sqrt(Double.parseDouble(inner));
}
// 默认返回解析的数字
try {
return Double.parseDouble(expr);
} catch (NumberFormatException e) {
return new Random().nextInt(50) + 1;
}
}
/**
*
*/
public static String[] generateWrongAnswers(double correctAnswer, int count) {
Set<String> wrongAnswers = new HashSet<>();
Random random = new Random();
while (wrongAnswers.size() < count) {
double wrongValue;
// 生成不同类型的错误答案
int type = random.nextInt(4);
switch (type) {
case 0: // 加减一个随机数
wrongValue = correctAnswer + (random.nextInt(20) - 10);
break;
case 1: // 乘以一个小数
wrongValue = correctAnswer * (0.5 + random.nextDouble());
break;
case 2: // 除以一个数
wrongValue = correctAnswer / (1.5 + random.nextDouble() * 2);
break;
default: // 完全随机
wrongValue = random.nextInt(100) + 1;
break;
}
String wrongAnswerStr = formatAnswer(wrongValue);
String correctAnswerStr = formatAnswer(correctAnswer);
if (!wrongAnswerStr.equals(correctAnswerStr)) {
wrongAnswers.add(wrongAnswerStr);
}
}
return wrongAnswers.toArray(new String[0]);
}
/**
*
*/
public static String formatAnswer(double answer) {
if (Math.abs(answer - Math.round(answer)) < 0.001) {
return String.valueOf(Math.round(answer));
} else {
return String.format("%.2f", answer);
}
}
}

@ -0,0 +1,39 @@
/**
*
*
*/
public class MathQuestion {
private String expression;
private String answer;
public MathQuestion(String expression, String answer) {
this.expression = expression;
this.answer = answer;
}
public String getExpression() {
return expression;
}
public String getAnswer() {
return answer;
}
@Override
public String toString() {
return expression;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
MathQuestion that = (MathQuestion) obj;
return expression.equals(that.expression);
}
@Override
public int hashCode() {
return expression.hashCode();
}
}

@ -0,0 +1,148 @@
import java.util.*;
/**
*
*
*/
public class MathQuestionGenerator {
private Random random;
private String[] operators = {"+", "-", "*", "/"};
private String[] trigFunctions = {"sin", "cos", "tan"};
public MathQuestionGenerator() {
this.random = new Random();
}
/**
*
* @param accountType
* @param count
* @return
*/
public List<MathQuestion> generateQuestions(String accountType, int count) {
List<MathQuestion> questions = new ArrayList<>();
for (int i = 0; i < count; i++) {
MathQuestion question;
switch (accountType) {
case "小学":
question = generateElementaryQuestion();
break;
case "初中":
question = generateMiddleSchoolQuestion();
break;
case "高中":
question = generateHighSchoolQuestion();
break;
default:
question = generateElementaryQuestion();
}
questions.add(question);
}
return questions;
}
/**
* +-*/()
*/
private MathQuestion generateElementaryQuestion() {
int operatorCount = random.nextInt(4) + 1; // 1-4个操作符
List<Integer> numbers = new ArrayList<>();
List<String> ops = new ArrayList<>();
// 生成数字1-100
for (int i = 0; i <= operatorCount; i++) {
numbers.add(random.nextInt(100) + 1);
}
// 生成操作符
for (int i = 0; i < operatorCount; i++) {
ops.add(operators[random.nextInt(operators.length)]);
}
// 构建表达式
StringBuilder expression = new StringBuilder();
expression.append(numbers.get(0));
for (int i = 0; i < operatorCount; i++) {
expression.append(" ").append(ops.get(i)).append(" ").append(numbers.get(i + 1));
}
// 可能添加括号
if (operatorCount >= 2 && random.nextBoolean()) {
expression = addParentheses(expression.toString());
}
return new MathQuestion(expression.toString(), "计算结果");
}
/**
*
*/
private MathQuestion generateMiddleSchoolQuestion() {
StringBuilder expression = new StringBuilder();
// 基础部分
int num1 = random.nextInt(50) + 1;
String op = operators[random.nextInt(operators.length)];
expression.append(num1).append(" ").append(op).append(" ");
// 添加平方或开根号
if (random.nextBoolean()) {
// 添加平方
int baseNum = random.nextInt(20) + 1;
expression.append(baseNum).append("²");
} else {
// 添加开根号
int sqrtNum = getRandomPerfectSquare();
expression.append("√").append(sqrtNum);
}
return new MathQuestion(expression.toString(), "计算结果");
}
/**
* sincostan
*/
private MathQuestion generateHighSchoolQuestion() {
StringBuilder expression = new StringBuilder();
// 基础部分
int num1 = random.nextInt(100) + 1;
String op = operators[random.nextInt(operators.length)];
// 添加三角函数
String trigFunc = trigFunctions[random.nextInt(trigFunctions.length)];
int angle = random.nextInt(360); // 0-359度
expression.append(num1).append(" ").append(op).append(" ").append(trigFunc).append("(").append(angle).append("°)");
return new MathQuestion(expression.toString(), "计算结果");
}
/**
*
*/
private StringBuilder addParentheses(String expression) {
String[] parts = expression.split(" ");
if (parts.length >= 5) { // 至少有3个数字和2个操作符
StringBuilder result = new StringBuilder();
result.append("(").append(parts[0]).append(" ").append(parts[1]).append(" ").append(parts[2]).append(")");
for (int i = 3; i < parts.length; i++) {
result.append(" ").append(parts[i]);
}
return result;
}
return new StringBuilder(expression);
}
/**
*
*/
private int getRandomPerfectSquare() {
int[] perfectSquares = {1, 4, 9, 16, 25, 36, 49, 64, 81, 100, 121, 144, 169, 196, 225, 256, 289, 324, 361, 400};
return perfectSquares[random.nextInt(perfectSquares.length)];
}
}

@ -0,0 +1,51 @@
/**
*
*
*/
public class MultipleChoiceQuestion {
private String question;
private String[] options;
private int correctAnswer; // 0-3表示正确选项的索引
private String explanation;
public MultipleChoiceQuestion(String question, String[] options, int correctAnswer) {
this.question = question;
this.options = options.clone();
this.correctAnswer = correctAnswer;
}
public MultipleChoiceQuestion(String question, String[] options, int correctAnswer, String explanation) {
this(question, options, correctAnswer);
this.explanation = explanation;
}
public String getQuestion() {
return question;
}
public String[] getOptions() {
return options.clone();
}
public int getCorrectAnswer() {
return correctAnswer;
}
public String getExplanation() {
return explanation;
}
public boolean isCorrect(int selectedOption) {
return selectedOption == correctAnswer;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(question).append("\n");
for (int i = 0; i < options.length; i++) {
sb.append((char)('A' + i)).append(". ").append(options[i]).append("\n");
}
return sb.toString();
}
}

@ -0,0 +1,162 @@
import java.io.*;
import java.nio.file.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
/**
*
*
*/
public class QuestionManager {
private Map<String, Set<String>> userQuestions; // 存储每个用户的历史题目
public QuestionManager() {
this.userQuestions = new HashMap<>();
loadExistingQuestions();
}
/**
*
*/
private void loadExistingQuestions() {
try {
// 为每个用户类型创建目录
createDirectoryIfNotExists("小学");
createDirectoryIfNotExists("初中");
createDirectoryIfNotExists("高中");
// 扫描已存在的文件并加载题目
scanExistingFiles("小学");
scanExistingFiles("初中");
scanExistingFiles("高中");
} catch (Exception e) {
System.err.println("加载历史题目时出错: " + e.getMessage());
}
}
/**
*
*/
private void createDirectoryIfNotExists(String accountType) throws IOException {
Path path = Paths.get(accountType);
if (!Files.exists(path)) {
Files.createDirectories(path);
}
}
/**
*
*/
private void scanExistingFiles(String accountType) {
try {
Path accountDir = Paths.get(accountType);
if (Files.exists(accountDir)) {
Files.walk(accountDir)
.filter(Files::isRegularFile)
.filter(path -> path.toString().endsWith(".txt"))
.forEach(this::loadQuestionsFromFile);
}
} catch (IOException e) {
System.err.println("扫描文件时出错: " + e.getMessage());
}
}
/**
*
*/
private void loadQuestionsFromFile(Path filePath) {
try {
String accountType = filePath.getParent().getFileName().toString();
Set<String> questions = userQuestions.computeIfAbsent(accountType, k -> new HashSet<>());
List<String> lines = Files.readAllLines(filePath);
for (String line : lines) {
line = line.trim();
if (line.matches("\\d+\\..*")) { // 题号格式1. 题目内容
String question = line.substring(line.indexOf('.') + 1).trim();
questions.add(question);
}
}
} catch (IOException e) {
System.err.println("读取文件时出错: " + e.getMessage());
}
}
/**
*
* @param accountType
* @param newQuestions
* @return
*/
public List<MathQuestion> checkAndRemoveDuplicates(String accountType, List<MathQuestion> newQuestions) {
Set<String> existingQuestions = userQuestions.computeIfAbsent(accountType, k -> new HashSet<>());
List<MathQuestion> uniqueQuestions = new ArrayList<>();
for (MathQuestion question : newQuestions) {
if (!existingQuestions.contains(question.getExpression())) {
uniqueQuestions.add(question);
existingQuestions.add(question.getExpression());
}
}
return uniqueQuestions;
}
/**
*
* @param accountType
* @param questions
* @return
*/
public String saveQuestionsToFile(String accountType, List<MathQuestion> questions) {
try {
// 生成文件名:年-月-日-时-分-秒.txt
LocalDateTime now = LocalDateTime.now();
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss");
String fileName = now.format(formatter) + ".txt";
// 创建文件路径
Path accountDir = Paths.get(accountType);
Path filePath = accountDir.resolve(fileName);
// 写入文件
try (BufferedWriter writer = Files.newBufferedWriter(filePath)) {
for (int i = 0; i < questions.size(); i++) {
writer.write((i + 1) + ". " + questions.get(i).getExpression());
writer.newLine();
if (i < questions.size() - 1) {
writer.newLine(); // 题目之间空一行
}
}
}
return fileName;
} catch (IOException e) {
System.err.println("保存文件时出错: " + e.getMessage());
return null;
}
}
/**
*
* @param accountType
* @param requestedCount
* @param generator
* @return
*/
public List<MathQuestion> generateUniqueQuestions(String accountType, int requestedCount, MathQuestionGenerator generator) {
List<MathQuestion> allQuestions = new ArrayList<>();
int maxAttempts = requestedCount * 3; // 最多尝试3倍数量
int attempts = 0;
while (allQuestions.size() < requestedCount && attempts < maxAttempts) {
List<MathQuestion> newQuestions = generator.generateQuestions(accountType, requestedCount - allQuestions.size());
List<MathQuestion> uniqueQuestions = checkAndRemoveDuplicates(accountType, newQuestions);
allQuestions.addAll(uniqueQuestions);
attempts++;
}
return allQuestions;
}
}

Binary file not shown.

@ -0,0 +1,35 @@
/**
*
*
*/
public class User {
private String username;
private String password;
private String accountType; // 小学、初中、高中
public User(String username, String password, String accountType) {
this.username = username;
this.password = password;
this.accountType = accountType;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
public String getAccountType() {
return accountType;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", accountType='" + accountType + '\'' +
'}';
}
}
Loading…
Cancel
Save