一、需求分析
实现一个命令行程序,要求:
自动生成小学四则运算题目(加、减、乘、除)
- 支持整数
- 支持多运算符(比如生成包含100个运算符的题目)
- 支持真分数
- 统计正确率
- 能生成随机数
- 产生的算式要有括号
- 要建立堆栈,进行中缀转后缀,以及后续后缀的运算
- 能输入想要产生的题目数
- 能输入用户计算的答案
- 能够比较用户输入的答案是否正确
- 能够统计用户答题的正确率
二、设计思路
- 生成一个有加减乘除支持括号的算式,以字符串的形式输出,每个操作数或操作符中间都用空格隔开。
- 先生成一个不带括号的算式
- 将生成的不带括号的算式随机插入括号
- 然后调用String类中的split方法,将字符串转化为字符串数组。
- 使用中缀表达式转后缀表达式规则将中缀表达式形式的字符串数组以后缀表达式的形式储存在堆栈中。
- 用后缀表达式计算规则进行计算,得出结果
- 得出的结果与用户输入结果进行比较
- 计算出正确率
三、实现过程中的关键代码解释
- 代码1:
import java.util.Random;
int num = randnum.nextInt(100) + 2;
- 功能:随机生成操作数的个数,包含100个运算符,记得后面一定要加2,因为操作数的个数一定大于等于2
。 - 解析:
- 代码2:
str = s.split(" ");
- 功能:将字符串转化为字符串数组,以空格作为分隔符。
- 解析:
- 代码3:
for (int i = 0; i < str2.length; i = i + 2) {
f = false;
if (flag[counter2] == 1) {
counter[counter2]++;
}
choicechar = randnum.nextInt(2);
if (choicechar == 1 && i != str2.length - 1) { //产生左括号
str2[i] = "( " + str2[i];
counter2++;
flag[counter2] = 1;//已经生成左括号了
}
choicechar = randnum.nextInt(2);
if (choicechar == 1 && counter[counter2]!=0&&counter2!=0) {
str2[i] = str2[i] + " )";
counter[counter2]=0;
counter2--;
}
}
for(int i=counter2;i>0;i--){
str2[str2.length - 1] = str2[str2.length - 1] + " )";
}
- 功能:生成随机生成括号,括号位置正确,而且能支持嵌套。
- 解析:
- counter2是用来生成的左括号的个数
- 用count[counter2]来记录生成的左括号后的操作数的个数,用来保证生成的括号不会出现(数字)这种情况。
str2[str2.length - 1] = str2[str2.length - 1] + " )"
当生成的左括号后面没有与之匹配的右括号时就在最后一个操作数后面把所有的右括号补全。
- 代码4:
while (tokenizer.hasMoreTokens()){
token=tokenizer.nextToken();
if (isOperator(token)){
if (!OpStack.empty()){
if(judgeValue(token)>judgeValue(OpStack.peek()) && !token.equals(")") || token.equals("("))
OpStack.push(token);
else if (token.equals(")")){
//如果遇到一个右括号则将栈元素弹出,将弹出的操作符输出直到遇到左括号为止
while (!OpStack.peek().equals("("))
output=output.concat(OpStack.pop()+" ");//弹出左括号上面的所有东西
OpStack.pop();//弹出左括号
}
else {
while (!OpStack.empty() && judgeValue(token)<=judgeValue(OpStack.peek())){
////如果遇到其他任何操作符,从栈中弹出这些元素直到遇到发现更低优先级的元素或栈空为止
output=output.concat(OpStack.pop()+" ");
}
OpStack.push(token);
}
}
else
OpStack.push(token);//如果栈空则直接将遇到的操作符送入栈中,第一个不可能为右括号
}
else {
output=output.concat(token+" ");//如果遇到操作数就直接输出
}
}
while (!OpStack.empty()){
//如果读到了输入分末尾,则将占中所有元素依次弹出
output=output.concat(OpStack.pop()+" ");
}
- 功能:将中缀表达式转后缀表达式
- 转化方法:
- 如果遇到操作数就直接输出。
- 如果遇到操作符则将其放入栈中,遇到左括号也将其放入
栈中。 - 如果遇到右括号,则将栈元素弹出,将弹出的操作符输出直到遇到遇到左括号为止,注意左括号只弹出不输出。
- 如果遇到任何其他操作符,如“+”,“-”,“*”,“÷”,“(”等,从栈中弹出元素直到遇到
更低优先级的元素或栈空为止。弹出完这些元素才将遇到的操作符压入栈中。 - 如果读到了输入的末尾,则将栈中所有元素依次弹出。
- 解析:请看上方代码中注释。
- 代码5:
while (tokenizer.hasMoreTokens()){
token=tokenizer.nextToken();
if(isOperator(token)){
op2=stack.pop();
op1=stack.pop();
result=calcSingle(op1,op2,token);
stack.push(new Integer(result));
}
else {
stack.push(new Integer(token));
}
- 功能:使用后缀表达式规则进行计算。
- 解析:参考后缀表达式计算规则
- 代码6:
try {
……
}catch (ArithmeticException e){
……
}
- 功能:解决生成的算式中除数是0的问题。
- 解析:参考try-catch的使用方法
四、UML类图
五、运行结果截图
六、代码提交
码云链接: https://gitee.com/imjoking/PairWork
七、遇到的困难及解决方法
- 问题1:如何生成随机数?
- 解决方法:
- 法一:用java.lang.Math中的random方法
- 法二:用Rondom中的public int nextInt(int n)方法
- 问题2:如何随机生成括号?
- 解决方法:将‘(’和‘)’也放入操作符数组中,进行随机生成操作符。
- 问题3:按照问题2的解决方法生成出的括号不配对,如何生成配对的括号?
- 解决方法:每生成一个左括号,就开始记录左括号后的操作数,当操作数不等于0的时候,就可以选择产不产生右括号,否则不产生右括号。
if(a[(int)(Math.random()*6)]=='('){
flag1++;
}
if(a[(int)(Math.random()*6)]==')'){
flag2++;
}
当flag1!=flag2时,重新生成算式。
- 问题4:生成的括号个数一样多,但是位置不正确。形如:
)23+(56
、(23)
if (flag == 1) {
counter++;
}
if (flag == 0) {
choicechar = randnum.nextInt(2);
if (choicechar == 1 && i != str.length - 1) { //产生左括号
str[i] = "( " + str[i];
counter = 0;
flag = 1;
}
}
if (flag == 1) {
choicechar = randnum.nextInt(2);
if (choicechar == 1 && counter != 0) {
str[i] = str[i] + " )";
flag = 0;
}
}
}
if (flag == 1) {
str[str.length - 1] = str[str.length - 1] + " )";
}
-
问题5:不能生成嵌套括号。
-
解决方法:我的小伙伴lyz他说你可以先全部生成左括号然后再生成右括号,counter记录生成的左括号的个数,每生成一个右括号counter--,直至counter=0时为止,于是我对我的代码依照他的思路进行了修改,最终实现了嵌套括号的生成。
-
问题6:生成的是头尾括号而且头尾括号所包含的内容是式子的全部内容。
-
解决方法:此问题还未能解决。
-
问题7:在后缀表达式的计算过程中,出现除数为0时,出现异常。
-
解决方法:我的小伙伴机智的使用了使用try-catch进行抓包。
八、结对感受
- 我们起初为了提高效率,对此任务进行了分工,我负责生成四则运算的式子,他负责四则算式的计算部分。我们由于时间冲突,所以两个人之间无法一起编程。我们打算各自完成各自的部分,当两个人都写完的时候在进行整合。
- 确实我们也是这么做的,但是等到整合的时候发现看不懂对方的写的代码,理解别人的代码真的很困难,因为lyh写的思路比较清晰,所以我大概能看懂他的代码,但是我的代码就比较晦涩难懂了。他在试图对我的代码进行优化的时候,发现无法进行下去。所以我们又花了大量的时间去理解对方的代码,他遇到不懂的时候就问我这个变量是干嘛的,然后我给他解释完后,他会向我提出一些编程习惯上的问题,而且还会对我的代码进行一些优化并对此进行解释。我从中学习到了很多,他也非常耐心的教我,我觉得跟他合作我进步飞快。
- 这是我们第一次一起合作,因为我的编程能力比较差,所以拖慢了我们的进度,并且我们在整合过程中遇到看不懂对方代码的问题,所以我们觉得下次还是得一起完成,不能各自完成各自的部分,然后再进行整合。
- 虽然我在自己编程的过程中没有体会到合作的感觉,只是偶尔我的小伙伴灵机一动会指点我一下,但是在整合的过程中,我就深深的体会到合作的好处了,我们不会的一起思考,能优化的一起想办法优化,互相为对方提出对方代码能够优化的地方,相互学习,共同上进。
- 我认为需要改进的地方就是两个人之间应该多进行交流,不应该各自想各自的。
九、PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(小时/分钟) | 实际耗时(小时/分钟) |
---|---|---|---|
Planning | 计划 | 2小时 | 4小时 |
· Estimate | · 估计这个任务需要多少时间 | 20小时 | 28小时半 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 4小时 | 2小时半 |
· Design Spec | · 生成设计文档 | ||
· Design Review | ·设计复审(和同事审核设计文档) | 2小时 | 1小时 |
·Code Standard | ·代码规范 | 半小时 | 50分钟 |
·Design | ·具体设计 | 1小时 | 半小时 |
·Coding | ·具体编码 | 5小时 | 8小时 |
·Code Review | ·代码复审 | 2小时 | 5小时 |
·Test | ·测试(自我测试,修改代码,提交修改) | 1小时 | 2小时半 |
Reporting | 报告 | 1小时 | 3小时 |
·Test Report | ·测试报告 | ||
·Size Measurement | ·计算工作量 | 半小时 | 半小时 |
·Postmortem&Process Improvement Plan | ·事后总结,并提出过程改进计划 | 半小时 | 40分钟 |
合计 | 19小时半 | 28小时半 |