四则运算
合作者:欧阳雨祥、巫杰龙
github 地址:https://github.com/1471104698/szys
题目:实现一个自动生成小学四则运算题目的命令行程序(也可以用图像界面,具有相似功能)。
-
说明:
自然数:0, 1, 2, …。
真分数:1/2, 1/3, 2/3, 1/4, 1’1/2, …。
运算符:+, −, ×, ÷。
括号:(, )。
等号:=。
分隔符:空格(用于四则运算符和等号前后)。
算术表达式:
e = n | e1 + e2 | e1 − e2 | e1 × e2 | e1 ÷ e2 | (e),
其中e, e1和e2为表达式,n为自然数或真分数。
四则运算题目:e = ,其中e为算术表达式
PSP
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 30 | 40 |
| · Estimate | · 估计这个任务需要多少时间 | 30 | 40 |
| Development | 开发 | 2030 | 1800 |
| · Analysis | · 需求分析 (包括学习新技术) | 15 | 15 |
| · Design Spec | · 生成设计文档 | 30 | 30 |
| · Design Review | · 设计复审 (和同事审核设计文档) | 5 | 5 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
| · Design | · 具体设计 | 90 | 120 |
| · Coding | · 具体编码 | 1440 | 870 |
| · Code Review | · 代码复审 | 60 | 120 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 360 | 720 |
| Reporting | 报告 | 250 | 150 |
| · Test Report | · 测试报告 | 210 | 120 |
| · Size Measurement | · 计算工作量 | 10 | 10 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 20 |
| 合计 | 2310 | 1990 |
流程图:

