第一次提交 #1

Merged
hnu202326010207 merged 1 commits from develop into main 5 months ago

@ -1,2 +0,0 @@
# smarthomework

@ -0,0 +1,22 @@
# 中小学数学卷子自动生成程序
## 功能概览
- 账户登录:支持小学、初中、高中各三位教师账号,登录成功提示当前难度。
- 出题控制:支持输入 10-30 道题的数量限制,-1 退出当前登录。
- 题目生成:根据难度生成符合运算规则的题目,并保证不与历史题库重复。
- 难度切换:输入 `切换为小学|初中|高中` 可即时切换出题类型。
- 结果保存:按 `YYYY-MM-DD-HH-mm-ss.txt` 格式保存在 `generated_questions/<用户名>/` 目录下,题号与空行符合要求。
## 使用方式
1. 在项目根目录执行 `javac $(find src -name '*.java') -d out` 完成编译。
2. 执行 `java -cp out com.personalproject.MathExamApplication` 启动程序。
3. 根据提示输入用户名、密码(以空格分隔)。
4. 输入题目数量或切换命令生成试题,生成完成后可继续出题或输入 `-1` 退出当前账号。
5. 登录新账号或结束程序(直接回车结束输入即可退出)。
## 目录结构
```
src/ Java 源码(遵循 Google Java Style
doc/README.md 项目说明文档
generated_questions/ 程序运行后生成的题目文件夹
```

@ -0,0 +1,172 @@
package com.personalproject;
import com.personalproject.auth.AccountRepository;
import com.personalproject.auth.UserAccount;
import com.personalproject.generator.HighSchoolQuestionGenerator;
import com.personalproject.generator.MiddleSchoolQuestionGenerator;
import com.personalproject.generator.PrimaryQuestionGenerator;
import com.personalproject.generator.QuestionGenerator;
import com.personalproject.model.DifficultyLevel;
import com.personalproject.service.QuestionGenerationService;
import com.personalproject.storage.QuestionStorageService;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.Scanner;
import java.util.Set;
/**
*
*/
public final class MathExamApplication {
private final AccountRepository accountRepository = new AccountRepository();
private final QuestionGenerationService questionGenerationService;
private final QuestionStorageService questionStorageService = new QuestionStorageService();
public MathExamApplication() {
Map<DifficultyLevel, QuestionGenerator> generatorMap = new EnumMap<>(DifficultyLevel.class);
generatorMap.put(DifficultyLevel.PRIMARY, new PrimaryQuestionGenerator());
generatorMap.put(DifficultyLevel.MIDDLE, new MiddleSchoolQuestionGenerator());
generatorMap.put(DifficultyLevel.HIGH, new HighSchoolQuestionGenerator());
questionGenerationService = new QuestionGenerationService(generatorMap);
}
public static void main(String[] args) {
MathExamApplication application = new MathExamApplication();
application.run();
}
private void run() {
Scanner scanner = new Scanner(System.in, StandardCharsets.UTF_8);
while (true) {
UserAccount account = promptLogin(scanner);
if (account == null) {
return;
}
handleSession(scanner, account);
}
}
private UserAccount promptLogin(Scanner scanner) {
while (true) {
System.out.print("请输入用户名和密码(以空格分隔):");
if (!scanner.hasNextLine()) {
return null;
}
String input = scanner.nextLine().trim();
if (input.isEmpty()) {
continue;
}
String[] parts = input.split("\\s+");
if (parts.length != 2) {
System.out.println("请输入正确的用户名、密码");
continue;
}
Optional<UserAccount> account = accountRepository.authenticate(parts[0], parts[1]);
if (account.isPresent()) {
DifficultyLevel level = account.get().difficultyLevel();
System.out.println("当前选择为" + level.getDisplayName() + "出题");
return account.get();
}
System.out.println("请输入正确的用户名、密码");
}
}
private void handleSession(Scanner scanner, UserAccount account) {
DifficultyLevel currentLevel = account.difficultyLevel();
Set<String> existingQuestions = loadExistingQuestions(account.username());
while (true) {
System.out.print(
"准备生成"
+ currentLevel.getDisplayName()
+ "数学题目,请输入生成题目数量(输入-1将退出当前用户重新登录");
if (!scanner.hasNextLine()) {
return;
}
String input = scanner.nextLine().trim();
if (input.isEmpty()) {
continue;
}
if (input.startsWith("切换为")) {
currentLevel = processSwitchCommand(input, currentLevel);
continue;
}
if ("-1".equals(input)) {
System.out.println("已退出当前用户。");
return;
}
Integer questionCount = parseQuestionCount(input);
if (questionCount == null) {
continue;
}
generateAndPersistQuestions(account, currentLevel, questionCount, existingQuestions);
}
}
private DifficultyLevel processSwitchCommand(String input, DifficultyLevel currentLevel) {
String targetName = input.substring("切换为".length()).trim();
Optional<DifficultyLevel> targetLevel = DifficultyLevel.fromDisplayName(targetName);
if (targetLevel.isEmpty()) {
System.out.println("请输入小学、初中和高中三个选项中的一个");
return currentLevel;
}
DifficultyLevel level = targetLevel.get();
System.out.println(
"系统提示: 准备生成"
+ level.getDisplayName()
+ "数学题目,请输入生成题目数量(输入-1将退出当前用户重新登录");
return level;
}
private Integer parseQuestionCount(String input) {
try {
int value = Integer.parseInt(input);
if (value < 10 || value > 30) {
System.out.println("题目数量必须在10到30之间请重新输入。");
return null;
}
return value;
} catch (NumberFormatException exception) {
System.out.println("请输入有效的数字或切换命令。");
return null;
}
}
private void generateAndPersistQuestions(
UserAccount account,
DifficultyLevel level,
int questionCount,
Set<String> existingQuestions) {
try {
var questions =
questionGenerationService
.generateUniqueQuestions(level, questionCount, existingQuestions);
Path outputPath = questionStorageService.saveQuestions(account.username(), questions);
existingQuestions.addAll(questions);
System.out.println(
"已生成"
+ questionCount
+ "道"
+ level.getDisplayName()
+ "数学题目,保存在:"
+ outputPath);
} catch (IllegalStateException exception) {
System.out.println("生成题目失败,请稍后重试。");
} catch (IOException exception) {
System.out.println("保存题目失败,请检查存储目录是否可写。");
}
}
private Set<String> loadExistingQuestions(String username) {
try {
return questionStorageService.loadExistingQuestions(username);
} catch (IOException exception) {
System.out.println("读取历史题目失败,将仅针对本次生成查重。");
return new HashSet<>();
}
}
}

