我的项目源码地址:https://git.coding.net/Dearpangpang/hahaha.git
项目需求:
软件基本功能要求如下:
- 程序可接收一个输入参数n,然后随机产生n道加减乘除(分别使用符号+-*÷来表示)练习题,每个数字在0和100之间,运算符在3个到5个之间。 为了让小学生得到充分锻炼,每个练习题至少要包含2种运算符。同时,由于小徐生没有分数和负数的概念,你所出的练习题在运算过程中不得出现负数与非整数,比如不能出(3÷5+2=2.6,2-5+10=7)等算式。
- 练习题生成好后,将你的学号与生成的n道练习题及对应的正确答案输出到文件“result.txt”中,不要输出额外信息,文件目录与程序目录一致。
- 当程序接收的参数为4时,以下为 我的输出文件
功能设计:
程序能接收一个输入参数n,然后根据题目要求随机产生n道四则运算以及其对应的正确答案并输入到文件“result.txt”中。
代码分析:
首先,我先创建了CreatProblems()方法,用来出题,如下图:
public static String CreatProblems() { Random random = new Random(); String s =new String(); int CaozuoCount = random.nextInt(3)+3;//保证操作符在3~5个 int CaozuoIndex[]=index(CaozuoCount,4); int Number[] =new int[CaozuoCount+1]; String[] FuHao = {"+","-","*","÷"}; for(int i=0;i<CaozuoCount+1;i++) { Number[i]=random.nextInt(100); } switch(CaozuoCount) { case 3: s= Number[0]+FuHao[CaozuoIndex[0]]+Number[1]+FuHao[CaozuoIndex[1]]+Number[2]+FuHao[CaozuoIndex[2]]+Number[3]; break; case 4: s= Number[0]+FuHao[CaozuoIndex[0]]+Number[1]+FuHao[CaozuoIndex[1]]+Number[2]+FuHao[CaozuoIndex[2]]+Number[3]+FuHao[CaozuoIndex[3]]+Number[4]; break; case 5: s= Number[0]+FuHao[CaozuoIndex[0]]+Number[1]+FuHao[CaozuoIndex[1]]+Number[2]+FuHao[CaozuoIndex[2]]+Number[3]+FuHao[CaozuoIndex[3]]+Number[4]+FuHao[CaozuoIndex[4]]+Number[5]; break; } return s; }
可能是我的方法有些问题,我并不能很好地解决每一部分的整数都可以除尽,我能想到的方法就是在我的符号前面加判断,如果是除号,那么除号的前一个数字和后一个数字需要能够整除,如果不能做到,那么就重新调用该方法重新得到一个问题,但是一些小细节始终处理的不好,我会继续修正,请教同学,看看其他同学是怎样实现该部分功能的。
除此之外,我还创建了一个index()方法,用于避免一道题中所有的符号一样,也就是至少有两种运算符。
public static int[] index(int n,int m){ //产生操作符的下标数组 Random random = new Random(); int b=0; int[] a = new int[n]; for(int j=0;j<n;j++){ a[j] = random.nextInt(m); } for(int j=1;j<n;j++){ if(a[0]==a[j]) b++; } if(b==n-1) return index(n,m); //保证一个式子里至少有2个不同的操作符,若所有操作符下标都一样,则重新产生操作符下标 else { return a; } }
然后我才用的是栈的方法实现字符串求值,该方法原理如下:
创建两个栈,一个栈用来存放操作符,叫做opter,另一个栈存放操作数,叫做opval;让字符串一个字符一个字符读取,逐一进栈,先将“#”字符存入opter栈中,再使得题目字符串结尾处也加上字符“#”。
赋值字符c为当前字符串将要读取的字符,top为opter栈顶元素。
若top的优先级小于c,即top<c,则弹出opter的栈顶元素,并读入下一字符赋值给c
若top的优先级等于c,即top=c,则弹出opter的栈顶元素,并读入下一字符c,该操作便于进行括号操作,便于后期完善
若top的优先级高于c,即top>c,则表明可以计算,此时弹出opval的两栈顶元素,并且弹出opter栈顶的运算符,且放入opval栈中,直至opter的栈顶元素和当前读入的字符均为“#”。
如下,是我的代码实现:
首先创建栈还有getIndex()方法,使得对应的符号有对应的索引值
如图所示:
static Stack<Character> opter = new Stack<Character>(); //运算符栈 static Stack<Integer> opval = new Stack<Integer>();//操作数栈
public static int getIndex(char theta)//获取theta所对应的索引 { int index = 0; switch(theta) { case '+': index=0; break; case '-': index=1; break; case '*': index=2; break; case '÷': index=3; break; case '(': index=4; break; case ')': index=5; break; case '#': index=6; break; default:break; } return index; }
创建getPriority()方法,获取到两符号之间的优先级,如果top的优先级高于c,则返回‘>’,如果top优先级小于c,则返回‘<’。
public static char getPriority(char theta1,char theta2)//获取theta1和theta2之间的优先级 { char priority[][]= { {'>','>','<','<','<','>','>'}, {'>','>','<','<','<','>','>'}, {'>','>','>','>','<','>','>'}, {'>','>','>','>','<','>','>'}, {'<','<','<','<','<','=','0'}, {'>','>','>','>','0','>','>'}, {'<','<','<','<','<','0','='}, }; int index1 =getIndex(theta1); int index2 =getIndex(theta2); return priority[index1][index2]; }
如图所示:
然后创建基本的计算功能,这一段代码比较简单
如图所示:
public static int calculate(int b,char theta,int a)//计算 { int result=0; switch(theta) { case '+': result= a+b; break; case '-': result=b-a; break; case '*': result=b*a; break; case '÷': if(b%a==0) { result=b/a; } else theta='+'; break; default:break; } return result; }
创建getAnsewer()方法,就是算法的实现部分,表达式求值
代码如下:
public static int getAnsewer(String problem)//表达式求值 { opter.push('#');//首先将#号入栈opter int counter =0;//添加变量counter便是有多少个数字相继入栈,实现多位数的四则运算 char c=0; problem+="#"; while(c!='#'||opter.peek()!='#')//终止条件 { for(int i=0;i<problem.length();i++){ c=problem.charAt(i); //System.out.println("e333"); if(Character.isDigit(c))//如果c在0~9之间 { if(counter==1)//counter==1表示上一字符也是数字,所以要合并 { int t =opval.peek(); opval.pop(); opval.push(t*10+(c-'0')); counter=1; } else { opval.push(c-'0'); counter++; } } else { counter=0;//counter至零 switch(getPriority(opter.peek(),c)){//获取运算符栈opter栈顶元素与c之间的优先级 case '<'://则将c入栈opter opter.push(c); break; case '='://将opter栈顶元素弹出,用于括号的处理 opter.pop(); break; case '>'://可以进行计算 char theta =opter.peek(); opter.pop(); int a =opval.peek(); opval.pop(); int b =opval.peek(); opval.pop(); opval.push(calculate(b,theta,a)); } } } } return opval.peek(); }
然后,基于不可以有负数这一要求,我创建了FinalltResult(),如果答案为负数的话,则返回CreateProblems()方法重新生成题目,重新求解。
最后创建文件代码,如下图所示:
public static void creatFile(int n) { try{ File file=new File("../result.txt"); if(file.exists()) { file.delete(); } if(file.createNewFile()) { FileOutputStream txtfile = new FileOutputStream(file); PrintStream p =new PrintStream(txtfile); p.println("2016011986"); Random random =new Random(); for(int i=0;i<n;i++) { p.println(CreatProblems()+"="+FinalltResult(CreatProblems())); } txtfile.close(); p.close(); System.out.println("文件创建成功!"); } }catch(IOException ioe) { ioe.printStackTrace(); } }
测试运行:
PSP:
PSP2.1 |
任务内容 |
计划共完成需要的时间(h) |
实际完成需要的时间(h) |
Planning |
计划 |
1 |
1 |
· Estimate |
· 估计这个任务需要多少时间,并规划大致工作步骤 |
45 |
60 |
Development |
开发 |
45 |
60 |
· Analysis |
· 需求分析 (包括学习新技术) |
2 |
2 |
· Design Spec |
· 生成设计文档 |
0 |
0 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
0 |
0 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
8 |
10 |
· Design |
· 具体设计 |
1 |
2 |
· Coding |
· 具体编码 |
30 |
38 |
· Code Review |
· 代码复审 |
1 |
2 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
1 |
3 |
Reporting |
报告 |
0 |
0 |
· Test Report |
· 测试报告 |
0 |
0 |
· Size Measurement |
· 计算工作量 |
0 |
0 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
3 |
3 |
我的感想:
刚看到这个作业的时候,我还以为就是上次小程序的完整版,而实际上,上次的只是很基本的操作,这一次的作业就有很多逻辑上的问题,比如我遇到的很多问题,像是如何生成很多道符号种类不同的算术题,如何将一串字符串进行数值运算,如何判断能否整除是否为负,还有怎样加括号还有怎样实现分数,分数运算还有整式运算怎样随机产生,如何将一串字符串进行数值运算这个是整道题中最难也是最有意思的部分,正好运用到我们上个学期学习的数据结构,我当时上数据结构的时候还觉得那些算法太复杂感觉变成代码根本做不到,但是通过百度各种博客发现,其实这种看上去逻辑很强的算法,其实都是一步一步慢慢得到的,通过不同的调用方法一点一点实现,这期间,我也出了很多错,也请教了同学很多,也学到了一些简单的找错的方法,一点一点执行代码一步一步找错,总之学到了很多,还有之前一直不是很懂的文件输出,这一次也锻炼到了,虽然我打的很慢慢,也遇到了很多问题,但是确实学到了很多很多,也体会到了实现代码兴奋激动的感觉,虽然熬了好几天的夜,但是真的完成这一任务时,真的是很开心的!