From 349bbaf2bee548aad6a1720a475c4a0a7c3dc13d Mon Sep 17 00:00:00 2001 From: smallbailangui Date: Tue, 7 Oct 2025 22:38:33 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=94=B9=E9=A2=98=E7=9B=AE=E7=94=9F?= =?UTF-8?q?=E6=88=90=E6=96=B9=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 29 ++---- .../generator/JuniorHighSchoolGenerator.java | 94 ++++++++----------- .../generator/PrimarySchoolGenerator.java | 74 ++++++++++++--- .../generator/SeniorHighSchoolGenerator.java | 39 ++++---- 4 files changed, 134 insertions(+), 102 deletions(-) diff --git a/pom.xml b/pom.xml index 800a271..4d69a1a 100644 --- a/pom.xml +++ b/pom.xml @@ -9,54 +9,46 @@ 1.0.0 - 17 17 UTF-8 - - 21.0.8 - com.google.code.gson gson 2.10.1 - org.openjfx javafx-controls ${javafx.version} - org.openjfx javafx-fxml ${javafx.version} - - - org.openjfx - javafx-graphics - ${javafx.version} - - org.apache.commons commons-email 1.5 + + + org.mozilla + rhino-engine + 1.7.14 + - org.apache.maven.plugins maven-compiler-plugin @@ -67,24 +59,19 @@ - - + org.apache.maven.plugins maven-assembly-plugin 3.6.0 - - com.mathgenerator.Application + com.mathgenerator.MainApplication jar-with-dependencies - mathgenerator - false - single diff --git a/src/main/java/com/mathgenerator/generator/JuniorHighSchoolGenerator.java b/src/main/java/com/mathgenerator/generator/JuniorHighSchoolGenerator.java index 7922dc4..add01a1 100644 --- a/src/main/java/com/mathgenerator/generator/JuniorHighSchoolGenerator.java +++ b/src/main/java/com/mathgenerator/generator/JuniorHighSchoolGenerator.java @@ -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 numbers = new ArrayList<>(); - while (matcher.find()) { numbers.add(matcher.group()); } - if (numbers.isEmpty()) continue; + // 1. 生成基础的表达式组件列表 + List 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 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 options = generateOptions(correctAnswer); - int correctIndex = options.indexOf(String.valueOf(correctAnswer)); + // 5. 生成选项 + List options = generateDecimalOptions(finalCorrectAnswer); + int correctIndex = options.indexOf(formatNumber(finalCorrectAnswer)); - return new ChoiceQuestion(questionText, options, correctIndex); + return new ChoiceQuestion(finalQuestionText, options, correctIndex); } } \ No newline at end of file diff --git a/src/main/java/com/mathgenerator/generator/PrimarySchoolGenerator.java b/src/main/java/com/mathgenerator/generator/PrimarySchoolGenerator.java index ff357b0..9ed1061 100644 --- a/src/main/java/com/mathgenerator/generator/PrimarySchoolGenerator.java +++ b/src/main/java/com/mathgenerator/generator/PrimarySchoolGenerator.java @@ -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 计算结果 (可能是Integer或Double) * @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 generateDecimalOptions(double correctAnswer) { + ThreadLocalRandom random = ThreadLocalRandom.current(); + Set 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 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 parts) { + protected void addParentheses(List parts) { ThreadLocalRandom random = ThreadLocalRandom.current(); int startOperandIndex = random.nextInt(parts.size() / 2); int endOperandIndex = random.nextInt(startOperandIndex + 1, parts.size() / 2 + 1); diff --git a/src/main/java/com/mathgenerator/generator/SeniorHighSchoolGenerator.java b/src/main/java/com/mathgenerator/generator/SeniorHighSchoolGenerator.java index 43ecb30..4dc6be4 100644 --- a/src/main/java/com/mathgenerator/generator/SeniorHighSchoolGenerator.java +++ b/src/main/java/com/mathgenerator/generator/SeniorHighSchoolGenerator.java @@ -6,35 +6,40 @@ import java.util.Map; import java.util.concurrent.ThreadLocalRandom; /** - * 高中选择题生成器 (已修正)。 - * 通过在初中题目的基础上添加值为整数的三角函数项,确保最终解为整数。 + * 高中选择题生成器 (最终版 - 采用构造式添加)。 + * 通过在已生成的初中题基础上添加预设的三角函数项来构造题目。 */ public class SeniorHighSchoolGenerator extends JuniorHighSchoolGenerator { - // 只使用值为整数的三角函数 - private static final Map TRIG_TERMS = Map.of( - "sin(90°)", 1, - "cos(0°)", 1, - "tan(45°)", 1 + // 预设计算结果简单的三角函数项及其对应的值 + private static final Map 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 options = generateOptions(finalCorrectAnswer); - int correctIndex = options.indexOf(String.valueOf(finalCorrectAnswer)); + List options = generateDecimalOptions(finalCorrectAnswer); + int correctIndex = options.indexOf(formatNumber(finalCorrectAnswer)); return new ChoiceQuestion(finalQuestionText, options, correctIndex); } -- 2.34.1