本次作业从布置的第一天我就着手做了,不过基础较差,并没有想过怎样实现相关功能,反正都是一步一步做的。我不知道我花了多少时间在这次作业上,但是在空闲的时候我就在琢磨。(今天大概从下午3点开始没课,我坐到在电脑前到8点半,先是修改代码,然后写完博客的大致内容,没去跑步,饭也忘了吃,食堂也关了门,第一次感受到了完成作业的喜悦,也感受到了程序员的艰苦╮(╯▽╰)╭真的是到了废寝忘食的地步)然后回到宿舍接着修改博客修改到宿舍断电。
博客版式也是参考了同学的,感觉他们的排版与思路都很清晰,我也觉得可以借鉴借鉴o(* ̄︶ ̄*)o
项目简介:
Coding.net源码仓库地址:
https://git.coding.net/francesca/Arithmetics.git
SSH:git@git.coding.net:francesca/Arithmetics.git
测试步骤:
1.进入src文件夹
2.在命令行输入javac -encoding utf-8 *.java
3.回车再输入java Main 1000
4.回车,将会在src同级目录下产生result.txt
一、需求分析
1.程序可从命令行接收一个输入参数n,然后随机产生n道加减乘除习题。
2.每个数字在 0 和 100 之间,运算符3到5个。
3.每道习题要包含3-5种运算符。
4.所出的练习题在运算过程中不得出现负数与非整数。
5.将学号与生成的n道练习题及其对应的正确答案输出到文件“result.txt”中。
6.支持有括号的运算式,包括出题与求解正确答案。算式中存在的括号必须大于2个,且不得超过运算符的个数。
二、功能设计
1.基本功能:
能够根据用户输入的参数n随机产生n道符合要求的练习题,自动算出答案,并将式子与答案以文档的形式呈现。
2.扩展功能:
支持有括号的运算式,包括出题与求解正确答案。
支持真分数运算,包括出题与解题正确答案。
(注意,算式中存在的括号必须大于2个,且不得超过运算符的个数。)
三、设计实现
BinaryTree:生成二叉树,获取最终表达式,计算并验证表达式
File:负责产生result.txt文件,并将学号和产生的习题写入文件
Main:主类,负责接收命令行的参数并启动程序
Ran:获取随机运算符及随机数
MakeF:生成真分数并计算结果
TreeNode:生成节点,并获取每个节点的运算结果,并检验除法和减法每个运算式添加括号,然后根据去括号法则,去掉多余的子式的括号
四、代码展示
1.不出现负数与非整数
public String getResult(){ if(hasChild()){ switch(str){ case "+": return String.valueOf(Integer.parseInt(getLchild().getResult()) + Integer.parseInt(getRchild().getResult())); case "-"://change if(Integer.parseInt(getLchild().getResult()) - Integer.parseInt(getRchild().getResult()) < 0){ while(str.equals("-")){ str = String.valueOf(Ran.getOperator()); } return this.getResult(); } else return String.valueOf(Integer.parseInt(getLchild().getResult()) - Integer.parseInt(getRchild().getResult())); case "*": return String.valueOf(Integer.parseInt(getLchild().getResult()) * Integer.parseInt(getRchild().getResult())); case "÷": if(getRchild().getResult().equals("0")){ while(str.equals("÷")){ str = String.valueOf(Ran.getOperator()); } return this.getResult(); } /** * 整除运算 */ else if(Integer.parseInt(getLchild().getResult()) % Integer.parseInt(getRchild().getResult()) != 0){ while(str.equals("÷")){ str = String.valueOf(Ran.getOperator()); } return this.getResult(); } else return String.valueOf(Integer.parseInt(getLchild().getResult()) / Integer.parseInt(getRchild().getResult())); } } return str; }
2.带括号的表达式
public String toString(){ String Lstr = "", Rstr = "", Str = ""; if(hasChild()){ //右子树如果有孩子,说明右子树是一个表达式,而不是数字节点。 if(getRchild().hasChild()){ //判断左邻括号的运算符是否为'/' if(str.equals("÷")){ //获取右子树的表达式,保留括号 Rstr = getRchild().toString(); } //判断左邻括号的运算符是否为'*'或'-' else if(str.equals("*") || str.equals("-")){ //判断op是否为'+'或'-' if(getRchild().str.equals("+") || getRchild().str.equals("-")){ Rstr = getRchild().toString(); } else{ //获取右子树的表达式,并且去括号 Rstr = getRchild().toString().substring(1, getRchild().toString().length()-1); } } else{ //右子树除此之外都是可以去括号的。 Rstr = getRchild().toString().substring(1, getRchild().toString().length()-1); } } else{ Rstr = getRchild().str; } //左子树的情况同右子树类似 if(getLchild().hasChild()){ if(str.equals("*") || str.equals("÷")){ if(getLchild().str.equals("+") || getLchild().str.equals("-")){ Lstr = getLchild().toString(); } else{ Lstr = getLchild().toString().substring(1, getLchild().toString().length()-1); } } else{ Lstr = getLchild().toString().substring(1, getLchild().toString().length()-1); } } else{ Lstr = getLchild().str; } //获取当前的运算式,并加上括号 Str = "(" + Lstr + str + Rstr + ")"; } else{ //若没有孩子,说明是数字节点,直接返回数字 Str = str; } return Str; }
3.真分数计算
public String MakeFraction() { String str = null; String[] op2 = { "+", "-" };// 存储连接分数的操作符的数组 int denominator = 1; int numerator = 1; int denominator1 = (int) (Math.random() * 19) + 1;// 生成分母 int numerator1 = (int) (Math.random() * 20);// 生成分子 if (numerator1 != 0) { if (numerator1 > denominator1) {// 如果分子大于分母,也就是不是真分数时,交换分子分母,使其变成真分数 int temp = numerator1; numerator1 = denominator1; denominator1 = temp; } if (numerator1 == denominator1) {// 如果分子刚好等于分母,重新生成分子 numerator1 = (int) (Math.random() * 20); } int gcd1 = gcd(numerator1, denominator1);// 求分子分母最大公因数,保证分数形式最简 denominator1 = denominator1 / gcd1;// 化简 numerator1 = numerator1 / gcd1;// 化简 } String question1 = numerator1 + "/" + denominator1;// 存储题目 int count = (int) (Math.random() * 2) + 3;// 随机产生运算符的数目 for (int u = 0; u < count; u++) {// 小于运算符数量时不断产生分数,不断计算 int denominator2 = (int) (Math.random() * 19) + 1;// 生成分母 int numerator2 = (int) (Math.random() * 20);// 生成分子 if (numerator2 != 0) { if (numerator2 > denominator2) {// 避免不是真分数 int temp = numerator2; numerator2 = denominator2; denominator2 = temp; } if (numerator2 == denominator2) {// 如果分子等于分母,重新生成分子 numerator2 = (int) (Math.random() * 20); } int gcd2 = gcd(numerator2, denominator2);// 化简分式,使其最简 denominator2 = denominator2 / gcd2; numerator2 = numerator2 / gcd2; } int symbol = (int) (Math.random() * 2);// 随机产生运算符 if (op2[symbol].equals("+")) {// 如果是加号,实现分数加法 if (denominator1 == denominator2) {// 如果两个分母相同,直接将分子相加 numerator = numerator1 + numerator2; } else {// 通分,相加 denominator = denominator1 * denominator2; numerator = numerator1 * denominator2 + numerator2 * denominator1; } if (denominator < numerator) {// 如果运算结果不是真分数 u--;// 计数的u减一,也就是重新生成重新计算 } else {// 在给定范围内的话,通分运算结果 int gcd = gcd(numerator, denominator); denominator = denominator / gcd; numerator = numerator / gcd; question1 += op2[symbol] + numerator2 + "/" + denominator2;// 把题目进行完善 denominator1 = denominator;// 储存运算结果到denominator1和numerator1 numerator1 = numerator; } } else {// 如果是减号,实现减法操作 if (denominator1 == denominator2) {// 分母相同直接分子相减 numerator = numerator1 - numerator2; } else {// 其他情况,先通分再相减 denominator = denominator1 * denominator2; numerator = numerator1 * denominator2 - numerator2 * denominator1; } if (numerator < 0) {// 如果导致结果小于0了,就重新生成 u--; } else {// 通分结果化简 int gcd = gcd(numerator, denominator); denominator = denominator / gcd; numerator = numerator / gcd; question1 += op2[symbol] + numerator2 + "/" + denominator2; denominator1 = denominator;// 储存通分结果 numerator1 = numerator; } } } str = question1 + " = " + numerator + "/" + denominator; return str; //System.out.println(question1 + " = " + numerator + "/" + denominator);// 输出题目和答案 }
4、最大公因数
public int gcd(int x, int y) {// 求最大公因数的函数 if (x == 0) { return y; } else { return gcd(y % x, x); } }
五、测试运行
六、PSP
SP2.1 |
任务内容 |
计划共完成需要的时间(m) |
实际完成需要的时间(m) |
Planning |
计划 |
1500 |
2000 |
· Estimate |
· 估计这个任务需要多少时间,并规划大致工作步骤 |
1000 |
1400 |
Development |
开发 |
1902 |
3323 |
· Analysis |
· 需求分析 (包括学习新技术) |
90 |
123 |
· Design Spec |
· 生成设计文档 |
0 |
0 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
0 |
0 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
0 |
0 |
· Design |
· 具体设计 |
300 |
480 |
· Coding |
· 具体编码 |
1200 |
2300 |
· Code Review |
· 代码复审 |
180 |
300 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
132 |
120 |
Reporting |
报告 |
360 |
380 |
· Test Report |
· 测试报告 |
300 |
320 |
· Size Measurement |
· 计算工作量 |
30 |
30 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
30 |
30 |
七、总结
1、最开始的随机数和随机符的产生也是琢磨了一小会儿,不过还好,通过百度加深了对java的理解,也更深一步了解了Random类和Math类下的random方法的区别。
2、是对非整数结果的构思,一开始采用的是比较除数和被除数的大小,然后交换数据,这个更改了好多遍,后来发现可以再次随机产生除了除法以外的运算符,这个花费了好长时间,不过在这个基础下,我接着没几分钟就完成了非负数的完成。
3、是附加功能中的括号查了很多资料,发现要用中缀转后缀表达式,用堆栈,这个过程比较容易懂,但是代码还是挺难明白的,在请教学长得前提下,终于解决了,收获也很大。
4、关于真分数的部分是在参考同学的代码中学习到的,也写了另外的类去测试,完成的比较好。
5、项目大部分功能是在本周三完成的,当时很兴奋。不过后来才去看了题目要求,发现要在命令行测试数据,并写入text文件,看了java教程后发现是File的相关内容,代码完成的比较好,就是命令行是第一次用,从周四晚上开始就在琢磨为什么我电脑的命令行输入会出现乱码,查看了很多博客,没找到一个成功的,到了周五下午,在学长得帮助下才发现之前的环境变量配置错了。
6、接着是传项目,我自己电脑配置好了git和相关的一些公钥,传项目也很熟练,不过换了台式电脑后,周五下午又花了一个小时弄公钥和传项目,成功传项目有很多方式,最后发现自己写的通过git传到GitHub的博客中的方式最简单。
总之,通过这次作业,我深刻意识到对java的掌握还不够,相关算法也是查阅很多博客和书籍才实现的。每晚睡觉都还想着怎么实现非负数和非整数,说真的作业很难,不过收获很大,这两周很充实也很疲惫。总之努力就会有收获,很庆幸自己没有放弃,我也会继续努力的ヾ(◍°∇°◍)ノ゙。
最后非常感谢您的阅读,您辛苦啦!