更改题目生成逻辑 #6

Merged
hnu202326010318 merged 1 commits from liguolin_branch into develop 3 months ago

@ -9,54 +9,46 @@
<version>1.0.0</version>
<properties>
<!-- 编译版本 -->
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!-- JavaFX 版本LTS 推荐) -->
<javafx.version>21.0.8</javafx.version>
</properties>
<dependencies>
<!-- Google Gson -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
<!-- JavaFX Controls (包含 ActionEvent, Button, Scene 等) -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-controls</artifactId>
<version>${javafx.version}</version>
</dependency>
<!-- JavaFX FXML (如果你使用 .fxml 布局文件) -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<!-- JavaFX Graphics (基础图形支持) -->
<dependency>
<groupId>org.openjfx</groupId>
<artifactId>javafx-graphics</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-email</artifactId>
<version>1.5</version>
</dependency>
<dependency>
<groupId>org.mozilla</groupId>
<artifactId>rhino-engine</artifactId>
<version>1.7.14</version>
</dependency>
</dependencies>
<build>
<plugins>
<!-- 插件1: 编译插件(确保使用 JDK 17 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
@ -67,24 +59,19 @@
</configuration>
</plugin>
<!-- 插件2: Assembly 打包插件(用于生成可运行的 fat jar -->
<!-- 注意:开发时不要禁用默认 jar否则 IDEA 无法运行 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>3.6.0</version>
<configuration>
<archive>
<manifest>
<!-- 替换为你的主类全路径 -->
<mainClass>com.mathgenerator.Application</mainClass>
<mainClass>com.mathgenerator.MainApplication</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
<finalName>mathgenerator</finalName>
<appendAssemblyId>false</appendAssemblyId>
<goal>single</goal>
</configuration>
<executions>
<execution>

@ -4,75 +4,63 @@ import com.mathgenerator.model.ChoiceQuestion;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* ()
*
* ( - )
*
*/
public class JuniorHighSchoolGenerator extends PrimarySchoolGenerator {
private static final Pattern NUMBER_PATTERN = Pattern.compile("\\d+");
private static final int[] PERFECT_SQUARES = {4, 9, 16, 25, 36, 49, 64, 81, 100};
private static final int[] PERFECT_SQUARES = {1, 4, 9, 16, 25, 36, 49, 64, 81, 100};
@Override
public ChoiceQuestion generateSingleQuestion() {
String questionText;
int correctAnswer;
ThreadLocalRandom random = ThreadLocalRandom.current();
int operandCount = random.nextInt(2, 5); // 2到4个操作数
// 循环直到生成一个有效的、有整数解的题目
while (true) {
// 1. 获取一个基础的小学题目字符串
String basicQuestionText = super.generateBasicQuestionText();
// 2. 随机选择在题目中加入平方还是开根号
boolean useSquare = ThreadLocalRandom.current().nextBoolean();
if (useSquare) {
// --- 平方策略 ---
// 随机找到一个数字,给它加上平方
Matcher matcher = NUMBER_PATTERN.matcher(basicQuestionText);
List<String> numbers = new ArrayList<>();
while (matcher.find()) { numbers.add(matcher.group()); }
if (numbers.isEmpty()) continue;
// 1. 生成基础的表达式组件列表
List<String> parts = new ArrayList<>();
// 使用 getOperand() 和 getRandomOperator() 这些继承自父类的方法
parts.add(String.valueOf(getOperand()));
for (int i = 1; i < operandCount; i++) {
parts.add(getRandomOperator());
parts.add(String.valueOf(getOperand()));
}
String numToSquare = numbers.get(ThreadLocalRandom.current().nextInt(numbers.size()));
questionText = basicQuestionText.replaceFirst(Pattern.quote(numToSquare), numToSquare + "²");
// 2. 结构化地插入初中特色运算
int modificationIndex = random.nextInt(operandCount) * 2; // 随机选择一个操作数的位置
boolean useSquare = random.nextBoolean();
} else {
// --- 开根号策略 (保证整数解) ---
// a. 随机找到一个数字
Matcher matcher = NUMBER_PATTERN.matcher(basicQuestionText);
List<String> numbers = new ArrayList<>();
while (matcher.find()) { numbers.add(matcher.group()); }
if (numbers.isEmpty()) continue;
String numToReplace = numbers.get(ThreadLocalRandom.current().nextInt(numbers.size()));
if (useSquare) {
// 平方策略:直接在数字后附加平方符号
parts.set(modificationIndex, parts.get(modificationIndex) + "²");
} else {
// 开根号策略:用一个完美的开根号表达式替换整个数字
int perfectSquare = PERFECT_SQUARES[random.nextInt(PERFECT_SQUARES.length)];
parts.set(modificationIndex, "√" + perfectSquare);
}
// b. 随机选一个完美的平方数
int perfectSquare = PERFECT_SQUARES[ThreadLocalRandom.current().nextInt(PERFECT_SQUARES.length)];
// 3. (可选)为增强后的表达式添加括号
if (operandCount > 2 && random.nextBoolean()) {
super.addParentheses(parts); // 调用父类的protected方法
}
// c. 用 "√(平方数)" 替换掉原数字
String sqrtExpression = "√" + perfectSquare;
questionText = basicQuestionText.replaceFirst(Pattern.quote(numToReplace), sqrtExpression);
}
String finalQuestionText = String.join(" ", parts);
// 3. 验证修改后的题目是否有整数解
try {
Object result = evaluateExpression(questionText);
if (result instanceof Number && ((Number) result).doubleValue() == ((Number) result).intValue()) {
correctAnswer = ((Number) result).intValue();
break; // 成功!得到整数解,跳出循环
}
} catch (Exception e) {
// 如果表达式计算出错(例如语法错误),则忽略并重试
}
// 4. 计算答案
double finalCorrectAnswer;
try {
Object result = evaluateExpression(finalQuestionText);
finalCorrectAnswer = ((Number) result).doubleValue();
} catch (Exception e) {
// 发生意外,安全返回一个小学题
return super.generateSingleQuestion();
}
// 4. 为最终的题目生成选项
List<String> options = generateOptions(correctAnswer);
int correctIndex = options.indexOf(String.valueOf(correctAnswer));
// 5. 生成选项
List<String> options = generateDecimalOptions(finalCorrectAnswer);
int correctIndex = options.indexOf(formatNumber(finalCorrectAnswer));
return new ChoiceQuestion(questionText, options, correctIndex);
return new ChoiceQuestion(finalQuestionText, options, correctIndex);
}
}

@ -10,6 +10,7 @@ import java.util.concurrent.ThreadLocalRandom;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;
import java.text.DecimalFormat;
/**
*
@ -87,30 +88,81 @@ public class PrimarySchoolGenerator implements QuestionGenerator {
}
/**
* 使JVM
* 使JVM ()
* @param expression
* @return
* @return (IntegerDouble)
* @throws ScriptException
*/
protected Object evaluateExpression(String expression) throws ScriptException {
ScriptEngineManager manager = new ScriptEngineManager();
ScriptEngine engine = manager.getEngineByName("JavaScript");
// 为支持初高中计算,替换特殊符号
// 注意√的正则表达式,确保只匹配数字
expression = expression.replaceAll("²", "**2")
.replaceAll("√(\\d+)", "Math.sqrt($1)");
return engine.eval(expression);
// --- 核心修改在这里 ---
// 使用 "rhino" 作为引擎名称这是Rhino引擎的官方名称
ScriptEngine engine = manager.getEngineByName("rhino");
if (engine == null) {
// 增加一个健壮性检查,如果引擎还是没找到,就给出清晰的错误提示
throw new IllegalStateException("错误找不到Rhino JavaScript引擎。请检查pom.xml中是否已添加rhino-engine的依赖。");
}
// Rhino不需要预定义函数可以直接计算
String script = expression.replaceAll("(\\d+(\\.\\d+)?)²", "Math.pow($1, 2)")
.replaceAll("√(\\d+(\\.\\d+)?)", "Math.sqrt($1)")
.replaceAll("(\\d+)°", " * (Math.PI / 180)"); // Rhino对角度计算的语法要求更严格
// 为了让sin/cos/tan能正确计算需要特殊处理
script = script.replaceAll("sin\\(", "Math.sin(")
.replaceAll("cos\\(", "Math.cos(")
.replaceAll("tan\\(", "Math.tan(");
return engine.eval(script);
}
/**
*
* @param number
* @return
*/
protected String formatNumber(double number) {
if (number == (long) number) {
return String.format("%d", (long) number); // 如果是整数,不显示小数位
} else {
// 使用DecimalFormat来去除末尾多余的0
DecimalFormat df = new DecimalFormat("#.##");
return df.format(number);
}
}
/**
*
* @param correctAnswer
* @return
*/
protected List<String> generateDecimalOptions(double correctAnswer) {
ThreadLocalRandom random = ThreadLocalRandom.current();
Set<String> options = new HashSet<>();
options.add(formatNumber(correctAnswer));
while (options.size() < 4) {
double delta = random.nextDouble(1, 11); // 答案加减1-10之间的随机小数
// 随机决定是加还是减
double distractor = random.nextBoolean() ? correctAnswer + delta : correctAnswer - delta;
options.add(formatNumber(distractor));
}
List<String> sortedOptions = new ArrayList<>(options);
Collections.shuffle(sortedOptions);
return sortedOptions;
}
private int getOperand() {
protected int getOperand() {
return ThreadLocalRandom.current().nextInt(1, 101);
}
private String getRandomOperator() {
protected String getRandomOperator() {
return OPERATORS[ThreadLocalRandom.current().nextInt(OPERATORS.length)];
}
private void addParentheses(List<String> parts) {
protected void addParentheses(List<String> parts) {
ThreadLocalRandom random = ThreadLocalRandom.current();
int startOperandIndex = random.nextInt(parts.size() / 2);
int endOperandIndex = random.nextInt(startOperandIndex + 1, parts.size() / 2 + 1);

@ -6,35 +6,40 @@ import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
/**
* ()
*
* ( - )
*
*/
public class SeniorHighSchoolGenerator extends JuniorHighSchoolGenerator {
// 只使用值为整数的三角函数
private static final Map<String, Integer> TRIG_TERMS = Map.of(
"sin(90°)", 1,
"cos(0°)", 1,
"tan(45°)", 1
// 预设计算结果简单的三角函数项及其对应的值
private static final Map<String, Double> TRIG_TERMS = Map.of(
"sin(0°)", 0.0,
"sin(30°)", 0.5,
"sin(90°)", 1.0,
"cos(0°)", 1.0,
"cos(60°)", 0.5,
"cos(90°)", 0.0,
"tan(45°)", 1.0
);
// 将Map的键转换为数组方便随机选取
private static final String[] TRIG_KEYS = TRIG_TERMS.keySet().toArray(new String[0]);
@Override
public ChoiceQuestion generateSingleQuestion() {
// 1. 先生成一个保证有整数解的初中选择题
// 1. 先生成一个保证可计算的、高性能的初中选择题,作为基础
ChoiceQuestion juniorHighQuestion = super.generateSingleQuestion();
String juniorQuestionText = juniorHighQuestion.questionText();
int juniorCorrectAnswer = Integer.parseInt(juniorHighQuestion.options().get(juniorHighQuestion.correctOptionIndex()));
// 直接从初中题的答案反推其数值
double juniorCorrectAnswer = Double.parseDouble(juniorHighQuestion.options().get(juniorHighQuestion.correctOptionIndex()));
// 2. 随机选择一个值为整数的三角函数项
// 2. 随机选择一个预设的三角函数项
String trigKey = TRIG_KEYS[ThreadLocalRandom.current().nextInt(TRIG_KEYS.length)];
int trigValue = TRIG_TERMS.get(trigKey);
// 3. 随机决定是加还是减
boolean useAddition = ThreadLocalRandom.current().nextBoolean();
double trigValue = TRIG_TERMS.get(trigKey);
// 3. 构造最终的高中题目
String finalQuestionText;
int finalCorrectAnswer;
double finalCorrectAnswer;
boolean useAddition = ThreadLocalRandom.current().nextBoolean(); // 随机决定是加还是减
if (useAddition) {
finalQuestionText = "(" + juniorQuestionText + ") + " + trigKey;
@ -45,8 +50,8 @@ public class SeniorHighSchoolGenerator extends JuniorHighSchoolGenerator {
}
// 4. 为最终的题目生成选项
List<String> options = generateOptions(finalCorrectAnswer);
int correctIndex = options.indexOf(String.valueOf(finalCorrectAnswer));
List<String> options = generateDecimalOptions(finalCorrectAnswer);
int correctIndex = options.indexOf(formatNumber(finalCorrectAnswer));
return new ChoiceQuestion(finalQuestionText, options, correctIndex);
}

Loading…
Cancel
Save