|
|
|
|
@ -1,509 +0,0 @@
|
|
|
|
|
package model;
|
|
|
|
|
|
|
|
|
|
import java.io.BufferedReader;
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.FileReader;
|
|
|
|
|
import java.io.FileWriter;
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.io.PrintWriter;
|
|
|
|
|
import java.time.LocalDateTime;
|
|
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.HashSet;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.Random;
|
|
|
|
|
import java.util.Set;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 题目生成器
|
|
|
|
|
* 根据年级生成相应难度的数学题目,支持查重功能
|
|
|
|
|
*/
|
|
|
|
|
public class QuestionGenerator {
|
|
|
|
|
private static final int MAX_ATTEMPTS = 100;
|
|
|
|
|
private final Random random;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 构造题目生成器
|
|
|
|
|
*/
|
|
|
|
|
public QuestionGenerator() {
|
|
|
|
|
this.random = new Random();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成指定数量的题目
|
|
|
|
|
*
|
|
|
|
|
* @param gradeType 年级类型
|
|
|
|
|
* @param count 题目数量
|
|
|
|
|
* @param email 用户邮箱
|
|
|
|
|
* @return 题目列表
|
|
|
|
|
*/
|
|
|
|
|
public List<Question> generateQuestions(GradeType gradeType,
|
|
|
|
|
int count, String email) {
|
|
|
|
|
List<Question> questions = new ArrayList<>();
|
|
|
|
|
Set<String> existingQuestions = loadUserQuestions(email);
|
|
|
|
|
Set<String> currentQuestions = new HashSet<>();
|
|
|
|
|
int attempts = 0;
|
|
|
|
|
|
|
|
|
|
while (questions.size() < count && attempts < MAX_ATTEMPTS) {
|
|
|
|
|
String expression = generateExpression(gradeType);
|
|
|
|
|
|
|
|
|
|
if (!existingQuestions.contains(expression) &&
|
|
|
|
|
!currentQuestions.contains(expression)) {
|
|
|
|
|
try {
|
|
|
|
|
double result = evaluateExpression(expression);
|
|
|
|
|
Question question = createQuestion(expression, result);
|
|
|
|
|
questions.add(question);
|
|
|
|
|
currentQuestions.add(expression);
|
|
|
|
|
existingQuestions.add(expression);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
// 表达式无效,继续尝试
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
attempts++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
saveUserQuestions(email, new ArrayList<>(currentQuestions));
|
|
|
|
|
return questions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 根据年级生成表达式
|
|
|
|
|
*
|
|
|
|
|
* @param gradeType 年级类型
|
|
|
|
|
* @return 数学表达式
|
|
|
|
|
*/
|
|
|
|
|
private String generateExpression(GradeType gradeType) {
|
|
|
|
|
switch (gradeType) {
|
|
|
|
|
case PRIMARY:
|
|
|
|
|
return generatePrimaryExpression();
|
|
|
|
|
case MIDDLE:
|
|
|
|
|
return generateMiddleExpression();
|
|
|
|
|
case HIGH:
|
|
|
|
|
return generateHighExpression();
|
|
|
|
|
default:
|
|
|
|
|
return generatePrimaryExpression();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成小学数学表达式(包含括号)
|
|
|
|
|
*
|
|
|
|
|
* @return 数学表达式
|
|
|
|
|
*/
|
|
|
|
|
private String generatePrimaryExpression() {
|
|
|
|
|
int termCount = random.nextInt(2) + 2; // 2或3个操作数
|
|
|
|
|
char[] operators = {'+', '-', '*', '/'};
|
|
|
|
|
|
|
|
|
|
int num1 = random.nextInt(100) + 1;
|
|
|
|
|
char op1 = operators[random.nextInt(operators.length)];
|
|
|
|
|
int num2;
|
|
|
|
|
|
|
|
|
|
// 为第一个操作符生成第二个操作数,并确保合法性
|
|
|
|
|
if (op1 == '-') {
|
|
|
|
|
num2 = random.nextInt(num1 + 1); // 确保 num1 >= num2
|
|
|
|
|
} else if (op1 == '/') {
|
|
|
|
|
List<Integer> divisors = getDivisors(num1);
|
|
|
|
|
num2 = divisors.get(random.nextInt(divisors.size()));
|
|
|
|
|
} else { // + or *
|
|
|
|
|
num2 = random.nextInt(20) + 1; // 乘法和加法的数小一点,避免结果过大
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 如果只有两个操作数,直接返回
|
|
|
|
|
if (termCount == 2) {
|
|
|
|
|
return String.format("%d %c %d", num1, op1, num2);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// --- 如果有三个操作数 ---
|
|
|
|
|
String firstPart = String.format("%d %c %d", num1, op1, num2);
|
|
|
|
|
double interimResult;
|
|
|
|
|
try {
|
|
|
|
|
interimResult = evaluateExpression(firstPart);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
// 如果第一部分计算出错(理论上不应该),则简化为两项操作
|
|
|
|
|
return firstPart;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 确保中间结果为非负整数
|
|
|
|
|
if (interimResult < 0 || interimResult != (int)interimResult) {
|
|
|
|
|
return firstPart; // 不满足条件,提前返回两项操作
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
char op2 = operators[random.nextInt(operators.length)];
|
|
|
|
|
int num3;
|
|
|
|
|
|
|
|
|
|
// 为第二个操作符生成第三个操作数,并确保合法性
|
|
|
|
|
if (op2 == '-') {
|
|
|
|
|
num3 = random.nextInt((int) interimResult + 1); // 确保 interimResult >= num3
|
|
|
|
|
} else if (op2 == '/') {
|
|
|
|
|
if (interimResult == 0) { // 除数不能为0
|
|
|
|
|
op2 = '+'; // 将操作符改为加法
|
|
|
|
|
num3 = random.nextInt(20) + 1;
|
|
|
|
|
} else {
|
|
|
|
|
List<Integer> divisors = getDivisors((int)interimResult);
|
|
|
|
|
num3 = divisors.get(random.nextInt(divisors.size()));
|
|
|
|
|
}
|
|
|
|
|
} else { // + or *
|
|
|
|
|
num3 = random.nextInt(20) + 1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 随机决定是否以及如何添加括号
|
|
|
|
|
if (random.nextBoolean()) {
|
|
|
|
|
return String.format("(%d %c %d) %c %d", num1, op1, num2, op2, num3);
|
|
|
|
|
} else {
|
|
|
|
|
// 检查第二个操作符优先级是否高于第一个
|
|
|
|
|
if ((op2 == '*' || op2 == '/') && (op1 == '+' || op1 == '-')) {
|
|
|
|
|
return String.format("%d %c (%d %c %d)", num1, op1, num2, op2, num3);
|
|
|
|
|
}
|
|
|
|
|
// 默认无括号或左侧括号
|
|
|
|
|
return String.format("%d %c %d %c %d", num1, op1, num2, op2, num3);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 获取一个数的所有正整数约数
|
|
|
|
|
* @param n 数字
|
|
|
|
|
* @return 约数列表
|
|
|
|
|
*/
|
|
|
|
|
private List<Integer> getDivisors(int n) {
|
|
|
|
|
List<Integer> divisors = new ArrayList<>();
|
|
|
|
|
if (n == 0) {
|
|
|
|
|
divisors.add(1); // 除数不能为0,返回1作为安全值
|
|
|
|
|
return divisors;
|
|
|
|
|
}
|
|
|
|
|
n = Math.abs(n); // 处理可能的负数输入
|
|
|
|
|
for (int i = 1; i <= n; i++) {
|
|
|
|
|
if (n % i == 0) {
|
|
|
|
|
divisors.add(i);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return divisors;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 为表达式添加括号
|
|
|
|
|
*
|
|
|
|
|
* @param expression 原表达式
|
|
|
|
|
* @return 添加括号后的表达式
|
|
|
|
|
*/
|
|
|
|
|
private String addBrackets(String expression) {
|
|
|
|
|
String[] parts = expression.split(" ");
|
|
|
|
|
if (parts.length >= 5 && random.nextBoolean()) {
|
|
|
|
|
int start = (random.nextInt((parts.length - 3) / 2) + 1) * 2;
|
|
|
|
|
int end = Math.min(start + 2, parts.length - 1);
|
|
|
|
|
|
|
|
|
|
StringBuilder result = new StringBuilder();
|
|
|
|
|
for (int i = 0; i < parts.length; i++) {
|
|
|
|
|
if (i == start) {
|
|
|
|
|
result.append("( ");
|
|
|
|
|
}
|
|
|
|
|
result.append(parts[i]);
|
|
|
|
|
if (i == end) {
|
|
|
|
|
result.append(" )");
|
|
|
|
|
}
|
|
|
|
|
if (i < parts.length - 1) {
|
|
|
|
|
result.append(" ");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return result.toString();
|
|
|
|
|
}
|
|
|
|
|
return expression;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成初中数学表达式(包含平方和平方根)
|
|
|
|
|
*
|
|
|
|
|
* @return 数学表达式
|
|
|
|
|
*/
|
|
|
|
|
private String generateMiddleExpression() {
|
|
|
|
|
int termCount = random.nextInt(2) + 2; // 2或3个项
|
|
|
|
|
StringBuilder expr = new StringBuilder();
|
|
|
|
|
char[] operators = {'+', '-', '*', '/'};
|
|
|
|
|
int specialOpIndex = random.nextInt(termCount); // 随机决定哪一项是特殊运算
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < termCount; i++) {
|
|
|
|
|
if (i > 0) {
|
|
|
|
|
char op = operators[random.nextInt(operators.length)];
|
|
|
|
|
expr.append(" ").append(op).append(" ");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (i == specialOpIndex) {
|
|
|
|
|
// 强制生成特殊运算
|
|
|
|
|
if (random.nextBoolean()) {
|
|
|
|
|
// 生成平方运算
|
|
|
|
|
int num = random.nextInt(15) + 1; // 平方的底数小一些
|
|
|
|
|
expr.append("pow(").append(num).append(",2)");
|
|
|
|
|
} else {
|
|
|
|
|
// 生成开方运算, 确保是完全平方数
|
|
|
|
|
int sqrtNum = random.nextInt(10) + 1; // 1-10
|
|
|
|
|
int perfectSquare = sqrtNum * sqrtNum;
|
|
|
|
|
expr.append("(√").append(perfectSquare).append(")");
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
expr.append(random.nextInt(100) + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return expr.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 生成高中数学表达式(包含三角函数)
|
|
|
|
|
*
|
|
|
|
|
* @return 数学表达式
|
|
|
|
|
*/
|
|
|
|
|
private String generateHighExpression() {
|
|
|
|
|
int termCount = random.nextInt(2) + 2; // 2或3个项
|
|
|
|
|
StringBuilder expr = new StringBuilder();
|
|
|
|
|
char[] operators = {'+', '-', '*', '/'};
|
|
|
|
|
String[] functions = {"sin", "cos", "tan"};
|
|
|
|
|
int trigFuncIndex = random.nextInt(termCount); // 随机决定哪一项是三角函数
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < termCount; i++) {
|
|
|
|
|
if (i > 0) {
|
|
|
|
|
char op = operators[random.nextInt(operators.length)];
|
|
|
|
|
expr.append(" ").append(op).append(" ");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (i == trigFuncIndex) {
|
|
|
|
|
// 强制生成三角函数
|
|
|
|
|
String func = functions[random.nextInt(functions.length)];
|
|
|
|
|
int angle;
|
|
|
|
|
if ("tan".equals(func)) {
|
|
|
|
|
angle = random.nextInt(90); // tan的角度范围 0-89
|
|
|
|
|
} else {
|
|
|
|
|
angle = random.nextInt(361); // sin/cos的角度范围 0-360
|
|
|
|
|
}
|
|
|
|
|
expr.append(func).append("(").append(angle).append(")");
|
|
|
|
|
} else {
|
|
|
|
|
expr.append(random.nextInt(100) + 1);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return expr.toString();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 计算表达式的值
|
|
|
|
|
*
|
|
|
|
|
* @param expression 数学表达式
|
|
|
|
|
* @return 计算结果
|
|
|
|
|
*/
|
|
|
|
|
private double evaluateExpression(String expression) {
|
|
|
|
|
// 预处理表达式,将自定义格式转换为标准格式
|
|
|
|
|
String processedExpression = expression.replaceAll("√(\\d+)", "sqrt($1)");
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
return eval(processedExpression);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
System.err.println("表达式求值失败: " + expression);
|
|
|
|
|
System.err.println("处理后: " + processedExpression);
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
throw new IllegalArgumentException("无效的表达式: " + expression, e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 表达式求值器
|
|
|
|
|
*
|
|
|
|
|
* @param expr 表达式字符串
|
|
|
|
|
* @return 计算结果
|
|
|
|
|
*/
|
|
|
|
|
private double eval(String expr) {
|
|
|
|
|
return new Object() {
|
|
|
|
|
int pos = -1;
|
|
|
|
|
int ch;
|
|
|
|
|
|
|
|
|
|
void nextChar() {
|
|
|
|
|
ch = (++pos < expr.length()) ? expr.charAt(pos) : -1;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
boolean eat(int charToEat) {
|
|
|
|
|
while (ch == ' ') {
|
|
|
|
|
nextChar();
|
|
|
|
|
}
|
|
|
|
|
if (ch == charToEat) {
|
|
|
|
|
nextChar();
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double parse() {
|
|
|
|
|
nextChar();
|
|
|
|
|
double x = parseExpression();
|
|
|
|
|
if (pos < expr.length()) {
|
|
|
|
|
throw new RuntimeException("Unexpected: " + (char) ch);
|
|
|
|
|
}
|
|
|
|
|
return x;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double parseExpression() {
|
|
|
|
|
double x = parseTerm();
|
|
|
|
|
for (;;) {
|
|
|
|
|
if (eat('+')) {
|
|
|
|
|
x += parseTerm();
|
|
|
|
|
} else if (eat('-')) {
|
|
|
|
|
x -= parseTerm();
|
|
|
|
|
} else {
|
|
|
|
|
return x;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double parseTerm() {
|
|
|
|
|
double x = parseFactor();
|
|
|
|
|
for (;;) {
|
|
|
|
|
if (eat('*')) {
|
|
|
|
|
x *= parseFactor();
|
|
|
|
|
} else if (eat('/')) {
|
|
|
|
|
x /= parseFactor();
|
|
|
|
|
} else {
|
|
|
|
|
return x;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double parseFactor() {
|
|
|
|
|
if (eat('+')) {
|
|
|
|
|
return parseFactor();
|
|
|
|
|
}
|
|
|
|
|
if (eat('-')) {
|
|
|
|
|
return -parseFactor();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
double x;
|
|
|
|
|
int startPos = this.pos;
|
|
|
|
|
if (eat('(')) {
|
|
|
|
|
x = parseExpression();
|
|
|
|
|
eat(')');
|
|
|
|
|
} else if ((ch >= '0' && ch <= '9') || ch == '.') {
|
|
|
|
|
while ((ch >= '0' && ch <= '9') || ch == '.') {
|
|
|
|
|
nextChar();
|
|
|
|
|
}
|
|
|
|
|
x = Double.parseDouble(expr.substring(startPos, this.pos));
|
|
|
|
|
} else if (ch >= 'a' && ch <= 'z') {
|
|
|
|
|
while (ch >= 'a' && ch <= 'z') {
|
|
|
|
|
nextChar();
|
|
|
|
|
}
|
|
|
|
|
String func = expr.substring(startPos, this.pos);
|
|
|
|
|
x = parseFactor();
|
|
|
|
|
if (func.equals("sqrt")) {
|
|
|
|
|
x = Math.sqrt(x);
|
|
|
|
|
} else if (func.equals("sin")) {
|
|
|
|
|
x = Math.sin(x);
|
|
|
|
|
} else if (func.equals("cos")) {
|
|
|
|
|
x = Math.cos(x);
|
|
|
|
|
} else if (func.equals("tan")) {
|
|
|
|
|
x = Math.tan(x);
|
|
|
|
|
} else {
|
|
|
|
|
throw new RuntimeException("Unknown function: " + func);
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
throw new RuntimeException("Unexpected: " + (char) ch);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return x;
|
|
|
|
|
}
|
|
|
|
|
}.parse();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建题目对象
|
|
|
|
|
*
|
|
|
|
|
* @param expression 数学表达式
|
|
|
|
|
* @param correctAnswer 正确答案
|
|
|
|
|
* @return 题目对象
|
|
|
|
|
*/
|
|
|
|
|
private Question createQuestion(String expression, double correctAnswer) {
|
|
|
|
|
String questionText = "计算: " + expression + " = ?";
|
|
|
|
|
double[] options = new double[4];
|
|
|
|
|
int correctIndex = random.nextInt(4);
|
|
|
|
|
|
|
|
|
|
options[correctIndex] = correctAnswer;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
|
if (i != correctIndex) {
|
|
|
|
|
double offset = (random.nextDouble() * 20 - 10);
|
|
|
|
|
if (Math.abs(offset) < 1) {
|
|
|
|
|
offset = random.nextBoolean() ? 5 : -5;
|
|
|
|
|
}
|
|
|
|
|
options[i] = correctAnswer + offset;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String[] optionStrings = new String[4];
|
|
|
|
|
for (int i = 0; i < 4; i++) {
|
|
|
|
|
optionStrings[i] = String.format("%c. %.2f",
|
|
|
|
|
(char) ('A' + i), options[i]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return new Question(expression, questionText, optionStrings, correctIndex);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 加载用户历史题目
|
|
|
|
|
*
|
|
|
|
|
* @param email 用户邮箱
|
|
|
|
|
* @return 历史题目集合
|
|
|
|
|
*/
|
|
|
|
|
private Set<String> loadUserQuestions(String email) {
|
|
|
|
|
Set<String> questions = new HashSet<>();
|
|
|
|
|
File userDir = new File("questions" + File.separator + email);
|
|
|
|
|
|
|
|
|
|
if (!userDir.exists() || !userDir.isDirectory()) {
|
|
|
|
|
return questions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
File[] files = userDir.listFiles((dir, name) -> name.endsWith(".txt"));
|
|
|
|
|
if (files == null) {
|
|
|
|
|
return questions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (File file : files) {
|
|
|
|
|
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
|
|
|
|
|
String line;
|
|
|
|
|
while ((line = reader.readLine()) != null) {
|
|
|
|
|
line = line.trim();
|
|
|
|
|
if (line.matches("\\d+\\.\\s+.*")) {
|
|
|
|
|
String question = line.substring(line.indexOf('.') + 1).trim();
|
|
|
|
|
questions.add(question);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
System.err.println("读取文件失败: " + e.getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return questions;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 保存用户题目到文件
|
|
|
|
|
*
|
|
|
|
|
* @param email 用户邮箱
|
|
|
|
|
* @param questions 题目列表
|
|
|
|
|
*/
|
|
|
|
|
private void saveUserQuestions(String email, List<String> questions) {
|
|
|
|
|
File userDir = new File("questions" + File.separator + email);
|
|
|
|
|
if (!userDir.exists()) {
|
|
|
|
|
if (!userDir.mkdirs()) {
|
|
|
|
|
System.err.println("创建目录失败");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
LocalDateTime now = LocalDateTime.now();
|
|
|
|
|
String fileName = now.format(
|
|
|
|
|
DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) + ".txt";
|
|
|
|
|
File file = new File(userDir, fileName);
|
|
|
|
|
|
|
|
|
|
try (PrintWriter writer = new PrintWriter(new FileWriter(file))) {
|
|
|
|
|
for (int i = 0; i < questions.size(); i++) {
|
|
|
|
|
writer.println((i + 1) + ". " + questions.get(i));
|
|
|
|
|
if (i < questions.size() - 1) {
|
|
|
|
|
writer.println();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
System.err.println("保存文件失败: " + e.getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|