@ -0,0 +1,51 @@
package com.personalproject.auth;
import com.personalproject.model.DifficultyLevel;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
*
*/
public final class AccountRepository {
private final Map<String, UserAccount> accounts = new HashMap<>();
public AccountRepository() {
registerPrimaryAccounts();
registerMiddleSchoolAccounts();
registerHighSchoolAccounts();
}
public Optional<UserAccount> authenticate(String username, String password) {
if (username == null || password == null) {
return Optional.empty();
}
UserAccount account = accounts.get(username.trim());
if (account == null) {
return Optional.empty();
}
if (!account.password().equals(password.trim())) {
return Optional.empty();
}
return Optional.of(account);
}
private void registerPrimaryAccounts() {
accounts.put("张三1", new UserAccount("张三1", "123", DifficultyLevel.PRIMARY));
accounts.put("张三2", new UserAccount("张三2", "123", DifficultyLevel.PRIMARY));
accounts.put("张三3", new UserAccount("张三3", "123", DifficultyLevel.PRIMARY));
}
private void registerMiddleSchoolAccounts() {
accounts.put("李四1", new UserAccount("李四1", "123", DifficultyLevel.MIDDLE));
accounts.put("李四2", new UserAccount("李四2", "123", DifficultyLevel.MIDDLE));
accounts.put("李四3", new UserAccount("李四3", "123", DifficultyLevel.MIDDLE));
}
private void registerHighSchoolAccounts() {
accounts.put("王五1", new UserAccount("王五1", "123", DifficultyLevel.HIGH));
accounts.put("王五2", new UserAccount("王五2", "123", DifficultyLevel.HIGH));
accounts.put("王五3", new UserAccount("王五3", "123", DifficultyLevel.HIGH));
}
}

