|
|
@ -0,0 +1,303 @@
|
|
|
|
|
|
|
|
# 数学题目生成器
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
这是潘俊晖在《软件工程导论》课程中的个人项目。
|
|
|
|
|
|
|
|
该项目是一个基于 Java 的命令行应用程序,支持用户登录并生成小学、初中、高中的数学题目。
|
|
|
|
|
|
|
|
项目采用**策略模式**设计,便于扩展新的题目生成器。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 环境要求
|
|
|
|
|
|
|
|
- **JDK 版本**:JDK 17+(兼容 JDK 11)
|
|
|
|
|
|
|
|
- **依赖库**:纯 Java 项目,无需额外依赖
|
|
|
|
|
|
|
|
- **推荐 IDE**:IntelliJ IDEA(也可通过命令行运行)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 运行
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 1. 通过 JRE 运行 jar 包
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
执行:
|
|
|
|
|
|
|
|
```aiignore
|
|
|
|
|
|
|
|
java -jar new-math-cli.jar
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
### 2. 克隆仓库并通过 IDEA 运行
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 代码说明
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 登陆检查
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
|
|
private static void handleLogin(Scanner scanner) {
|
|
|
|
|
|
|
|
System.out.println("**********欢迎使用数学题目生成 Client **********");
|
|
|
|
|
|
|
|
System.out.println("请输入用户名和密码,用空格隔开 (输入 'exit' 退出程序):");
|
|
|
|
|
|
|
|
String line = scanner.nextLine();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if ("exit".equalsIgnoreCase(line)) {
|
|
|
|
|
|
|
|
System.out.println("感谢使用,程序已退出。");
|
|
|
|
|
|
|
|
System.exit(0);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String[] input = line.split(" ");
|
|
|
|
|
|
|
|
if (input.length == 2) {
|
|
|
|
|
|
|
|
authService.login(input[0], input[1]).ifPresentOrElse(
|
|
|
|
|
|
|
|
user -> {
|
|
|
|
|
|
|
|
currentUser = user;
|
|
|
|
|
|
|
|
currentLevel = user.getLevel();
|
|
|
|
|
|
|
|
System.out.printf("当前选择为%s出题%n", currentLevel.getName());},
|
|
|
|
|
|
|
|
() -> System.out.println("请输入正确的用户名、密码")
|
|
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
System.out.println("输入格式不正确,请重新输入。");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 生成题目时检查输入
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
|
|
private static void handleQuestionGeneration(Scanner scanner) {
|
|
|
|
|
|
|
|
System.out.printf("准备生成%s数学题目, 请输入生成题目数量(输入-1将退出当前用户, 重新登录):%n", currentLevel.getName()); // 生成提示
|
|
|
|
|
|
|
|
System.out.println("可输入“切换为(小学/初中/高中)”");
|
|
|
|
|
|
|
|
String input = scanner.nextLine();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (input.startsWith("切换为")) {
|
|
|
|
|
|
|
|
handleLevelSwitch(input);
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
int count = Integer.parseInt(input);
|
|
|
|
|
|
|
|
if (count == -1) {
|
|
|
|
|
|
|
|
currentUser = null;
|
|
|
|
|
|
|
|
currentLevel = null;
|
|
|
|
|
|
|
|
System.out.println("已退出,请重新登录。");
|
|
|
|
|
|
|
|
} else if (count >= 10 && count <= 30) {
|
|
|
|
|
|
|
|
testPaperService.generateAndSave(currentUser, currentLevel, count);
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
System.out.println("输入无效,请输入10-30之间的数字或-1。");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (NumberFormatException e) {
|
|
|
|
|
|
|
|
System.out.println("输入无效,请输入一个数字。");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
### 题目查重和生成部分
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
|
|
public void generateAndSave(User user, Level level, int count) {
|
|
|
|
|
|
|
|
Set<String> existingQuestions = loadExistingQuestions(user.getUsername());
|
|
|
|
|
|
|
|
System.out.println("检测到历史题目 " + existingQuestions.size() + " 道。");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
IQuestionGenerator generator = createGenerator(level);
|
|
|
|
|
|
|
|
Set<String> newQuestions = new HashSet<>();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
// 生成新的、与历史和本次不重复的题目
|
|
|
|
|
|
|
|
for (int i = 0; i < count; i++) {
|
|
|
|
|
|
|
|
Set<String> used = new HashSet<>(existingQuestions);
|
|
|
|
|
|
|
|
used.addAll(newQuestions);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
String question = generator.generate(used);
|
|
|
|
|
|
|
|
newQuestions.add(question);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
saveToFile(user.getUsername(), newQuestions);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private Set<String> loadExistingQuestions(String username) {
|
|
|
|
|
|
|
|
Set<String> existingQuestions = new HashSet<>();
|
|
|
|
|
|
|
|
File userDir = new File(username);
|
|
|
|
|
|
|
|
if (!userDir.exists() || !userDir.isDirectory()) {
|
|
|
|
|
|
|
|
return existingQuestions;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
File[] files = userDir.listFiles((dir, name) -> name.toLowerCase().endsWith(".txt"));
|
|
|
|
|
|
|
|
if (files == null) {
|
|
|
|
|
|
|
|
return existingQuestions;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (File file : files) {
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
|
|
|
Files.lines(file.toPath()).forEach(line -> {
|
|
|
|
|
|
|
|
if (line.matches("^\\d+\\.\\s.*")) {
|
|
|
|
|
|
|
|
String question = line.substring(line.indexOf(' ') + 1).trim();
|
|
|
|
|
|
|
|
existingQuestions.add(question);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
|
|
|
System.err.println("错误:读取历史文件失败: " + file.getName());
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
return existingQuestions;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
### 题目生成接口和抽象类
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
|
|
// generator/IQuestionGenerator.java
|
|
|
|
|
|
|
|
package generator;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import java.util.Set;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public interface IQuestionGenerator {
|
|
|
|
|
|
|
|
String generate(Set<String> existingQuestions);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
和
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
|
|
// generator/BaseGenerator.java
|
|
|
|
|
|
|
|
package generator;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import java.util.Random;
|
|
|
|
|
|
|
|
import java.util.Set;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public abstract class BaseGenerator implements IQuestionGenerator {
|
|
|
|
|
|
|
|
protected final Random random = new Random();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected int getRandomOperand() {
|
|
|
|
|
|
|
|
return random.nextInt(100) + 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected int getNumberOfOperands() {
|
|
|
|
|
|
|
|
return random.nextInt(5) + 1;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
public String generate(Set<String> existingQuestions) {
|
|
|
|
|
|
|
|
String question;
|
|
|
|
|
|
|
|
do {
|
|
|
|
|
|
|
|
question = createUniqueQuestion();
|
|
|
|
|
|
|
|
} while (existingQuestions.contains(question));
|
|
|
|
|
|
|
|
return question;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected abstract String createUniqueQuestion();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
### 小学题目生成器
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
|
|
public class ElementarySchoolGenerator extends BaseGenerator {
|
|
|
|
|
|
|
|
private final String[] OPERATORS = {"+", "-", "*", "/"};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
protected String createUniqueQuestion() {
|
|
|
|
|
|
|
|
int numOperands = random.nextInt(4) + 2;
|
|
|
|
|
|
|
|
StringBuilder sb = new StringBuilder();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int runningTotal = getRandomOperand();
|
|
|
|
|
|
|
|
sb.append(runningTotal);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < numOperands - 1; i++) {
|
|
|
|
|
|
|
|
String operator = pickOperator();
|
|
|
|
|
|
|
|
int operand = getRandomOperand();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
operator = adjustOperator(operator, operand, runningTotal);
|
|
|
|
|
|
|
|
runningTotal = applyOperation(runningTotal, operator, operand);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sb.append(" ").append(operator).append(" ").append(operand);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return sb.append(" = ?").toString();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String pickOperator() {
|
|
|
|
|
|
|
|
return OPERATORS[random.nextInt(OPERATORS.length)];
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private String adjustOperator(String op, int operand, int current) {
|
|
|
|
|
|
|
|
if (op.equals("-") && operand > current) return "+";
|
|
|
|
|
|
|
|
if (op.equals("/") && (operand == 0 || current % operand != 0)) return "*";
|
|
|
|
|
|
|
|
return op;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
private int applyOperation(int current, String op, int operand) {
|
|
|
|
|
|
|
|
switch (op) {
|
|
|
|
|
|
|
|
case "+": return current + operand;
|
|
|
|
|
|
|
|
case "-": return current - operand;
|
|
|
|
|
|
|
|
case "*": return current * operand;
|
|
|
|
|
|
|
|
case "/": return current / operand;
|
|
|
|
|
|
|
|
default: throw new IllegalArgumentException("Unknown operator: " + op);
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
### 初中题目生成器
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
|
|
public class JuniorHighGenerator extends BaseGenerator {
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
protected String createUniqueQuestion() {
|
|
|
|
|
|
|
|
int numOperands = random.nextInt(4) + 2;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
List<String> operands = new ArrayList<>();
|
|
|
|
|
|
|
|
for (int i = 0; i < numOperands; i++) {
|
|
|
|
|
|
|
|
operands.add(String.valueOf(getRandomOperand()));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int specialIndex = random.nextInt(numOperands);
|
|
|
|
|
|
|
|
int originalValue = Integer.parseInt(operands.get(specialIndex));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if (random.nextBoolean()) {
|
|
|
|
|
|
|
|
operands.set(specialIndex, "sqrt(" + (originalValue * originalValue) + ")");
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
operands.set(specialIndex, originalValue + "^2");
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
StringBuilder questionBuilder = new StringBuilder();
|
|
|
|
|
|
|
|
String[] basicOperators = {"+", "-", "*"};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
questionBuilder.append(operands.get(0));
|
|
|
|
|
|
|
|
for (int i = 1; i < numOperands; i++) {
|
|
|
|
|
|
|
|
String operator = basicOperators[random.nextInt(basicOperators.length)];
|
|
|
|
|
|
|
|
questionBuilder.append(" ").append(operator).append(" ").append(operands.get(i));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
questionBuilder.append(" = ?");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return questionBuilder.toString();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
### 高中题目生成器
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
|
|
|
public class SeniorHighGenerator extends BaseGenerator {
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|
|
|
protected String createUniqueQuestion() {
|
|
|
|
|
|
|
|
int numOperands = random.nextInt(4) + 2;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
List<String> operands = new ArrayList<>();
|
|
|
|
|
|
|
|
for (int i = 0; i < numOperands; i++) {
|
|
|
|
|
|
|
|
operands.add(String.valueOf(getRandomOperand()));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
int specialIndex = random.nextInt(numOperands);
|
|
|
|
|
|
|
|
String[] trigFunctions = {"sin", "cos", "tan"};
|
|
|
|
|
|
|
|
String func = trigFunctions[random.nextInt(trigFunctions.length)];
|
|
|
|
|
|
|
|
int angle = random.nextInt(91);
|
|
|
|
|
|
|
|
operands.set(specialIndex, String.format("%s(%d)", func, angle));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
StringBuilder questionBuilder = new StringBuilder();
|
|
|
|
|
|
|
|
String[] basicOperators = {"+", "-", "*"};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
questionBuilder.append(operands.get(0));
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i < numOperands; i++) {
|
|
|
|
|
|
|
|
String operator = basicOperators[random.nextInt(basicOperators.length)];
|
|
|
|
|
|
|
|
questionBuilder.append(" ").append(operator).append(" ").append(operands.get(i));
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
questionBuilder.append(" = ?");
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
return questionBuilder.toString();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
```
|
|
|
|
|
|
|
|
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
## 后续改进方向
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 增加题目难度参数(如:数字范围、运算符种类)。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 增加 GUI 界面,提升交互体验。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 增加单元测试和持续集成,确保题目生成正确性。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
- 题目结果可导出为 PDF/Word 试卷格式。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
---
|