Version1.0 #1

Merged
hnu202326010425 merged 13 commits from develop into main 5 months ago

@ -0,0 +1,3 @@
Manifest-Version: 1.0
Main-Class: src.Main
Class-Path: .

@ -0,0 +1,19 @@
@echo off
REM Math Question Generator System - Windows Batch File
REM Author: 赵俊杰
REM Created: 2025
@echo off
REM Set console encoding to GBK to match scanner input
REM GBK code page is 936, which supports Chinese characters
chcp 936 >nul
echo Starting Math Question Generator System...
echo.
REM Execute Java program
java -jar MathQuestionGenerator.jar
REM Pause after program execution to prevent window from closing immediately
pause

Binary file not shown.

@ -1,2 +1 @@
# Personal_Project

@ -0,0 +1,304 @@
# 数学题目生成系统 - 项目说明
## 项目概述
本项目是一个基于Java的数学题目生成系统专门为不同学段小学、初中、高中的教师设计。系统能够根据教师类型自动生成相应难度的数学题目并支持题目查重、文件存储等功能。
## 项目结构
```
Personal_Project/
├── src/ # 源代码目录
│ ├── Main.java # 程序入口类
│ ├── SysFunc.java # 系统功能类(登录、操作界面)
│ ├── Teacher.java # 教师抽象基类
│ ├── PrimaryTea.java # 小学教师类
│ ├── JuniorTea.java # 初中教师类
│ └── SeniorTea.java # 高中教师类
├── doc/ # 文档目录
│ └── 项目说明.md # 项目说明文档
├── MANIFEST.MF # JAVA JAR清单文件
├── 运行程序.bat # Windows批处理文件
├── 张三1/ # 教师个人文件夹(自动生成)
├── 张三2/
├── 张三3/
├── 李四1/
├── 李四2/
├── 李四3/
├── 王五1/
├── 王五2/
└── 王五3/
```
## 核心功能
### 1. 用户管理
- **预置用户**系统预置了9名教师用户3名小学、3名初中、3名高中
- **登录验证**:支持用户名和密码验证登录
- **个人文件夹**:每个教师登录后自动创建个人文件夹用于存储生成的题目
### 2. 题目生成
- **学段适配**:根据教师类型生成相应难度的数学题目
- **类型切换**:可以切换指定类型出题
- **题目数量**支持一次性生成10-30道题目
- **随机生成**:题目内容和结构随机生成,确保多样性
### 3. 题目类型
- **小学题目**:包含加减乘除四则运算,支持括号
- **初中题目**:在小学基础上增加平方(^2)和开方(√)运算
- **高中题目**:在初中基础上增加三角函数(sin, cos, tan)运算
### 4. 查重机制
- **自动查重**:生成题目时会检查是否与个人历史题目重复
- **文件扫描**:扫描教师个人文件夹中的所有历史题目文件
- **重复处理**:发现重复题目会自动重新生成
### 5. 文件管理
- **自动存储**:生成的题目自动保存到教师个人文件夹
- **时间戳命名**:文件名包含生成时间,便于管理
- **格式规范**:题目按序号排列,格式统一
## 技术特点
### 面向对象设计
- **继承体系**:使用抽象类`Teacher`作为基类,三个具体子类实现不同学段的题目生成
- **统一方法**:由'Teacher'实现子类都具有的类似结构
- **多态应用**:通过抽象方法实现不同学段题目的差异化生成
- **封装性**:将用户信息、文件操作等功能封装在相应的类中
### 算法设计
- **随机算法**使用Java Random类确保题目的随机性
- **括号处理**:智能添加括号,保证表达式合理性
- **操作符分布**:合理分布不同难度的操作符
### 文件操作
- **NIO API**使用Java NIO进行高效的文件读写操作
- **异常处理**:完善的异常处理机制确保程序稳定性
- **路径管理**:自动创建和管理教师个人文件夹以及题目文档
## 使用流程
1. **保存文件**:将项目保存至同一目录
2. **启动程序**:在当前目录下双击运行 “运行程序.bat”
3. **用户登录**:根据提示输入预置的用户名和密码(格式:用户名 密码)
4. **选择模式**:系统显示当前出题类型,可选择切换
5. **切换类型**:输入" 切换** "切换至对应出题类型
6. **生成题目**输入题目数量10-30
7. **查看结果**:题目生成后显示保存路径
8. **重复使用**:题目生成后可选择继续生成或切换难度
9. **退出登录**:输入题目数量为-1则退出当前用户可重新登录
10. **退出系统**:可选返回登陆或者退出系统
## 预置用户信息
| 用户名 | 密码 | 类型 | 初始类型 |
|--------|------|------|----------|
| 张三1 | 123 | 小学教师 | 小学 |
| 张三2 | 123 | 小学教师 | 小学 |
| 张三3 | 123 | 小学教师 | 小学 |
| 李四1 | 123 | 初中教师 | 初中 |
| 李四2 | 123 | 初中教师 | 初中 |
| 李四3 | 123 | 初中教师 | 初中 |
| 王五1 | 123 | 高中教师 | 高中 |
| 王五2 | 123 | 高中教师 | 高中 |
| 王五3 | 123 | 高中教师 | 高中 |
## API文档
### Teacher 抽象基类
#### 构造函数
```java
public Teacher(String name, String password, String path)
```
- **参数**
- `name`: 教师用户名
- `password`: 教师密码
- `path`: 教师文件夹路径可为null自动创建
#### 公共方法
```java
public boolean checkDuplicate(String question)
```
- **功能**: 检查题目是否与历史题目重复
- **参数**: `question` - 待检查的题目字符串
- **返回**: `true` - 存在重复,`false` - 无重复
```java
public String generatePath()
```
- **功能**: 生成题目文件的相对存储路径
- **返回**: 包含时间戳的文件路径
```java
public String generateEX(int num)
```
- **功能**: 生成指定数量的题目并保存到文件
- **参数**: `num` - 题目数量10-30
- **返回**: 保存题目的文件路径
#### 抽象方法
```java
protected abstract String generateSingleQuestion()
```
- **功能**: 生成单个题目(由子类实现)
- **返回**: 题目字符串
```java
public abstract String getType()
```
- **功能**: 返回教师类型
- **返回**: "小学"、"初中"或"高中"
### SysFunc 系统功能类
#### 静态字段
```java
static final int LEAST_NUM = 10 // 最小题目数量
static final int MAX_NUM = 30 // 最大题目数量
static ArrayList<Teacher> teachers // 用户列表
static Teacher teacher // 当前用户
static HashMap<String, Integer> typeMap // 类型映射
```
#### 静态方法
```java
public static void init()
```
- **功能**: 初始化系统,预加载用户数据
```java
public static Teacher login(Scanner scanner)
```
- **功能**: 用户登录界面
- **参数**: `scanner` - 输入扫描器
- **返回**: 登录成功的教师对象
```java
public static void ShiftType(Scanner scanner)
```
- **功能**: 切换出题类型界面
- **参数**: `scanner` - 输入扫描器
```java
public static void Operate(Scanner scanner)
```
- **功能**: 主操作界面,处理题目生成流程
- **参数**: `scanner` - 输入扫描器
### PrimaryTea 小学教师类
#### 构造函数
```java
public PrimaryTea(String name, String password, String path)
```
#### 特有方法
```java
public void getRandom()
```
- **功能**: 生成随机操作数和括号位置
#### 重写方法
```java
@Override
protected String generateSingleQuestion()
```
- **功能**: 生成小学难度数学题目(四则运算)
```java
@Override
public String getType()
```
- **返回**: "小学"
### JuniorTea 初中教师类
#### 构造函数
```java
public JuniorTea(String name, String password, String path)
```
#### 特有方法
```java
public void getRandom()
```
- **功能**: 生成随机操作数、特殊运算符和括号位置
#### 重写方法
```java
@Override
protected String generateSingleQuestion()
```
- **功能**: 生成初中难度数学题目(四则运算 + 平方/开方)
```java
@Override
public String getType()
```
- **返回**: "初中"
### SeniorTea 高中教师类
#### 构造函数
```java
public SeniorTea(String name, String password, String path)
```
#### 特有方法
```java
public void getRandom()
```
- **功能**: 生成随机操作数、三角函数和括号位置
#### 重写方法
```java
@Override
protected String generateSingleQuestion()
```
- **功能**: 生成高中难度数学题目(四则运算 + 三角函数)
```java
@Override
public String getType()
```
- **返回**: "高中"
### Main 程序入口类
#### 主方法
```java
public static void main(String[] args)
```
- **功能**: 程序入口点,启动数学题目生成系统
- **流程**:
1. 初始化系统
2. 创建GBK编码的Scanner
3. 进入主循环(登录 → 操作 → 退出选择)
## 扩展性
系统具有良好的扩展性,可以通过以下方式增强功能:
- 添加用户注册功能
- 添加新的学段类型
- 增加新的数学运算类型
- 支持自定义题目难度
- 添加题目答案计算功能
- 支持批量导入导出
## 开发环境
- **语言**Java21.0.4
- **编码**标准输入scanner为GBK支持中文输入,文件保存为UTF-8
- **依赖**纯Java标准库无需额外依赖
## 项目价值
本项目展示了面向对象编程的实际应用,体现了良好的软件工程实践,包括:
- 清晰的代码结构
- 符合Google编码规范的设计编写
- 合理,扩展性良好的类设计
- 完善的异常处理
- 用户友好的交互界面
- 实用的业务功能
该系统可以实际应用于教育场景,帮助教师快速生成练习题,提高教学效率。