@ -0,0 +1,9 @@
package com.personalproject.auth;
import com.personalproject.model.DifficultyLevel;
/**
*
*/
public record UserAccount(String username, String password, DifficultyLevel difficultyLevel) {
}

@ -0,0 +1,36 @@
package com.personalproject.generator;
import java.util.Random;
/**
*
*/
public final class HighSchoolQuestionGenerator implements QuestionGenerator {
private static final String[] OPERATORS = {"+", "-", "*", "/"};
private static final String[] TRIG_FUNCTIONS = {"sin", "cos", "tan"};
@Override
public String generateQuestion(Random random) {
int operandCount = random.nextInt(5) + 1;
String[] operands = new String[operandCount];
for (int index = 0; index < operandCount; index++) {
operands[index] = String.valueOf(random.nextInt(100) + 1);
}
int specialIndex = random.nextInt(operandCount);
String function = TRIG_FUNCTIONS[random.nextInt(TRIG_FUNCTIONS.length)];
operands[specialIndex] = function + '(' + operands[specialIndex] + ')';
StringBuilder builder = new StringBuilder();
for (int index = 0; index < operandCount; index++) {
if (index > 0) {
String operator = OPERATORS[random.nextInt(OPERATORS.length)];
builder.append(' ').append(operator).append(' ');
}
builder.append(operands[index]);
}
String expression = builder.toString();
if (operandCount > 1 && random.nextBoolean()) {
return '(' + expression + ')';
}
return expression;
}
}

@ -0,0 +1,38 @@
package com.personalproject.generator;
import java.util.Random;
/**
*
*/
public final class MiddleSchoolQuestionGenerator implements QuestionGenerator {
private static final String[] OPERATORS = {"+", "-", "*", "/"};
@Override
public String generateQuestion(Random random) {
int operandCount = random.nextInt(5) + 1;
String[] operands = new String[operandCount];
for (int index = 0; index < operandCount; index++) {
operands[index] = String.valueOf(random.nextInt(100) + 1);
}
int specialIndex = random.nextInt(operandCount);
if (random.nextBoolean()) {
operands[specialIndex] = '(' + operands[specialIndex] + ")^2";
} else {
operands[specialIndex] = "sqrt(" + operands[specialIndex] + ')';
}
StringBuilder builder = new StringBuilder();
for (int index = 0; index < operandCount; index++) {
if (index > 0) {
String operator = OPERATORS[random.nextInt(OPERATORS.length)];
builder.append(' ').append(operator).append(' ');
}
builder.append(operands[index]);
}
String expression = builder.toString();
if (operandCount > 1 && random.nextBoolean()) {
return '(' + expression + ')';
}
return expression;
}
}

@ -0,0 +1,29 @@
package com.personalproject.generator;
import java.util.Random;
/**
*
*/
public final class PrimaryQuestionGenerator implements QuestionGenerator {
private static final String[] OPERATORS = {"+", "-", "*", "/"};
@Override
public String generateQuestion(Random random) {
int operandCount = random.nextInt(5) + 1;
StringBuilder builder = new StringBuilder();
for (int index = 0; index < operandCount; index++) {
if (index > 0) {
String operator = OPERATORS[random.nextInt(OPERATORS.length)];
builder.append(' ').append(operator).append(' ');
}
int value = random.nextInt(100) + 1;
builder.append(value);
}
String expression = builder.toString();
if (operandCount > 1 && random.nextBoolean()) {
return '(' + expression + ')';
}
return expression;
}
}

@ -0,0 +1,10 @@
package com.personalproject.generator;
import java.util.Random;
/**
*
*/
public interface QuestionGenerator {
String generateQuestion(Random random);
}

