小学四则运算练习软件报告
本篇博客仍在更新修改中 ing
前言:因为这一周各方面的事情接踵而来,所以对于这次作业,除了问题本身的算法,时间对我来说也是一个很大的难题。对于问题的难度,我可以通过和同学的交流,通过博客,去学习,去积累,所以我用了我所有的闲散时间来完成这个作业,但是时间还是太紧迫了。每天早上醒来、晚上睡觉前,只要是可以利用的时间我都会想算法,想完成这个作业的思路,然后晚上忙完工作之后回寝第一件事也是打开电脑开始敲代码,再讨论、再学习。但是因为时间对于我来说真的太需要了,所以对于实现这个项目,我基本完成了最基本的功能,对于附加功能,还在不断敲代码中,因为马上要交作业了,所以我选择先把代码提交了,然后继续调试,再来修改博客。在这个项目没有满足所有的条件前,我是不会放弃的!
https://git.coding.net/Faith_suzy/Sizeyunsuan_.git
项目实施过程
需求分析:
基本功能:
- 程序可接收一个输入参数n,然后随机产生n道加减乘除(分别使用符号+-*÷来表示)练习题,每个数字在 0 和 100 之间,运算符在3个到5个之间。
- 所出练习题至少要包含2种运算符,且运算过程中不得出现负数与非整数。
- 练习题生成好后,将你的学号与生成的n道练习题及其对应的正确答案输出到文件“result.txt”中,不要输出额外信息,文件目录与程序目录一致。
- 当程序接收的参数为4时,右图为一个输出文件示例。
附加功能:
- 出题与求解正确答案过程中,支持有括号的运算式。其中需要注意的是,算式中存在的括号必须大于2个,且不得超过运算符的个数。
- 支持真分数加减的出题与求解,在此过程中,保证运算时分数自动化简,且计算过程与结果都须为真分数。
功能设计:
针对基本功能:
- 根据基本功能的需求可知,实现一个基本的四则混合运算的出题和计算
针对扩展功能:
- 对于括号的形成是一大难点,因为优先级的关系,所以括号的形成是否有意义是我们需要考虑的;其次,在运算中,因为有了括号,所以对优先级会有一个判断,这也是需要解决的问题
- 对于真分数的运算,主要解决最简分式的问题,需要时刻将分数化为最简(即先求其gcd,再进行化简)
设计实现:
设计包括你会有哪些类,这些类分别负责什么功能,他们之间的关系怎样?你会设计哪些重要的函数,关键的函数是否需要画出流程图?函数之间的逻辑关系如何?
首先解决最基本四则混合运算的问题。再仔细研究了最早提交的代码后,我发现同学们设计生成四则运算的思路都不一样,于我来说,每一种算法都很巧妙。我认为这是我值得去学习的,所以再研究了大家已上传的代码,也经常找璐瑶针对生成的算法进行讨论之后,我现阶段选取的算法结合了璐瑶和宇欣两个人的思路。我最开始根据宇欣提供的思路,根据自己进一步的算法,进行了编程,但是到后来发现在中缀表达式转后缀表达式并计算的时候,会出现莫名其妙的debug,然后让我很焦躁,但是也是在耐心的不断调bug,不过调了两天之后,我还是有地方没有弄清,对于式子的生成总是有问题,所以我重头再来,还是选择数组的方法,因为我一般不会选择用数组的方法进行计算,所以在这个过程中,我不断在学习,在编程的时候通过输出语句,不断地在程序运行之后让其显示每一步的运行结果,以便我再调整。
设计的类:
就一个Main类,和一个Lib类,Lib类里面定义了多个方法。
Exam方法:通过两个数组生成运算式
InfixToSuffixExpression方法:中缀表达式转后缀表达式并计算(包含优先级等方法)
gcd:求最大公因数
random:便于求满足各个范围的随机数
printFile:输出文件
算法详解:
在生成题目时,我选择的是数组生成。先定义两个数组,分别存放随机数与符号,这样在两个数组数据生成完之后,进行整合,就将其整形成了一个完整的含有3~5个运算符的式子。
在进行对题目的运算时,我选择运用的还是中缀表达式转后缀表达式并进行运算,这个算法我大概看了十几个博主写的关于中缀转后缀的博客,其中有代码,也有经验。我觉得这个过程是我在这次作业中学到的最大的知识。之前学数据结构的时候对栈的应用,只是知道可以进行运算式的求解,但是从来没有真正写过,所以这一次,再借鉴了其他人的经验和代码之后,我也尝试了自己写,先把这一部分的算法单独创建一个类,先保证它的正常运行,这样再之后的融合中,我就会先排除针对这个算法的debug。
对于括号的添加及优先级的运算,我自己有一个大致的思路,但是对于具体的程序还没有进行编程,因为时间实在是对我来说有点紧,我会在之后继续完善这一部分,我希望通过我的不断调试可以实现此附加功能。
对于真分数的运算,我认为比添加括号运算的思路更清晰一点,现阶段已完善真分数的生成,为了满足需求,在生成和计算的过程中需要一直保持最简,所以每一步都需要用到求gcd。之后的运算我也会在提交了博客之后,进行完善。也希望老师能够督促我,同时我自己也会在这个运算软件没有完全满足所有要求之前,继续研究,继续思考,继续调节。
算法详解:
- 建立两个数组,一个存放随机数,一个存放运算符,然后再将两个数组整合成一个完整的运算式。其中,对于减法,保证前一个数大于后一个数,使其不出现负数;对于除法,使前一个数为后一个数的倍数,以满足整除的需求。
int N = rand(1,4); //随机产生3~5个随机运算符 int count = 0; for(int j=1;j<=N;j++){ count++; int opselect=rand(0,3); if(opselect == 0){ op[j] = "+"; num[j+1] = rand(2,100); } else if(opselect == 1){ //保证两数相减大于零 op[j] = "-"; num[j+1] = rand(2,100); if(num[j]<num[j+1]){ int Temp = num[j+1]; num[j] = num[j+1]; num[j+1] = Temp; } } else if(opselect == 2){ if(op[j-1] == "×" || op[j-1] == "÷"){ op[j] = "+"; num[j+1] = rand(2,10); } else{ op[j] = "×"; num[j+1] = rand(2,10); } } else if(opselect == 3){ //保证整除 if(op[j-1] == "×" || op[j-1] == "÷"){ op[j] = "+"; num[j+1] = rand(2,10); } else{ op[j] = "÷"; num[j+1] = rand(2,10); num[j] = num[j+1]*rand(2,10); } } } String question=""+num[0]; //将生成的运算符整合成一个完整的运算式 op[1]="+"; for(int k=1;k<count;k++){ question+=op[k]+num[k+1]; }
- 为了完成运算,先后查找了关于中缀表达式转后缀表达式的算法,也看了很多博主的博客。虽然我现阶段还没有完成括号的添加和计算,但是在转为表达式和运算的时候,我还是先给定了对括号优先级的判断。
利用堆栈完成中缀表达式转后缀表达式:
- 遇到操作数:直接输出(添加到后缀表达式中)
- 栈为空时,遇到运算符,直接入栈
- 遇到左括号:将其入栈
- 遇到右括号:执行出栈操作,并将出栈的元素输出,直到弹出栈的是左括号,左括号不输出。
- 遇到其他运算符:加减乘除:弹出所有优先级大于或者等于该运算符的栈顶元素,然后将该运算符入栈
- 最终将栈中的元素依次出栈,输出。
public static int InfixToSuffixExpression(String Str){ //中缀表达式转后缀表达式并计算 Stack<Integer> numStack = new Stack<Integer>(); Stack<Character> opStack = new Stack<Character>(); Stack<Object> suffixExpr = new Stack<Object>(); int len = Str.length(); char c,temp; int number; for(int i=0;i<len;i++) { c = Str.charAt(i); if(Character.isDigit(c)) { int endDigitPos = getEndPosOfDigit(Str,i); number = Integer.parseInt(Str.substring(i,endDigitPos)); i = endDigitPos - 1; numStack.push(number); if((int)number == number) { suffixExpr.push(number); } else { suffixExpr.push(number); } } else if(isOperator(c)) //操作符栈非空,且栈顶不是'(',且当前操作符优先级低于栈顶操作符 { while(!opStack.isEmpty() && opStack.peek()!='(' && priorityCompare(c,opStack.peek())<=0) { suffixExpr.push(opStack.peek()); numStack.push(calc(numStack,opStack.pop())); } opStack.push(c); } else if(c == '(') { opStack.push(c); } else if(c == ')') { while((temp = opStack.pop())!='(') { numStack.push(calc(numStack,temp)); suffixExpr.push(temp); } } else if(c == ' ') { } else { throw new IllegalArgumentException("Wrong character '" + c + "'"); } } while(!opStack.isEmpty()) { temp = opStack.pop(); suffixExpr.push(temp); numStack.push(calc(numStack,temp)); } return numStack.pop(); // printStack(suffixExpr); // System.out.println( Str + " = " + numStack.pop()); }
测试运行:
PSP
PSP2.1 | 任务内容 | 计划共完成需要的时间(min) | 实际完成需要的时间(min) |
Planning | 计划 | 10 | 5 |
Estimate | 估计这个任务需要多少时间,并规划大致工作步骤 | 10 | 8 |
Development | 开发 | 30*60min | 15*60min |
Analysis | 需求分析 (包括学习新技术) | 10 | 15 |
Design Spec | 生成设计文档 | 0 | 0 |
Design Review | 设计复审 (和同事审核设计文档) | 0 | 0 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 5 | 5 |
Design | 具体设计 | 30 | 60 |
Coding | 具体编码 | 6*60 | 8*60min |
Code Review | 代码复审 |
| 持续中 |
Test | 测试(自我测试,修改代码,提交修改) |
| 240 |
Reporting | 报告 | 180 | 120 |
Test Report | 测试报告 |
|
|
Size Measurement | 计算工作量 |
|
|
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 |
| 持续中 |
更深一步的总结:
完成这个作业耗用了我很多时间,让我深知了自己的不足,但是与此同时,我也在同学的博客中学习到了很多知识,每一次打翻以前的思路重写代码的时候,都是一种历练、一种学习。虽然这个过程是非常难熬的,但是真正自己完成下来了还是有一定的成就感的,正是因为知道自己还有非常多的不足,所以每一次的作业完成后我也会不断修改,比其他人用更多的时间去完善、不怕这个过程的艰辛,我相信自己如果真正能够坚持,最好的效果会非常好!希望我所有的付出都值得,不求所有都能有回报,但求自己能有长进、能够更加向前。
To be continued: 这篇博客还在继续完善中ing
在完善这篇博客期间,我看到了很多同学用的是js中eval函数,我觉得这个比以前的复杂的算法简单的多,所以在学习、借鉴了别人的代码之后,自己也按照同学博客里面介绍的写了代码。
static ScriptEngine jse = new ScriptEngineManager().getEngineByName("JavaScript"); //运用js
private static ArrayList<String> calculate(ArrayList<String> arrayList) { ArrayList<String> question = new ArrayList<String>(); for (String nquestion : arrayList) { try { nquestion = nquestion + "=" + jse.eval(nquestion); System.out.println(nquestion); question.add(nquestion); } catch (ScriptException SE) { // TODO Auto-generated catch block SE.printStackTrace(); } } try { File test = new File("result.txt"); FileWriter fw = new FileWriter(test); PrintWriter pw = new PrintWriter(fw); pw.println("2016012062"); for (String answer : question) { pw.println(answer); } fw.close(); } catch (IOException IOE) { // TODO Auto-generated catch block IOE.printStackTrace(); } return question; }
所以我也改了coding.net的代码,然后也会持续更改代码,用不同的方式解决。现在还不够好。