Binary file not shown.

@ -0,0 +1,77 @@
package src;
public class JuniorTea extends Teacher{
final static String[] binaryOps = {"+", "-", "*", "/"}; // 操作符
final static String[] unaryOps = {"^2", "√"};
int operandCount;
String[] questionParts; // 操作数
boolean[] specialOpFlags; // 特殊操作符索引序列
int parenStart;
int parenEnd;
public JuniorTea(String name, String password, String path) {
super(name, password, path);
}
// 数据预处理
public void getRandom(){
this.operandCount = random.nextInt(4) + 2;
this.questionParts = new String[this.operandCount];
// 随机决定特殊操作符的数量和位置
int specialNum = Math.min(this.operandCount, random.nextInt(this.operandCount) + 1);
this.specialOpFlags = new boolean[this.operandCount];
for (int i = 0; i < specialNum; i++) {
int pos;
do {
pos = random.nextInt(this.operandCount);
} while (this.specialOpFlags[pos]);
this.specialOpFlags[pos] = true;
}
for (int i = 0; i < this.operandCount; i++) {
int operand = random.nextInt(100) + 1;
if (this.specialOpFlags[i]) {
String op = unaryOps[random.nextInt(unaryOps.length)];
if (op.equals("√")) {
this.questionParts[i] = op + operand;
} else {
this.questionParts[i] = operand + op;
}
} else {
this.questionParts[i] = String.valueOf(operand);
}
}
// 生成有效括号
boolean useParen = this.operandCount > 2 && random.nextBoolean();
this.parenStart = 0;
this.parenEnd = this.operandCount - 1;
while (this.parenStart == 0 && this.parenEnd == this.operandCount - 1) {
this.parenStart = useParen ? random.nextInt(this.operandCount - 1) : -2;
this.parenEnd = useParen ? random.nextInt(this.operandCount - 1 - this.parenStart) + this.parenStart + 1 : -2;
}
}
@Override
protected String generateSingleQuestion(){
getRandom();
StringBuilder question = new StringBuilder();
for (int i = 0; i < operandCount; i++) {
if (i == parenStart) {
question.append("(");
}
question.append(questionParts[i]);
if (i == parenEnd) {
question.append(")");
}
if (i < operandCount - 1) {
question.append(" ").append(binaryOps[random.nextInt(binaryOps.length)]).append(" ");
}
}
return question.toString();
}
@Override
public String getType(){
return "初中";
}
}