@ -0,0 +1,38 @@
package com.personalproject.model;
import java.util.Optional;
/**
*
*/
public enum DifficultyLevel {
PRIMARY("小学"),
MIDDLE("初中"),
HIGH("高中");
private final String displayName;
DifficultyLevel(String displayName) {
this.displayName = displayName;
}
public String getDisplayName() {
return displayName;
}
public static Optional<DifficultyLevel> fromDisplayName(String name) {
if (name == null) {
return Optional.empty();
}
String trimmed = name.trim();
if (trimmed.isEmpty()) {
return Optional.empty();
}
for (DifficultyLevel level : values()) {
if (level.displayName.equals(trimmed)) {
return Optional.of(level);
}
}
return Optional.empty();
}
}

@ -0,0 +1,55 @@
package com.personalproject.service;
import com.personalproject.generator.QuestionGenerator;
import com.personalproject.model.DifficultyLevel;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.EnumMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
/**
*
*/
public final class QuestionGenerationService {
private static final int MAX_ATTEMPTS = 10_000;
private final Map<DifficultyLevel, QuestionGenerator> generators;
private final Random random = new SecureRandom();
public QuestionGenerationService(Map<DifficultyLevel, QuestionGenerator> generatorMap) {
generators = new EnumMap<>(DifficultyLevel.class);
generators.putAll(generatorMap);
}
public List<String> generateUniqueQuestions(
DifficultyLevel level, int count, Set<String> existingQuestions) {
QuestionGenerator generator = generators.get(level);
if (generator == null) {
throw new IllegalArgumentException("Unsupported difficulty level: " + level);
}
Set<String> produced = new HashSet<>();
List<String> results = new ArrayList<>();
int attempts = 0;
while (results.size() < count) {
if (attempts >= MAX_ATTEMPTS) {
throw new IllegalStateException("Unable to generate enough unique questions.");
}
attempts++;
String question = generator.generateQuestion(random).trim();
if (question.isEmpty()) {
continue;
}
if (existingQuestions.contains(question)) {
continue;
}
if (!produced.add(question)) {
continue;
}
results.add(question);
}
return results;
}
}

@ -0,0 +1,85 @@
package com.personalproject.storage;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
/**
*
*/
public final class QuestionStorageService {
private static final String BASE_DIRECTORY = "generated_questions";
private static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss");
public Set<String> loadExistingQuestions(String username) throws IOException {
Path accountDirectory = getAccountDirectory(username);
Set<String> questions = new HashSet<>();
if (!Files.exists(accountDirectory)) {
return questions;
}
try (Stream<Path> paths = Files.list(accountDirectory)) {
paths
.filter(path -> path.getFileName().toString().endsWith(".txt"))
.sorted()
.forEach(path -> readQuestionsFromFile(path, questions));
}
return questions;
}
public Path saveQuestions(String username, List<String> questions) throws IOException {
Path accountDirectory = getAccountDirectory(username);
Files.createDirectories(accountDirectory);
String fileName = FORMATTER.format(LocalDateTime.now()) + ".txt";
Path outputFile = accountDirectory.resolve(fileName);
StringBuilder builder = new StringBuilder();
for (int index = 0; index < questions.size(); index++) {
String question = questions.get(index);
builder
.append(index + 1)
.append(". ")
.append(question)
.append(System.lineSeparator())
.append(System.lineSeparator());
}
Files.writeString(
outputFile, builder.toString(), StandardCharsets.UTF_8, StandardOpenOption.CREATE_NEW);
return outputFile;
}
private Path getAccountDirectory(String username) {
return Paths.get(BASE_DIRECTORY, username);
}
private void readQuestionsFromFile(Path path, Set<String> questions) {
try {
List<String> lines = Files.readAllLines(path, StandardCharsets.UTF_8);
for (String line : lines) {
String trimmed = line.trim();
if (trimmed.isEmpty()) {
continue;
}
int dotIndex = trimmed.indexOf('.');
if (dotIndex >= 0 && dotIndex + 1 < trimmed.length()) {
String question = trimmed.substring(dotIndex + 1).trim();
if (!question.isEmpty()) {
questions.add(question);
}
} else {
questions.add(trimmed);
}
}
} catch (IOException exception) {
// 继续处理其他文件,如有需要可在此处添加日志。
}
}
}
Loading…
Cancel
Save