结对项目:自动生成小学四则运算题目的命令行程序

news/2024/9/28 22:31:45
这个作业属于哪个课程 计科22级12班
这个作业要求在哪里 https://edu.cnblogs.com/campus/gdgy/CSGrade22-12/homework/13221
姓名 学号
曾繁曦 3122004841
吴健民 3122004667

PSP表格

一、流程图

二、模块设计

1.模块划分

  1. Main模块(Main.java)
    功能描述:程序的入口点,负责接收命令行参数,进行参数校验,调用其他模块的方法来生成题目、检查答案以及处理可能出现的异常情况,如超时异常和其他一般异常。
点击查看代码
```plaintext
package com.tsang.fancy_3122004841.Maths;import com.tsang.fancy_3122004841.Maths.entity.Args;
import com.tsang.fancy_3122004841.Maths.utils.ValidationUtils;import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;import static com.tsang.fancy_3122004841.Maths.utils.QuizGenerator.checkExercisesAnswers;
import static com.tsang.fancy_3122004841.Maths.utils.QuizGenerator.generateQuizzes;public class Main {public static void main(String[] args){int num = 0;try{//校验参数Args argsObj = ValidationUtils.validateArgs(args);Integer numberOfQuestions = argsObj.getNumberOfQuestions();num = numberOfQuestions;//?Integer range = argsObj.getRange();// 判题checkExercisesAnswers(argsObj.getExercisesFileName(), argsObj.getAnswerFileName());// 生成题目CompletableFuture<Void> task = CompletableFuture.runAsync(() -> generateQuizzes(numberOfQuestions, range));// 5秒超时task.get(5, TimeUnit.SECONDS);} catch (TimeoutException e) {System.err.println("数据范围不支持生成" + num + "道题,请调整参数!");} catch (Exception e) {System.err.println(e.getMessage());}}
}
</details>2. DuplicateChecker模块(DuplicateChecker.java)
功能描述:用于检查生成的数学表达式是否重复。通过维护一个特定结构的映射来存储已生成的表达式的结果、长度以及每个操作数和操作符的出现次数,以判断新生成的表达式是否与已有的重复。<details>
<summary>点击查看代码</summary>
package com.tsang.fancy_3122004841.Maths.utils;import com.tsang.fancy_3122004841.Maths.entity.Fraction;import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;public class DuplicateChecker {// key:表达式的结果;value:map(key:表达式的长度,value:map集合(key:表达式中的每一个操作数或操作符;value:该字符串出现的次数))private static final Map<String, Map<Integer, List<Map<String, Integer>>>> DUMPLICATE_MAP = new HashMap<>();public static boolean isDuplicate(Fraction result, String expression) {// 已创建的表达式中,如果有计算结果相同,且表达式中的所有字符和出现的次数都一样,就认为是重复的String resultStr = result.toString();expression = expression.replaceAll("[()]", "");Integer length = expression.length();// 统计表达式中每个操作数和操作符出现的次数Map<String, Integer> characterCountMap = Arrays.stream(expression.split("\\s+")).collect(Collectors.groupingBy(Function.identity(),Collectors.collectingAndThen(Collectors.counting(), Long::intValue)));Map<Integer, List<Map<String, Integer>>> expressionLengthMap = DUMPLICATE_MAP.get(resultStr);if (expressionLengthMap != null) {// 存在计算结果相同的表达式List<Map<String, Integer>> characterCountMapList = expressionLengthMap.get(length);if (characterCountMapList != null) {// 存在长度相同的表达式boolean isDuplicate = characterCountMapList.stream().anyMatch(map -> map.equals(characterCountMap));if (isDuplicate) {// 存在操作数和操作符出现次数相同的表达式return true;}}} else {expressionLengthMap = new HashMap<>();DUMPLICATE_MAP.put(resultStr, expressionLengthMap);}List<Map<String, Integer>> characterCountMapList = expressionLengthMap.computeIfAbsent(length, k -> new ArrayList<>());characterCountMapList.add(characterCountMap);return false;}
}
</details>3. ExpressionUtils模块(ExpressionUtils.java)
功能描述:提供了将中缀表达式转换为后缀表达式的方法,判断字符串是否为数字或操作符的方法,以及对后缀表达式进行求值的方法,包括允许负数和不允许负数的情况。<details>
<summary>点击查看代码</summary>
package com.tsang.fancy_3122004841.Maths.utils;import com.tsang.fancy_3122004841.Maths.entity.Fraction;import java.util.Deque;
import java.util.LinkedList;
import java.util.StringTokenizer;public class ExpressionUtils {public static String infixToPostfix(String infix) {StringBuilder postfix = new StringBuilder();Deque<Character> operatorStack = new LinkedList<>();StringTokenizer tokens = new StringTokenizer(infix, "()+-÷×", true);while (tokens.hasMoreTokens()) {String token = tokens.nextToken().trim();if (token.isEmpty()) {continue;}if (isNumber(token)) {postfix.append(token).append(' ');} else if ("(".equals(token)) {operatorStack.push('(');} else if (")".equals(token)) {while (!operatorStack.isEmpty() && !operatorStack.peek().equals('(')) {postfix.append(operatorStack.pop()).append(' ');}operatorStack.pop(); // Remove '('} else if (isOperator(token.charAt(0))) {while (!operatorStack.isEmpty() && precedence(operatorStack.peek()) >= precedence(token.charAt(0))) {postfix.append(operatorStack.pop()).append(' ');}operatorStack.push(token.charAt(0));}}while (!operatorStack.isEmpty()) {postfix.append(operatorStack.pop()).append(' ');}return postfix.toString().trim();}private static int precedence(char op) {switch (op) {case '+':case '-':return 1;case '×':case '÷':return 2;}return -1;}public static boolean isNumber(String operator) {String regex = "^\\d+'\\d+/\\d+$|^\\d+/\\d+$|^\\d+$";return operator.matches(regex);}private static boolean isOperator(char c) {return c == '+' || c == '-' || c == '×' || c == '÷';}public static Fraction evaluatePostfix(String postfix) {Deque<Fraction> stack = new LinkedList<>();String[] tokens = postfix.split(" ");for (String token : tokens) {if (isNumber(token)) {stack.push(Fraction.parseFraction(token));} else if (isOperator(token.charAt(0))) {Fraction operand2 = stack.pop();Fraction operand1 = stack.pop();Fraction result = FractionUtils.calculate(operand1, operand2, token.charAt(0));if (result.isNegative()) {// 负数return null;}stack.push(result);}}return stack.pop();}public static Fraction evaluatePostfixAllowNegative(String postfix) {Deque<Fraction> stack = new LinkedList<>();String[] tokens = postfix.split(" ");for (String token : tokens) {if (isNumber(token)) {stack.push(Fraction.parseFraction(token));} else if (isOperator(token.charAt(0))) {Fraction operand2 = stack.pop();Fraction operand1 = stack.pop();Fraction result = FractionUtils.calculate(operand1, operand2, token.charAt(0));stack.push(result);}}return stack.pop();}
}
</details>4. FileUtils模块(FileUtils.java)
功能描述:包含了一些与文件操作相关的实用方法,如验证文件名是否为有效的.txt格式、检查文件是否存在、删除已存在的文件等。<details>
<summary>点击查看代码</summary>
package com.tsang.fancy_3122004841.Maths.utils;import java.io.File;public class FileUtils {private static final String TXT_FILE_PATTERN = "^[a-zA-Z0-9_-]+\\.txt$";public static boolean isValidTxtFileName(String fileName) {return fileName.matches(TXT_FILE_PATTERN);}public static boolean isNotValidTxtName(String fileName) {return !isValidTxtFileName(fileName);}public static void deleteFileIfExists(String filePath) {File file = new File(filePath);if (file.exists() && file.delete()) {System.out.println("旧题目文件已删除: " + filePath);}}public static void validateFileExists(String filePath) {if (!new File(filePath).exists()) {throw new IllegalArgumentException("文件不存在: " + filePath);}}
}
</details>5. FractionUtils模块(FractionUtils.java)
功能描述:提供了生成随机真分数和随机操作数的方法,以及根据给定的操作符对两个分数进行计算的方法。<details>
<summary>点击查看代码</summary>
package com.tsang.fancy_3122004841.Maths.utils;import com.tsang.fancy_3122004841.Maths.entity.Fraction;import java.util.Random;public class FractionUtils {private static final Random RANDOM = new Random();//随机生成真分数public static Fraction generateRandomFraction(int range) {//分子 0 ~ range-1int numerator = RANDOM.nextInt(range);//分母 1 ~ range-1int denominator = RANDOM.nextInt(range - 1) + 1;return new Fraction(numerator, denominator);}public static Fraction generateRandomOperand(String operator, int range) {if (RANDOM.nextBoolean()) {// 真分数return generateRandomFraction(range);} else {// 自然数if ("÷".equals(operator)) {return new Fraction(RANDOM.nextInt(range - 1) + 1);}return new Fraction(RANDOM.nextInt(range));}}public static Fraction calculate(Fraction operand1, Fraction operand2, char operator) {switch (operator) {case '+':return operand1.add(operand2);case '-':return operand1.subtract(operand2);case '×':return operand1.multiply(operand2);case '÷':return operand1.divide(operand2);default:throw new IllegalArgumentException("无效的运算符");}}
}
</details>6. QuizGenerator模块(QuizGenerator.java)
功能描述:负责生成数学题目和答案的核心模块。包括随机生成操作符、操作符数量、数学表达式,添加随机括号,生成题目和答案文件,以及检查给定的题目文件和答案文件的正确性。<details>
<summary>点击查看代码</summary>
package com.tsang.fancy_3122004841.Maths.utils;import cn.hutool.core.io.FileUtil;
import com.tsang.fancy_3122004841.Maths.entity.Fraction;
import groovy.lang.Tuple2;
import io.micrometer.common.util.StringUtils;import java.io.File;
import java.util.*;public class QuizGenerator {private static final String[] OPERATORS = {"+", "-", "×", "÷"};private static final Random RANDOM = new Random();public static String generateRandomOperator() {return OPERATORS[RANDOM.nextInt(OPERATORS.length)];}public static int generateRandomOperatorCounts() {return RANDOM.nextInt(3) + 1;}public static Tuple2<List<String>, List<String>> generateQuiz(int range, int numberOfQuestions) {int duplicateCount = 0;int negativeCount = 0;int totalCount = 0;List<String> quizzes = new ArrayList<>(numberOfQuestions);List<String> answers = new ArrayList<>(numberOfQuestions);for(int i = 1; i <= numberOfQuestions; i++) {int maxOperators = generateRandomOperatorCounts();while(true) {totalCount++;List<String> operands = new ArrayList<>();List<String> operators = new ArrayList<>();for (int j = 0; j < maxOperators + 1; j++){String lastOperator = operators.isEmpty() ? "" : operators.get(operators.size() - 1);Fraction operand = FractionUtils.generateRandomOperand(lastOperator, range);operands.add(operand.toString());if (j < maxOperators) {operators.add(generateRandomOperator());}}StringBuilder quiz = new StringBuilder();for (int j = 0; j < operands.size(); j++) {quiz.append(operands.get(j));if (j < operators.size()) {quiz.append(" ").append(operators.get(j)).append(" ");}}String expression = quiz.toString();if (operands.size() > 2 && RANDOM.nextBoolean()) {expression = addRandomParentheses(expression);}String postfix = ExpressionUtils.infixToPostfix(expression);Fraction result = ExpressionUtils.evaluatePostfix(postfix);if (Objects.nonNull(result)) {if (!DuplicateChecker.isDuplicate(result, expression)) {quizzes.add(i + ". " + expression);answers.add(i + ". " + result);break;} else {duplicateCount++;}} else {negativeCount++;}}}System.out.println( numberOfQuestions + "道题目已生成,生成题目总次数:" + totalCount + ",重复次数(已去除该题目):" + duplicateCount + ",负数次数(已去除该题目):" + negativeCount);return new Tuple2<>(quizzes, answers);}public static void generateQuizzes(int numberOfQuestions, int range) {System.out.printf("生成题目的个数:" + numberOfQuestions + ",题目中数值的范围:0~%d(不包含%d)\n", range, range);Tuple2<List<String>, List<String>> quizAndAnswers = QuizGenerator.generateQuiz(range, numberOfQuestions);//当前用户目录("user.dir")(即工程根目录)String generateExercisesFilePath = System.getProperty("user.dir") + "/Exercises.txt";String generateAnswerFilePath = System.getProperty("user.dir") + "/Answers.txt";// 删除文件File exercisesFile = new File(generateExercisesFilePath);File answerFile = new File(generateAnswerFilePath);FileUtils.deleteFileIfExists(exercisesFile.getName());FileUtils.deleteFileIfExists(answerFile.getName());// 将题目和答案写入文件FileUtil.writeUtf8Lines(quizAndAnswers.getFirst(), exercisesFile);FileUtil.writeUtf8Lines(quizAndAnswers.getSecond(), answerFile);System.out.println("新生成的题目问题存入执行程序的当前目录下的Exercises.txt文件,路径如下:" + generateExercisesFilePath);System.out.println("新生成的题目答案存入执行程序的当前目录下的Exercises.txt文件,路径如下:" + generateAnswerFilePath);}private static String addRandomParentheses(String expression) {String[] tokens = expression.split(" ");StringBuilder result = new StringBuilder();List<Integer> addSubIndices = new ArrayList<>();for (int i = 1; i < tokens.length; i += 2) {String operator = tokens[i];if ("+".equals(operator) || "-".equals(operator)) {addSubIndices.add(i);}}if (!addSubIndices.isEmpty()) {Integer index = addSubIndices.get(RANDOM.nextInt(addSubIndices.size()));for (int i = 0; i < tokens.length; i++) {if (i == index - 1) {result.append("(");}result.append(tokens[i]);if (i == index + 1) {result.append(")");} else if (i < tokens.length - 1) {result.append(" ");}}} else {result.append(expression);}return result.toString();}public static void checkExercisesAnswers(String exercisesFileName, String answerFileName) {if (StringUtils.isBlank(exercisesFileName) && StringUtils.isBlank(answerFileName)) {return;}String exercisesFilePath = System.getProperty("user.dir") + "/" + exercisesFileName;String answerFilePath = System.getProperty("user.dir") + "/" + answerFileName;FileUtils.validateFileExists(exercisesFilePath);FileUtils.validateFileExists(answerFilePath);List<String> exercises = FileUtil.readUtf8Lines(exercisesFilePath);List<String> answers = FileUtil.readUtf8Lines(answerFilePath);if (exercises.size() != answers.size()) {throw new IllegalStateException("题目和答案的数量不一致!");}System.out.println("开始校验题目和答案...");List<String> rightAnswers = new ArrayList<>();List<String> wrongAnswers = new ArrayList<>();for (int i = 0; i < exercises.size(); i++) {String[] parts = exercises.get(i).trim().split("\\.\\s+");String exercise = parts[1];String answer = answers.get(i).trim().split("\\.\\s+")[1];String infixToPostfix = ExpressionUtils.infixToPostfix(exercise);Fraction result = ExpressionUtils.evaluatePostfixAllowNegative(infixToPostfix);if (Objects.equals(result.toString(), answer)) {rightAnswers.add(String.valueOf(parts[0]));} else {wrongAnswers.add(String.valueOf(parts[0]));}}List<String> gradeList = new ArrayList<>();gradeList.add("Correct: " + rightAnswers.size() + "(" + String.join(", ", rightAnswers) + ")");gradeList.add("Wrong: " + wrongAnswers.size() + "(" + String.join(", ", wrongAnswers) + ")");FileUtil.writeUtf8Lines(gradeList, System.getProperty("user.dir") + "/Grade.txt");System.out.println("校验完成,结果已保存至 " + System.getProperty("user.dir") + "/Grade.txt");}
}
</details>7. ValidationUtils模块(ValidationUtils.java)
功能描述:用于验证命令行参数的合法性。从命令行参数中读取和解析特定的参数,如题目数量参数-n,题目文件参数-e和答案文件参数-a,并进行相应的合法性检查。<details>
<summary>点击查看代码</summary>
package com.tsang.fancy_3122004841.Maths.utils;import com.tsang.fancy_3122004841.Maths.entity.Args;import java.util.Objects;public class ValidationUtils {public static Args validateArgs(String[] args) {Args argsObj = new Args();String exercisesFileName = null;String answerFileName = null;//从命令行参数读取 -n 和 -r 参数for(int i = 0; i < args.length; i++){if(Objects.equals("-n", args[i]) && i+1 < args.length) {try{int numberOfQuestions = Integer.parseInt(args[i+1]);argsObj.setNumberOfQuestions(numberOfQuestions);}catch (NumberFormatException e) {throw new IllegalArgumentException("题目个数参数不合法:" + args[i+1]);}} else if (Objects.equals("-e", args[i]) && i+1 < args.length) {exercisesFileName = args[i + 1];} else if (Objects.equals("-a", args[i]) && i+1 < args.length) {answerFileName = args[i + 1];}}//验证 -e 和 -a 参数的存在性?if ((exercisesFileName == null && answerFileName != null) || (exercisesFileName != null && answerFileName == null)){throw new IllegalArgumentException("如果需要对给定题目文件和答案文件进行校验,则参数 -e 和 -a 必须同时给出");}//如果 -e 和 -a 参数都存在,校验文件名并进行答案校验if (exercisesFileName != null) {if(FileUtils.isNotValidTxtName(exercisesFileName)) {throw new IllegalArgumentException("题目文件格式不正确:" + exercisesFileName + ",必须为txt文件");}if(FileUtils.isNotValidTxtName(answerFileName)) {throw new IllegalArgumentException("答案文件格式不正确:" + answerFileName + ",必须为txt文件");}argsObj.setExercisesFileName(exercisesFileName);argsObj.setAnswerFileName(answerFileName);}return argsObj;}
}
</details>2.接口设计
1. ValidationUtils.validateArgs方法(ValidationUtils.java)
功能:接收命令行参数数组,对参数进行校验,并返回一个包含校验结果的Args对象。
输入参数:String[] args,命令行参数数组。
输出参数:Args对象,包含校验后的题目数量、题目文件和答案文件等信息。
2. QuizGenerator.generateQuiz方法(QuizGenerator.java)
功能:生成指定数量和数值范围的数学题目和答案。
输入参数:int range,数值范围;int numberOfQuestions,题目数量。
输出参数:Tuple2<List<String>, List<String>>,包含生成的题目列表和答案列表。
3. QuizGenerator.generateQuizzes方法(QuizGenerator.java)
功能:根据给定的题目数量和数值范围生成题目和答案文件。
输入参数:int numberOfQuestions,题目数量;int range,数值范围。
无特定输出参数,但会生成题目和答案文件,并在控制台输出相关信息。
4. QuizGenerator.checkExercisesAnswers方法(QuizGenerator.java)
功能:检查给定的题目文件和答案文件,并生成校验结果文件。
输入参数:String exercisesFileName,题目文件名;String answerFileName,答案文件名。
无特定输出参数,但会生成校验结果文件,并在控制台输出相关信息。##三、性能分析
![](https://img2024.cnblogs.com/blog/3512942/202409/3512942-20240928215116850-1867623322.png)##四、优化改进
* 代码优化方向:
1. 多线程与并发优化: 题目生成与校验部分已经采用了 CompletableFuture 实现异步执行,可以在题目生成过程中添加一些进度反馈机制(例如当前生成的题目数量),提升用户体验。 
2. 错误处理: 当前的异常处理相对简单,主要捕获 TimeoutException 和一般的 Exception。可以考虑为不同类型的错误定制化错误信息,例如文件读取失败、题目生成失败等,增加调试信息。
* 面向对象设计: 考虑进一步引入接口和抽象类的概念,将 FractionUtils 等工具类的逻辑拆分为更小的职责单元。可以创建一个 MathOperation 接口,每个运算符实现不同的运算逻辑。##五、测试结果
运行结果:
![](https://img2024.cnblogs.com/blog/3512942/202409/3512942-20240928220019584-1770128857.png)
Exercises.txt文件:
![](https://img2024.cnblogs.com/blog/3512942/202409/3512942-20240928220117912-694444517.png)
Answers.txt文件:
![](https://img2024.cnblogs.com/blog/3512942/202409/3512942-20240928220141267-1133094941.png)
Grade.txt文件:
![](https://img2024.cnblogs.com/blog/3512942/202409/3512942-20240928220208269-1088567821.png)##六、项目总结
我们两人合作完成了小学四则运算题目生成程序,项目成功实现了自动生成题目、控制数量与范围、存入文件及答案校验统计等功能。过程中解决了题目重复、高效处理大量题目及确保文件格式正确数量一致等问题。通过合作,我们学会团队协作,提升了 Java 编程及软件开发能力,收获颇丰,未来将继续优化性能与拓展功能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/65940.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

作业三:结对项目

结对项目 一、作业介绍这个作业属于哪个课程 班级的链接这个作业要求在哪里 作业要求的链接这个作业的目标 完成小学四则运算题目的命令行程序,熟悉项目开发流程,提高团队合作能力二、成员信息代码仓库 GitHub成员1 杨智雄-3122004409成员2 陈愉锋-3122004387三、效能分析 各…

题解 ABC373G【No Cross Matching】/ POJ3565【Ants】

题目描述 年轻的自然主义者比尔在学校里研究蚂蚁。他的蚂蚁以生活在苹果树上的蚜虫为食。每个蚂蚁群需要自己的苹果树来养活自己。比尔有一张地图,上面标有 \(n\) 个蚂蚁群和 \(n\) 棵苹果树的坐标。他知道蚂蚁从它们的蚂蚁群到它们的取食地点,然后返回蚂蚁群,都是使用化学标…

代码随想录算法训练营第三天|203.移除链表元素,707.设计链表,206.反转链表

203.移除链表元素文章链接:https://programmercarl.com/0203.移除链表元素.html#算法公开课 视频讲解:https://www.bilibili.com/video/BV18B4y1s7R9 题目出处:https://leetcode.cn/problems/remove-linked-list-elements/卡哥在这里讲解了为什么要使用虚拟头节点,以及使用…

Android页面跳转与返回机制详解

在Android开发中,页面跳转是实现应用功能交互的重要手段之一。本文将从Activity之间的跳转、Activity与Fragment之间的跳转、Fragment之间的跳转以及页面返回的问题四个方面进行详细解析。 一、Activity之间的跳转 Activity是Android应用的基本构建块,代表了一个用户界面的单…

04-Consul服务注册与发现

1.为什么要引入服务注册中心 1.1 原因 public static final String PAYMENT_SRV_URL = "http://localhost:8001";//硬编码微服务所在的IP地址和端口号硬编码到订单微服务中,会存在非常多的问题 (1)如果订单微服务和支付微服务的IP地址或者端口号发生了变化,则支付…

星际战甲:战甲配卡

题记部分 一、永恒烈焰(火鸡)进图开2,随后4技能升温、3技能降温,钢铁地图炮 二、标题三、标题— 业精于勤荒于嬉,行成于思毁于随 —