代码实现:
主操作类:
主要是输入指令,然后根据指令进行不同的处理
比如确定了 数据的范围 range 和 题目的数量 questions,那么就会进行题目生成,并生成题目文件 和 答案文件
package cn.oy.szys; import java.io.IOException; import java.util.Scanner; /** * 对外开放主类 */ public class OpenMain { //数值范围,默认为 0 int range = 0; //题目数量,默认为 0 int questions = 0; public void test() { FileName question = new FileName(); System.out.println("当前已有的题目文件:"); //获取所有的题目文件数 question.getExercisesName(); //question.get_AnswersName(); System.out.println("生成题目的个数,示例:-n 10 "); System.out.println("题目中数值的范围,示例:-r 10"); System.out.println("对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,示例:-e <exercisefile>.txt -a <answerfile>.txt"); while (true) { //输入操作指令 inputOpStrs(); if(range < 0 || questions < 0){ System.out.println("参数值必须为正数"); continue; } if (range == 0 || questions == 0) { if (questions == 0) System.out.println("未输入题目数量"); else{ System.out.println("将生成题目数量为:" + questions); } if (range == 0) System.out.println("未输入数值范围"); else{ System.out.println("生成的题目的数值范围为:" + range); } } else { new Create(range, questions);//题目生成 System.out.println("题目生成完毕,题目文件存放在项目目录的 Exercises 文件夹中,答案文件存放在 Answers 文件夹中"); this.range = 0; //初始化数组范围和题目数量范围 this.questions = 0; System.out.println("数值范围与题目数量已初始化"); System.out.println("当前已有的题目文件:"); question.getExercisesName(); } } } private void inputOpStrs(){ // 创建Scanner对象 Scanner scanner = new Scanner(System.in); System.out.print("输入指令:"); // 获取操作指令 String opStr = scanner.nextLine(); //操作指令分割,通过空格分割,变成操作指令字符串 String[] opStrs = opStr.split("\\s+"); //根据不同的情况进行处理 switch (opStrs[0]) { case "-n": this.questions = Integer.parseInt(opStrs[1]); break; case "-r": this.range = Integer.parseInt(opStrs[1]); break; case "-e": //-e Exercises1.txt -a Answers1.txt try { //opStrs[1]是题目文件名,opStrs[3]是答案文件名,FileOperate:文件操作类 //根据传入的题目文件名 和 答案文件名 进行 匹配校验 new FileOperate().proFile(opStrs[1], opStrs[3], questions); System.out.println("对错判断完毕,请在 Finally 文件夹中查看"); } catch (IOException e) { e.printStackTrace(); } break; default: break; } } }
表示式存储类:
存储表达式,并根据表达式计算出结果(这里只放一部分)
class Exercise { int value; //数的和,单个数的时候是本身,两个数的时候是两数的运算结果 int value1; //两个数的时候第一个数 int value2; //两个数的时候第二个数 int sign = 0;//符号标志,默认为 0,整数 String createExercise; //createExercise 用来存放算式 String fraction; //fraction 用来存放分数结果 char symbol; boolean swap = false; //是否出现负数需要调换 boolean isFraction = false; //是否是分式 char[] symbols = new char[]{'+', '-', '*', '/'}; public Exercise(int range) { if ((Math.random() * 2) <= 1) { //只有一数的情况 value = (int) (Math.random() * (range - 1) + 1); createExercise = String.valueOf(value); symbol = '\0'; } else { //两个数的情况 value1 = (int) (Math.random() * range + 1); value2 = (int) (Math.random() * range + 1); //随机选取一个符号 + - * / symbol = symbols[(int) (Math.random() * 4)]; //如果是 / ,那么特殊处理 if (symbol == '/' && value1 % value2 != 0) { isFraction = true; fraction = proFraction(value1, value2); } else { value = getValue(value1, value2, symbol); } if (swap) { value = -value; createExercise = "(" + value2 + symbol + value1 + ")"; } else { createExercise = "(" + value1 + symbol + value2 + ")"; } } } public int getValue(int a, int b, char symbol) { switch (symbol) { case '+': return a + b; case '-': if (a < b) swap = true; return a - b; case '/': return a / b; case '*': return a * b; } return 0; } /** * 计算结果,得到表达式的答案 * * @param a * @param b * @param c * @param d * @param symbol * @return */ public String getAnswer(int a, int b, int c, int d, char symbol) { int molecule; int denominator; switch (symbol) { case '+': molecule = a * d + b * c; denominator = b * d; return proFraction(molecule, denominator); case '-': molecule = a * d - b * c; denominator = b * d; if (molecule < 0) { this.sign = 1; return proFraction(-molecule, denominator); } return proFraction(molecule, denominator); case '/': molecule = a * d; denominator = b * c; return proFraction(molecule, denominator); case '*': molecule = a * c; denominator = b * d; return proFraction(molecule, denominator); } return null; } /** * 处理分数 * * @param molecule 分子 * @param denominator 分母 * @return */ public String proFraction(int molecule, int denominator) { //商 int merchant = molecule / denominator; //将分数化为真分数,那么需要将分子减去多余的分母的倍数 molecule -= merchant * denominator; //得到分子和分母的最大公约数 int gcdNum = gcd(molecule, denominator); //存储化简分数结果 String res = ""; //分子为 0,那么结果直接就是 商 if (molecule == 0) { res = String.valueOf(0); } else { //分子不为 0,那么根据商的结果判断是真分数还是假分数,对应做处理 res = String.valueOf(molecule / gcdNum) + '/' + denominator / gcdNum; //如果商不为 0,那么我们将商的结果也加到结果字符串中 if (merchant != 0) { res = String.valueOf(merchant) + '’' + res; } } return res; } private int gcd(int a, int b) { if (a % b == 0) { return b; } return gcd(b, a % b); } }
文件操作类
用于传入题目和给定答案的匹配校验,基本看注释就知道流程
package cn.oy.szys; import cn.oy.util.IOUtils; import cn.oy.constant.IOConstant; import cn.oy.constant.StringConstant; import java.io.*; import java.util.*; /** * 对传入的答案 和 题目 文件进行批改处理 */ public class FileOperate {//传式子跟答案进文件 String suffix = StringConstant.FILE_SUFFIX; //将题目文件 和 答案文件 进行匹配校验,并写入 Grade 文件中 public FileOperate() { } /** * 生成 题目文件和 答案文件 * @param exercises * @param answers * @param exercisesName * @param answersName * @param num */ public void doCreate(String[] exercises, String[] answers, String exercisesName, String answersName, int num){ String line = "\n"; try { //获取题目和答案字节流 BufferedWriter writer1 = (BufferedWriter) IOUtils.getIO(StringConstant.EXERCISES_PATH + exercisesName, IOConstant.WRITER.name()); BufferedWriter writer2 = (BufferedWriter) IOUtils.getIO(StringConstant.ANSWERS_PATH + answersName, IOConstant.WRITER.name()); //将表达式写入到对应的文件中 for (int i = 1; i <= num; i++) { writer1.write(i + "、" + exercises[i - 1] + "=" + line); writer1.flush(); writer2.write(i + "、" + exercises[i - 1] + "=" + answers[i - 1] + line); writer2.flush(); } //关闭流 IOUtils.closeIO(writer1, writer2); } catch (IOException e) { e.printStackTrace(); } } /** * 题目 和 答案进行校对,并将结果输入到 Finally 文件中 * @param exercisesName * @param answersName * @throws IOException */ public void proFile(String exercisesName, String answersName, int questions) throws IOException{ //题目序号 int idx = 1; //正确题数 int correctCount = 0; //错误题数 int wrongCount = 0; if(questions <= 0){ questions = 10000; } //存储正确题目的标号(从 0 开始计算,偏移量为 1) String[] corrects = new String[questions]; //存储错误题目的标号 String[] wrongs = new String[questions]; //获取题目文件的序号 int index = exercisesName.indexOf("."); char num = exercisesName.charAt(index - 1); //获取官方 Answers.txt 路径 + 文件名 String officialAnswerFile = StringConstant.ANSWERS_PATH + StringConstant.ANSWERS_PREFIXX + num + suffix; //获取给定 Answers.txt 路径 + 文件名 String givenAnswerFile = StringConstant.ANSWERS_PATH + answersName; //获取官方答案文件字节流 BufferedReader reader1 = (BufferedReader) IOUtils.getIO( officialAnswerFile, IOConstant.READER.name()); //获取给定答案文件字节流 BufferedReader reader2 = (BufferedReader) IOUtils.getIO(givenAnswerFile, IOConstant.READER.name()); String str1 = ""; String str2 = ""; //将官方答案和给定答案进行匹配校验 while ((str1 = reader1.readLine()) != null && (str2 = reader2.readLine()) != null) { if (str1.equals(str2) && !str1.equals("\n")) { corrects[correctCount++] = String.valueOf(idx); } else { wrongs[wrongCount++] = String.valueOf(idx); } idx++; } //关闭流 IOUtils.closeIO(reader1,reader2); //记录正确题号和错误题号到文件中 inputFinally(correctCount, wrongCount, corrects, wrongs); } /** * 将正确的题号 和 错误的题号 输入到 Finally.txt 文件中 * @param correctCount * @param wrongCount * @param corrects * @param wrongs * @throws IOException */ private void inputFinally(int correctCount, int wrongCount, String[] corrects, String[] wrongs) throws IOException { String left = "("; String right = ")"; String line = "\n"; String comma = ","; String correctName = "Correct:"; String wrongName = "Wrong:"; //获取字节流 BufferedWriter writer = (BufferedWriter) IOUtils.getIO( StringConstant.FINALLY_PATH + StringConstant.FINALLY_PREFIXX + suffix, IOConstant.WRITER.name()); //输入正确的题号 writer.write(correctName + correctCount + left); writer.flush(); for (int x = 0; x < correctCount; x++) { if (x != correctCount - 1) writer.write(corrects[x] + comma); else writer.write(corrects[x]); writer.flush(); } //输入错误的题号 writer.write(right + line + wrongName + wrongCount + left); writer.flush(); for (int x = 0; x < wrongCount; x++) { if (x != wrongCount - 1) writer.write(wrongs[x] + comma); else writer.write(wrongs[x]); writer.flush(); } writer.write(right + line); writer.flush(); //流的关闭 IOUtils.closeIO(writer); } }
测试截图

总结:
这次项目花费了很多的时间,刚开始看到项目是有思路的,只是不知道从哪里动手写
经过两个人一段时间的协商,做好分工才进动手写代码
对于部分难题,的确卡了不少时间,但综合两个的想法,讨论过后还是能够解决,虽然并不完美,但也是做到了
这次的结对,让我们明白了事前讨论、协商、分工的合理性和必要性,更让我们学会了如何去解决一些之前从未碰过的难题


浙公网安备 33010602011771号