2016012044+小学四则运算练习软件项目报告
代码仓库地址:https://git.coding.net/mafx8859/homework2.0.git
1.需求分析:
①程序需接受一个数值n,自动生成n道四则运算式,其中生成的运算式的运算符需要在3到5个之间,同时程序能对生成的运算式自动求解,并且输出到.txt文档中。
②生成的运算式中不能出现小数和负数,运算结果也不能出现小数和负数。
③程序需要对不合法输入数据n进行友好处理
④生成的运算式也可支持带有括号的运算式和分数运算式。
2.功能设计:
①程序接受一个参数n,并对n的合法性做判断,出现不合法的输入时可提醒用户重新输入合法数据。
②程序根据用户输入的数据n可生成n个运算式,其中运算形式包括,不带括号的运算式、带括号的运算式、分数运算。
③程序对生成的运算式可以自动求解。
④程序支持两种出题模式,第一种是直接生成运算式并将运算式和运算结果进行打印,第二种是用户可提交自己的运算答案,并且由程序对提交的答案进行判断。
3.设计实现:该程序主要包括两个类:
(1)Main类主要是该程序运行的入口,该类中包含main方法,该方法可接收一个生成运算式数目n,并且对输入数据n的合法性进行判断并作出响应,同时该方法调用DealAtith类中的creatAtith(int n)方法和printOperForm()方法来实现运算式的生成和输出。
(2)DealAtith类主要用于处理运算式,该类中包含除构造器外的5个方法,其中public void creatAtith(int n)方法是用于生成运算式的,其中主要运用的是随机数的方式,并且对运算符的生成也采用随机数的方式,并且运算符的生成是等概率生成的。同时该方法也存在检测生成的运算式是否满足要求的机制,对于不满足要求的运算式进行相应处理。该方法运行流程图如下:
②方法public String[] getBl(String temp1),该方法主要负责将传入的运算式的中缀表达式转化成后缀表达式,并且以数组的形式返回,该方法中将中缀表达式转化成后缀表达式的基本方式是借助数据结构‘栈’,该方法运行流程图如下:
③public void getResult(String Aith)方法主要是用于计算运算式的结果,计算运算式的结果也是借助了栈,该方法中首先需要调用public String[] getBl(String temp1)方法来将输入的运算式转化成后缀表达式,然后再借助栈来计算结果。
④public boolean isOperator(String operator)方法主要用于判断传入的数据是运算数还是运算符。
⑤public void printOperForm()方法主要用于将生成的运算式和结果写入result.txt文件当中。
4.算法详解:
(1)main方法中判断输入数据是否合法是利用异常处理来实现的,当用户命令行输入一个非法数据时,系统会抛出一个Exception,将该Exception进行捕获,在catch块中进行错误提示和程序中断处理,核心代码如下:
try{ n=Integer.parseInt(args[0]); }catch(Exception e){ System.out.println("输入错误,请输入一个整数!!!"); return; } DealAtith.creatAtith(n); try { DealAtith.printOperForm(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); }
(2)方法public String[] getBl(String temp1)主要负责将中缀表达式转化成后缀表达式,其算法思想是:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分;若是符号,则判断其与栈顶符号的优先级,是右括号或优先级不高于栈顶符号则栈顶元素一次出栈并输出,并将当前符号进栈,一直到最终输出后缀表达式为止。该环节核心代码如下:
for(int i=0;i<temp.length;i++){ if(!isOperator(temp[i])){//是数字的话,则直接输出 bl[x]=temp[i];x++; //System.out.println(temp[i]); }else if(s1.isEmpty()){//是运算符并且栈为空时则直接入栈 s1.push(temp[i]); }else if(temp[i].equals(")")){//是‘)’时,则开始出栈知道‘(’出栈为止。 while(!s1.isEmpty()){ if(!s1.peek().equals("(")){ bl[x]=s1.pop();x++; }else{ s1.pop(); break; } } }else if(map.get(temp[i])<=map.get(s1.peek())&&!temp[i].equals("(")){//如果该运算符的优先级不高于栈顶运算符的优先级,则出栈并将该运算符入栈 while(!s1.isEmpty()){ bl[x]=s1.pop();x++; } s1.push(temp[i]); }else{ s1.push(temp[i]); } } while(!s1.isEmpty()){//最后全部出栈 bl[x]=s1.pop();x++; } }
其中在该模块中对不同运算符优先级的判断是借助了容器map,由于map是可以存储键值对的,所以key值设置为运算符,对不同key值赋予不同大小的数值用来表示优先级的高低,对优先级的构建是放在DealAtith的构造器中的,代码如下:
public DealAtith(){
map.put("(", 0);
map.put("+", 1);
map.put("-", 1);
map.put("*", 2);
map.put("÷", 2);
}
(3)public void getResult(String Aith)方法主要是用于计算运算式的结果,其主要算法思想是:从左到右遍历后缀表达式的每个数字和字符,遇到是数字就进栈,遇到是符号,就将处于栈顶两个数字出栈进行运算,运算结果进栈,一直到最终获得结果,其核心代码如下:
for(int i=0;(kh?i<bl.length&&bl[i]!=null:i<bl.length);i++){
if(!isOperator(bl[i])){//是数字的话就进栈
s2.push(Integer.parseInt(bl[i]));
}else{
switch(bl[i]){
case "+" : s2.push(s2.pop()+s2.pop()); break;
case "-" : int x1=s2.pop();int x2=s2.pop();s2.push(x2-x1); break;
case "*" : s2.push(s2.pop()*s2.pop()); break;
case "÷" : int x3=s2.pop();int x4=s2.pop();s2.push(x4/x3); break;
}
}
}
(4) public void creatAtith(int n)方法主要运用随机数的方式生成运算式和运算符,其算法大致思路为:随机生成运算数和运算符并分别存储到运算数数组和运算符数组中,其中运算数的生成由运算符决定,具体规则是:当检测到有除号生成时我们需要控制除号前边的运算数是除号后边运算数的倍数,其中放大多少倍数也是由随机数来决定的,同时为了防止由于减法的出现而使得数据再次修改则需控制减号后禁止除号出现。当检测到有‘-’号生成时,则减号后边的运算数不能大于减号前边的运算数,同时为了降低运算式在运算中结果是负数的概率以及防止因为除法的出现而导致数据再次被修改,则控制减号后的运算符不能出现乘号和除号。其核心代码为:
String operator[]={" + "," - "," ÷ "," * "}; for(int i=1;i<=n;i++){//产生n个运算式 String operForm=""; int digitNum=random.nextInt(maxDigitNum)%(maxDigitNum-minDigitNum+1) + minDigitNum;//运算数的数量4-5之间 digitArray=new int[digitNum];//存储运算数 int operatorNum1=digitNum-1;//在没有括号的情况下运算符的数量 int operatorNum2=digitNum+1;//在有括号的情况下运算符的数量 operatorArray=new String[operatorNum1];//存储运算符 digitArray[0]=random.nextInt(101)+1;//产生第一个运算数 int numIndex=0;//运算数下标 boolean flag=false; boolean flag1=false;//用于标注‘÷’号后不能有‘-’和‘÷’ boolean flag2=false; for(int j=0;j<operatorNum1;j++){//产生其他运算数和相同数量的运算符,并且由符号决定运算数的生成 numIndex++; int operIndex=0; if(flag){ operIndex=random.nextInt(2);//当前边出现‘-’号后,后边就不能出现乘法和除法 }else if(flag1){//当前边出现‘÷’时,后边不能出现‘-’或者‘÷’ while((operIndex=random.nextInt(4))==1||(operIndex=random.nextInt(4))==2){ ; } }else{ operIndex=random.nextInt(4); } operatorArray[j]=operator[operIndex]; if(operIndex==1||operIndex==3){ flag=true;//用于标注‘-’或者‘*’号后边不能出现‘*’号 } if(operIndex==1){//如果‘-’号出现 //flag2=true; if(numIndex==1){ digitArray[numIndex]=random.nextInt(digitArray[numIndex-1])+1;//如果出现‘-’号则后边数据应该小于前一个数据 } else{ String quesTemp=""+digitArray[0]; for(int k=0;k<j;k++){ quesTemp=quesTemp+operatorArray[k]+digitArray[k+1]; } int result=getResult(quesTemp,false); digitArray[numIndex]=random.nextInt(result+1)%100; } }else if(operIndex==2){//如果是‘÷’ flag1=true; digitArray[numIndex]=random.nextInt(20)+1; int mul=random.nextInt(6)+1; digitArray[numIndex-1]=digitArray[numIndex]*mul;//将前一个数改成后一个数的倍数 }else if(operIndex==3){ digitArray[numIndex]=random.nextInt(50)+1; digitArray[numIndex-1]=random.nextInt(100/digitArray[numIndex])+1; }else{ digitArray[numIndex]=random.nextInt(20)+1; } }
5.运行测试:
6.总结:在该小学四则运算小程序中主要分为两个类,其中Main类负责开启程序运行的入口,DealAtith主要用于处理运算式,整个程序由这两个类块构成,实现了功能的模块化。其中在DealAAtith类中又分离出了专门生成运算式的public void creatAtith(int n)方法、生成后缀表达式的public String[] getBl(String temp1)方法、计算运算式结果的public void getResult(String Aith)方法、判断运算符的public boolean isOperator(String operator)方法以及专门负责打印的public void printOperForm()方法,这样设计使得各功能再次模块化,使得程序更加的便于维护和扩展,下表示这个项目的时间统计表:
PSP | 任务内容 | 计划共完成需要的时间(min) | 实际完成需要的时间(min) |
Planning | 计划 | 10 | 8 |
Estimate | 估计这个任务需要多少时间,并规划大致工作步骤 | 48(h) | 42(h) |
Development | 开发 | 12(h) | 13.5(h) |
Analysis | 需求分析 (包括学习新技术) | 60 | 52 |
Design Spec | 生成设计文档 | 40 | 45 |
Design Review | 设计复审 (和同事审核设计文档) | 40 | 33 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 40 |
Design | 具体设计 | 120 | 100 |
Coding | 具体编码 | 3(h) | 3(h) |
Code Review | 代码复审 | 2(h) | 2(h) |
Test | 测试(自我测试,修改代码,提交修改) | 1(h) | 1(h) |
Reporting | 报告 | 3(h) | 2.5(h) |
Test Report | 测试报告 | 1h | 1h |
Size Measurement | 计算工作量 | 0.5h | 25min |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 1h | 1h |