Binary file not shown.

@ -0,0 +1,34 @@
package src;
import java.util.Scanner;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args) {
// 用户预加载
SysFunc.init();
Scanner scanner = new Scanner(System.in, "GBK");
while(true){
// 登录界面
SysFunc.login(scanner);
// 出题界面
SysFunc.Operate(scanner);
// 退出选项
System.out.println("是否返回登陆(y/n)?");
String choice = scanner.nextLine();
if (choice.charAt(0) == 'y') {
continue;
} else if (choice.charAt(0) == 'n') {
System.out.println("感谢您的使用");
break;
} else {
System.out.println("无效输入, 正在返回登陆界面");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
}

Binary file not shown.

@ -0,0 +1,61 @@
package src;
public class PrimaryTea extends Teacher{
final static char[] operators = {'+', '-', '*', '/'}; // 操作符
int operandCount; // 操作数个数
int[] operands; // 操作数列表
int parenStart; // 左括号索引
int parenEnd; // 右括号
public PrimaryTea(String name, String password, String path) {
super(name, password, path);
}
// 数据预处理
public void getRandom(){
// 随机决定操作数个数
this.operandCount = random.nextInt(4) + 2;
this.operands = new int[operandCount];
for (int j = 0; j < operandCount; j++) {
operands[j] = random.nextInt(100) + 1;
}
// 产生有效括号
boolean useParen = this.operandCount > 2 && random.nextBoolean();
this.parenStart = 0;
this.parenEnd = this.operandCount - 1;
while (this.parenStart == 0 && this.parenEnd == this.operandCount - 1) {
this.parenStart = useParen ? random.nextInt(this.operandCount - 1) : -2;
this.parenEnd = useParen ? random.nextInt(this.operandCount - 1 - this.parenStart) + this.parenStart + 1 : -2;
}
}
@Override
protected String generateSingleQuestion() {
getRandom();
StringBuilder question = new StringBuilder();
for (int j = 0; j < this.operandCount; j++) {
if (j == this.parenStart) {
question.append("(");
}
question.append(operands[j]);
if (j == this.parenEnd) {
question.append(")");
}
if (j < this.operandCount - 1) {
char op = operators[random.nextInt(operators.length)];
if (op == '/') {
this.operands[j+1] = this.operands[j+1] == 0 ? 1 : this.operands[j+1];
}
question.append(" ").append(op).append(" ");
}
}
return question.toString();
}
@Override
public String getType(){
return "小学";
}
}

Binary file not shown.

@ -0,0 +1,75 @@
package src;
public class SeniorTea extends Teacher{
final static String[] binaryOps = {"+", "-", "*", "/"}; // 操作符
final static String[] trigOps = {"sin", "cos", "tan"};
int operandCount;
String[] questionParts; // 操作数
boolean[] specialOpFlags; // 特殊操作符索引序列
int parenStart;
int parenEnd;
public SeniorTea(String name, String password, String path) {
super(name, password, path);
}
// 数据预处理
public void getRandom(){
this.operandCount = random.nextInt(4) + 2;
this.questionParts = new String[this.operandCount];
// 随机决定特殊操作符的数量和位置
int specialNum = Math.min(this.operandCount, random.nextInt(this.operandCount) + 1);
this.specialOpFlags = new boolean[this.operandCount];
for (int i = 0; i < specialNum; i++) {
int pos;
do {
pos = random.nextInt(this.operandCount);
} while (this.specialOpFlags[pos]);
this.specialOpFlags[pos] = true;
}
for (int i = 0; i < operandCount; i++) {
int operand = random.nextInt(100) + 1;
if (specialOpFlags[i]) {
String op = trigOps[random.nextInt(trigOps.length)];
questionParts[i] = op + "(" + operand + ")";
} else {
questionParts[i] = String.valueOf(operand);
}
}
// 生成有效括号
boolean useParen = this.operandCount > 2 && random.nextBoolean();
this.parenStart = 0;
this.parenEnd = this.operandCount - 1;
while (this.parenStart == 0 && this.parenEnd == this.operandCount - 1) {
this.parenStart = useParen ? random.nextInt(this.operandCount - 1) : -2;
this.parenEnd = useParen ? random.nextInt(this.operandCount - 1 - this.parenStart) + this.parenStart + 1 : -2;
}
}
@Override
protected String generateSingleQuestion(){
getRandom();
StringBuilder question = new StringBuilder();
for (int i = 0; i < operandCount; i++) {
if (i == parenStart) {
question.append("(");
}
question.append(questionParts[i]);
if (i == parenEnd) {
question.append(")");
}
if (i < operandCount - 1) {
String op = binaryOps[random.nextInt(binaryOps.length)];
question.append(" ").append(op).append(" ");
}
}
return question.toString();
}
@Override
public String getType(){
return "高中";
}
}

Binary file not shown.

@ -0,0 +1,155 @@
package src;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Scanner;
public class SysFunc {
// 题目数量
static final int LEAST_NUM = 10;
static final int MAX_NUM = 30;
// 用户列表
static ArrayList<Teacher> teachers;
// 当前用户
static Teacher teacher;
static HashMap<String, Integer> typeMap;
// ANSI颜色代码
static final String RESET = "\u001B[0m";
static final String RED = "\u001B[31m";
static final String GREEN = "\u001B[32m";
static final String YELLOW = "\u001B[33m";
static final String BLUE = "\u001B[34m";
static final String PURPLE = "\u001B[35m";
static final String CYAN = "\u001B[36m";
static final String BOLD = "\u001B[1m";
public static void init(){
// 用户预加载
teachers = new ArrayList<>();
teachers.add(new PrimaryTea("张三1", "123", null));
teachers.add(new PrimaryTea("张三2", "123", null));
teachers.add(new PrimaryTea("张三3", "123", null));
teachers.add(new JuniorTea("李四1", "123", null));
teachers.add(new JuniorTea("李四2", "123", null));
teachers.add(new JuniorTea("李四3", "123", null));
teachers.add(new SeniorTea("王五1", "123", null));
teachers.add(new SeniorTea("王五2", "123", null));
teachers.add(new SeniorTea("王五3", "123", null));
typeMap = new HashMap<>();
typeMap.put("小学", 1);
typeMap.put("初中", 2);
typeMap.put("高中", 3);
}
// 登陆界面
public static Teacher login(Scanner scanner) {
boolean flag = false;
System.out.println(BOLD + CYAN + "=== 数学题目生成系统 ===" + RESET);
System.out.println(BOLD + CYAN + "请登录您的账户" + RESET);
while (!flag) {
System.out.println(BOLD + YELLOW + "请输入: 用户名 密码" + RESET);
System.out.print(BOLD + BLUE + "> " + RESET);
String cur = scanner.nextLine();
if (cur.indexOf(" ") == -1) {
System.out.println(RED + "请输入用户名和密码,中间用空格隔开");
continue;
}
String cur_name = cur.substring(0, cur.indexOf(" "));
String cur_password = cur.substring(cur.indexOf(" ") + 1);
// 匹配已有帐户
for (Teacher t : teachers) {
if (t.name.equals(cur_name) && t.password.equals(cur_password)) {
teacher = t;
flag = true;
System.out.println(GREEN + "当前选择为" + teacher.getType() + "出题");
break;
}
}
if (!flag) {
System.out.println(RED + "请输入正确的用户名、密码");
}
}
return teacher;
}
// 切换类型界面
public static void ShiftType(Scanner scanner) {
int type = SysFunc.typeMap.get(SysFunc.teacher.getType());
System.out.println(BOLD + PURPLE + "=== 是否切换出题类型(y/n) ===" + RESET);
System.out.print(BOLD + BLUE + "> " + RESET);
String choice = scanner.nextLine();
if (choice.equals("y")) {
System.out.println(BOLD + YELLOW + "请输入指令: 切换** (支持小学,初中,高中难度): " + RESET);
while (true) {
System.out.print(BOLD + BLUE + "> " + RESET);
String choice_type = scanner.nextLine();
if (choice_type.substring(0, 2).equals("切换") && typeMap.containsKey(choice_type.substring(2))) {
type = typeMap.get(choice_type.substring(2));
if (type == 1) {
teacher = new PrimaryTea(teacher.name, teacher.password, teacher.path);
} else if (type == 2) {
teacher = new JuniorTea(teacher.name, teacher.password, teacher.path);
} else {
teacher = new SeniorTea(teacher.name, teacher.password, teacher.path);
}
System.out.println(GREEN + "准备生成" + teacher.getType() + "数学题目,请输入生成题目数量(输入-1将退出当前用户,重新登录):");
break;
} else {
System.out.println(RED + RESET + "请输入小学、初中和高中三个选项中的一个");
}
}
} else if (choice.equals("n")) {
System.out.println(GREEN + "请继续输入题目数量:");
} else {
System.out.println(RED + RESET + "无效输入,已为您设置默认类型:" + teacher.getType());
System.out.println(GREEN + "请输入题目数量:");
}
}
// 出题界面
public static void Operate(Scanner scanner){
boolean logout = false;
while (!logout) {
SysFunc.ShiftType(scanner);
int num = 0;
while (true) {
try {
System.out.print(BOLD + BLUE + "> " + RESET);
String input = scanner.nextLine();
num = Integer.parseInt(input);
break;
} catch (NumberFormatException e) {
System.out.println(RED + RESET + "请输入有效的数字(10-30之间的整数,或-1退出登录):");
}
}
while (true) {
if (num == -1) {
System.out.println(BOLD + YELLOW + "已退出登录" + RESET);
logout = true;
break;
}
if (num >= LEAST_NUM && num <= MAX_NUM) {
System.out.println(BOLD + GREEN + "正在生成题目..." + RESET);
String filepath = teacher.generateEX(num);
System.out.println(GREEN + RESET + "题目生成成功,已保存至" + filepath);
System.out.println(BOLD + CYAN + "=== 操作完成 ===" + RESET);
break;
} else {
System.out.println(RED + RESET + "题目数量应在10-30之间,请重新输入");
try {
System.out.print(BOLD + BLUE + "> " + RESET);
num = Integer.parseInt(scanner.nextLine());
} catch (NumberFormatException e) {
System.out.println(RED + RESET + "请输入有效的数字:");
}
}
}
}
}
}

Binary file not shown.

@ -0,0 +1,113 @@
package src;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;
public abstract class Teacher {
protected String name;
protected String password;
protected String path = null; // 教师文件夹路径
public Random random = new Random(); // 共用Random
public Teacher(String name, String password, String path) {
this.name = name;
this.password = password;
if (path != null) {
this.path = path;
return;
}
// 生成文件夹
File folder = new File(name);
try {
if (!folder.exists()) {
folder.mkdirs();
}
} catch (Exception e) {
e.printStackTrace();
}
this.path = folder.getAbsolutePath();
}
// 查重
public boolean checkDuplicate(String question){
File folder = new File(this.path);
File[] files = folder.listFiles();
if (files == null) {
return false;
}
for (File file : files) {
if (!file.getName().endsWith(".txt")) {
continue;
}
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
String line;
while ((line = reader.readLine()) != null) {
if (line.contains(")")) {
try {
String fileQuestion = line.substring(line.indexOf(")") + 2).trim();
if (fileQuestion.equals(question)) {
return true;
}
} catch (StringIndexOutOfBoundsException e) {
// 继续处理下一行
continue;
}
}
}
} catch (IOException e) {
System.err.println("读取文件时出错: " + file.getName());
e.printStackTrace();
}
}
return false;
}
// 生成存储文档
public String generatePath(){
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss");
File file = new File(this.path, sdf.format(date) + ".txt");
try {
if (!file.exists()) {
file.createNewFile();
}
} catch (IOException e) {
e.printStackTrace();
}
return file.getAbsolutePath();
}
// 生成并存储题目
public String generateEX(int num) {
Path filePath = Paths.get(generatePath());
for (int i = 0; i < num; i++) {
String question = generateSingleQuestion();
if (checkDuplicate(question)) {
i--;
continue;
}
String qstr = "(" + (i + 1) + ") " + question + " =\n";
try {
Files.writeString(filePath, qstr + '\n', StandardOpenOption.APPEND);
} catch (IOException e) {
e.printStackTrace();
}
}
return filePath.toString();
}
// 生成单个题目
protected abstract String generateSingleQuestion();
// 返回教师类型
public abstract String getType();
}
Loading…
Cancel
Save