20175205 20175306 结对编程项目-四则运算 阶段总结
一、预估与实际
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
• Estimate | • 估计这个任务需要多少时间 | 1000 | 1500 |
Development | 开发 | ||
• Analysis | • 需求分析(包括学习新技术) | 30 | 90 |
• Design Spec | • 生成设计文档 | 30 | 40 |
• Design Review | • 设计复审 | 60 | 60 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 60 | 60 |
• Design | • 具体设计 | 60 | 60 |
• Coding | • 具体编码 | 600 | 1000 |
• Code Review | • 代码复审 | 60 | 60 |
• Test | • 测试(自我测试,修改代码,提交修改) | 30 | 60 |
Reporting | 报告 | ||
• Test Repor | • 测试报告 | 20 | 20 |
• Size Measurement | • 计算工作量 | 10 | 10 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 40 | 40 |
合计 | 1000 | 1500 |
二、需求分析
·特点一:需要随机产生算式
·特点二:可以进行多运算符运算,包括括号
·特点三:需要判断正确和错误
·特点四:需要输出答对的数目和正确率
扩展需求
·特点一:可以进行真分数的运算
·特点二:文件:处理生成题目并输出到文件;完成题目后从文件读入并判题
·特点三:多语言支持:简体中文,繁體中文,English
·特点四:生成题目去重
三、设计思路(同时输出UML类图)
- 首先解决怎么生成随机数,及随机运算符的问题
- 怎么产生多个随机运算符
- 括号怎么随机插入四则运算中
- 如何让计算机计算中缀表达式
(UML图)
四、实现过程中的关键代码解释
// 将中缀表达式转换为后缀表达式
public static String infixToSuffix(String exp) {
Stack<String> s = new Stack<String>(); // 创建操作符堆栈
String suffix = ""; // 要输出的后缀表达式字符串
String str[] = exp.split(" "); //以空格为分界线放到数组里
int length = str.length; // 输入的中缀表达式的长度
String temp="";
for (int i = 0; i < length; i++) { // 对该中缀表达式的每一个字符并进行判断
switch (str[i]) {
case " ":break; // 忽略空格
case "(":
s.push(str[i]); // 如果是左括号直接压入堆栈
break;
case "+":
case "-":
if(s.size() != 0){ // 碰到'+' '-',将栈中的所有运算符全部弹出去,直至碰到左括号为止,输出到队列中去
temp = s.pop();
if (temp.equals("(")) { // 将左括号放回堆栈,终止循环
s.push(temp);
s.push(str[i]);
break;
}
else{
s.push(str[i]);
suffix = suffix+temp+" ";
break;
}
}
else{
s.push(str[i]); // 说明是当前为第一次进入或者其他前面运算都有括号等情况导致栈已经为空,此时需要将符号进栈
break;
}
// 如果是乘号或者除号,则弹出所有序列,直到碰到加好、减号、左括号为止,最后将该操作符压入堆栈
case "*":
case "/":
if(s.size()!=0){
temp = s.pop();
if(temp.equals("+")||temp.equals("-")||temp.equals("(")){
s.push(temp);
s.push(str[i]);
break;
}
else{
s.push(str[i]);
suffix = suffix+temp+" ";
break;
}
}
else {
s.push(str[i]); //当前为第一次进入或者其他前面运算都有括号等情况导致栈已经为空,此时需要将符号进栈
break;
}
// 如果碰到的是右括号,则距离栈顶的第一个左括号上面的所有运算符弹出栈并抛弃左括号
case ")":
while (!s.isEmpty()) {
temp = s.pop();
if (temp.equals("(")) {
break;
} else {
suffix = suffix+temp+" ";
}
}
break;
// 默认情况,如果读取到的是数字,则直接送至输出序列
default:
suffix = suffix+str[i]+" ";
break;
}
}
// 如果堆栈不为空,则把剩余运算符一次弹出,送至输出序列
while (s.size() != 0) {
suffix = suffix+s.pop()+" ";
}
//
return suffix;
}
注:详细解释请看:>https://blog.csdn.net/coder_dacyuan/article/details/79941743
或>https://www.cnblogs.com/unixfy/p/3192446.html
中缀转后缀是核心的计算方法,掌握此计算方法才可以判断正误!!!
switch (num.RandomNum(2)) {
case 0: //不加括号
key = com.stringToArithmetic(s);
System.out.print(s + " = ");
break;
case 1: //加括号
if (choice > 1) {
str = s.split(" "); //字符串转化为字符串数组
Stack<String> Z = new Stack<String>(); //定义个栈
String temp = new String(); //定义一个临时数组
for (int k=0; k!=str.length-2; k++) { //遍历前length-2个元素
if ((str[k].equals("+") || str[k].equals("-"))) { //如果是加减
int b = k+2; //可以直接访问数组
int a = k+1;
if (str[b].equals("*") || str[b].equals("÷")) { //下一个符号是乘除
temp = Z.pop();
Z.push("(");
Z.push(temp);
Z.push(str[k]);
Z.push(str[a]);
Z.push(")");
k++;
} else //下一个符号是加减
Z.push(str[k]);
}
else //如果是乘除数字就直接入栈
Z.push(str[k]);
}
for(int m=str.length-2;m<str.length;m++)
Z.push(str[m]);
String[] ZZ = new String[Z.size()];
int p = Z.size() - 1;
while (Z.size() != 0) { //将栈中的元素放到数组中
temp = Z.pop();
ZZ[p] = temp;
p--;
}
String t = new String();
t = "";
for (int q = 0; q < ZZ.length; q++)
t = t + ZZ[q] + " ";
key = com.stringToArithmetic(t);
System.out.print(t + " = ");
ZZ = null;
t = "";
break;
}
else{
key = com.stringToArithmetic(s);
System.out.print(s + " = ");
break;
}
}
注:这是在算式中随机插入括号的关键代码,同样运用了堆栈的知识。首先随机产生两个随机数01,如果是0,则不插入括号,如果是1则插入括号;我们接下来解释当随机数为1时的情况,遍历整个算式,第一位是数字,则将数字压入栈,当遇到加号或减号时,记作k,继续遍历,如果k+2是乘号或除号,则需要加括号。此时将第一次压入栈的数字弹出,然后将"(",压入栈,依次将数字、加减号、数字压入,直到碰见")"结束,输出即可。
五、测试方法
六、运行过程截图
七、代码托管地址
码云链接:>https://gitee.com/wjs123456/wjs20175306.git
八、遇到的困难及解决方法
- 问题一:如何产生多运算符算式
解决过程:首先定义一个choice,表示符号的个数,choice = num.RandomNum(5) + 1;
,但是必须+1,因为符号的个数是要从1开始,而不能从0开始。()里的数则表示你想输出题目中符号的个数。 - 问题二:如何随机加入括号
解决过程:首先我们打算采用移位的方法,当碰到加(减)号时,设此时加(减)号的位置为k,如果k+2位是乘(除)号,则需要进行移位,分别将数字和符号后移,然后在k-2插入括号。这个方法初次实验是可以的,但是随着后续的进行,发现这个方法行不通。会出现bug。在学习了中缀转后缀后,我们采用了堆栈的知识解决。(详情请见上面关键代码解释!) - 问题三:如何判断答案正误
解决过程:想要判断答案正误就要先计算出正确答案,这里我们学习了老师给我们的建议采用调度场算法(>http://hczhcz.github.io/2014/02/27/shunting-yard-algorithm.html)(>https://en.wikipedia.org/wiki/Shunting-yard_algorithm)。调度场算法的关键是中缀转后缀,于是经过不段的学习,有了最终的算法。(详情请见上面关键代码解释!!!) - 问题四:如何计算正确率
解决过程:有了上面的能够判断答案正误再输出正确率就很简单了,我们直接采用用正确题的个数除以总题数就可以了。但是在输出时出现了问题,输出时为double型,保留的小数过多,int型又不够精确,最终我们采用了float型。由于我们并没有定义变量正确率,所以采用了C语言中学到的方法,将println改为printf,并定义保留两位小数。
九、对结对的小伙伴做出评价
我结对的小伙伴实在是非常优秀!!!非常勤奋刻苦,善于钻研,遇到困难会想尽一切方法进行解决。同时又非常具有创造力,自己会想出很多方法来解决问题,虽然有时候想的办法很难,但是她还是会神奇般的实现出来,别人怎么说都不会换,直到最后出现问题才会改正。(就当我是在夸她~)能跟她结对我感到非常荣幸!!!膜拜!!!
十、总结
本次结对收获颇丰,我们学会了如何产生随机数,产生随机符号,产生多运算符算式,最重要的是我们学会了用调度场算法进行计算,更学会了堆栈的简单使用,可以说是很厉害了!这次结对主要是她负责写代码,我负责提供思路,发现代码问题,解决问题,测试代码等工作,配合的还是非常好的。下周我们的分工将更加明确,我相信我们会实现1+1>2的效果!!!