20175209王梓鸿 结对编程项目—四则运算 第一周 阶段总结
一、需求分析
1.题目要求:
-
实现一个命令行程序,要求:
-
自动生成小学四则运算题目(加、减、乘、除)
- 支持整数
- 支持多运算符(比如生成包含100个运算符的题目)
- 支持真分数
-
统计正确率
-
-
扩展需求
-
文件
- 处理生成题目并输出到文件
- 完成题目后从文件读入并判题
-
多语言支持
简体中文
,繁體中文
,English
-
生成题目去重
-
二、设计思路
1.编写背景及完成情况
- 在开始写设计思路之前,我觉得应该说一下我们编写的情况(可能有些跑题)。这次结对的项目上周一老师就给出了,因为上周的时候我和我的结对伙伴都有计算机二级的考试,一直在准备复习,所以也没有花太久时间去思考。我们真正开始进行是在周日晚上,近几天的课比较多,因此完成起来略显仓促。
- 完成情况:这一周的任务要求大体完成,因为真分数的随机产生比较复杂,因此这部分内容没有进行,在做下一周的任务时会添加上;扩展需求由于时间问题暂时也没有考虑,下周的任务中会尽可能完成(其中一些东西感觉并不是很会)
2.开始编写之前的想法
在大一下C语言课上老师也让我们写过四则运算的程序,最初我想在之前的基础上进行修改,但对比要求后发现差别很大,因此推翻了这个想法。在开始写之前,我仔细阅读了题目及相关要求,大概划分了需要完成的部分。主要分为产生问题,计算,和比较判断三个部分,但其实计算部分是最复杂的,其中包含的中缀转后缀采用的逆波兰法并不是很理解,也花费了相当长的时间去理解,因为时间紧张,晚上熄灯后也在思考应该怎样去完成好一部分,这也是编写程序时最令我头疼的地方,因为即使弄懂了原理,在具体执行到代码时还会存在能否成功运行的问题。
3.我们的设计和分工
结对项目,当然要两个人一起完成,为了缩短我们的工作时间,我们进行了简单的分工
-
我负责的部分:
- 随机数和随机运算符的产生(GetNumber&&GetOperator)
- 问题的建立和正确率的统计 (SetQusetion(这里主要是确定运算数字的范围和运算符的个数并统计正确数目))
- 中缀表达式转换为后缀表达式(MidToEnd)
-
伙伴负责的问题:
- 运算式的产生以及括号的插入位置的随机情况 (GetQuestion&&GetBracket)
- 计算并生成正确答案(GetAnswer)
4.UML图
三、实现过程中关键代码的解释
- 将后缀表达式计算出结果
public class GetAnswer {
double result, num = 0;
int result1;
String q;
Stack stack;
public GetAnswer() {
stack = new Stack();
}
void set(String question) { //输入后续排列的字符串
q = question;
}
int get() {
double op1, op2;
StringTokenizer token = new StringTokenizer(q, " ");
String temp;
while (token.hasMoreTokens()) {
temp = token.nextToken();
if (Isop(temp) == 1)//遇到操作符,弹出栈顶的两个数进行运算 {
op2 = (double) stack.pop();
op1 = (double) stack.pop();//弹出最上面两个操作数
result = cal(temp.charAt(0), op1, op2);//根据运算符进行运算
stack.push(result);//将计算结果压栈
}
else {
num = Double.valueOf(temp);
stack.push(num);//操作数入栈
}
}
if (result >= 0)
result1 = (int) Math.floor(result); //将结果舍去小数,无论正负
else
result1 = (int) Math.ceil(result);
return result1;//输出结果
}
double cal(char op, double a, double b) { //对栈顶弹出的两个数进行运算
double c = 0;
switch (op) {
case '+':
c = a + b;
break;
case '-':
c = a - b;
break;
case '*':
c = a * b;
break;
case '÷':
if (b == 0) {
System.out.println("出现了分母为0的情况,请重新开始");
System.exit(0);
}
else {
c = a / b;
break;
}
}
return c;
}
int Isop(String op) { //判断是不是运算符
if (op.equals("+") || op.equals("-") || op.equals("*") || op.equals("÷") || op.equals("/"))
return 1;
else return 0;
}
}
简要说明:我们考虑到了计算机在进行计算时存在的问题,当遇到除法的时候,如果数据类型为整型,那么计算机会将结果自动向下取整,当出现了多个除法接连出现时,会将每一次的结果都向下取整后再进行下一个除法运算,这样就会与实际算出来的结果有较大的误差。因此我们选择将数据类型定义为浮点型,将算出的最终结果向下取整再转换为整型,这样在实际运算时就不会因为计算机计算的方式而产生误差
- 中缀表达式转化为后缀表达式
public class MidToEnd {
String C = new String();
String End = ""; //存储数字的字符串
public void ChangeString(String str) {
C = str;
}
public String ChangeOrder() {
Stack store = new Stack(); //创建一个存储字符的栈
for (int i = 0; i < C.length(); i++) {
char op = C.charAt(i); //将索引值为i处的字符的值返回
if (op >= '0' && op <= '9')
End = End + op;
else if (op == '(')
store.push(op);
else if (op == '+' || op == '-' || op == '*' || op == '÷') {
End = End + " ";
if (store.empty())
store.push(op);
else if (compareValue(op) > compareValue((char) store.peek())) //比较运算符优先级
store.push(op);
else {
End = End + String.valueOf(store.pop()) + " ";
i--;
}
}
else if (op == ')') {
while ((char) store.peek() != '(') {
End = End + " " + String.valueOf(store.pop());
}
store.pop();
}
}
while (!store.empty())
End = End + " " + String.valueOf(store.pop());
return End;
}
public int compareValue(char chi) {
nt number = 0;
switch (chi) {
case '(':
number = 1;
break;
case '+':
case '-':
number = 2;
break;
case '*':
case '÷':
number = 3;
break;
case ')':
number = 4;
break;
default:
number = 0;
break;
}
return number;
}
}
简要说明:因为在压栈和弹栈的过程中涉及到运算符的优先级问题,因此需要对运算符的优先级进行比较,否则在转换为后缀计算的时候会与正常的结果不同
- 计算过程
public class GetQuestion {
public String get(int a,int b) {
String q = "";
int flag1 = 0, flag2 = 0, flag3 = 1, flag4 = 0, flag5 = 0, countTotal; // flag1记录插了几个左括号,flag2标记下一个插入的是数字还是运算符,countTotal记录共插入几个字符
GetNumber num = new GetNumber(); //flag3限制括号数量,flag4控制左右括号之间至少间隔一个运算符
GetOperator op = new GetOperator(); //flag5控制左括号不连续出现
GetBracket bra = new GetBracket();
for (countTotal = 0; flag3 == 1 && countTotal < b; ) {
GetNumber random = new GetNumber();
int rand = (random.GetNumber(a)) % 2;
if (flag1 == 3)
flag3 = 0;
if (countTotal != 0 && rand == 1 && flag2 % 2 == 0 && flag3 == 1 && flag4 % 2 == 0 && flag5 % 2 == 0 && countTotal < b-3) { //插左括号
q += bra.setBracket(0);
flag1++;
flag5 = 1;
countTotal++;
}
else if (rand == 0 && flag2 % 2 == 0 || flag5 % 3 == 0 && flag2 % 2 == 0){
//插数字
q += num.GetNumber(a);
flag2++;
flag5 = 0;
countTotal++;
}
else if (rand == 1 && flag1 > 0 && flag2 % 2 == 1 && flag4 % 2 == 1) { //插右括号
q += bra.setBracket(1);
flag1--;
countTotal++;
}
else if (rand == 0 && flag2 % 2 == 1) { //插运算符
q += op.GetOperator();
flag2++;
flag4++;
countTotal++;
}
}
while (q.endsWith("+") || q.endsWith("-") || q.endsWith("*") || q.endsWith("÷") || q.endsWith("(")) {
int L = q.length();
if (q.endsWith("("))
flag1--;
q = q.substring(0, L - 1);
}
while (flag1 > 0) {
q += bra.setBracket(1);
flag1--;
}
return q;
}
}
四、运行过程截图
五、代码托管地址
https://gitee.com/wangzihong/20175209/tree/master/src/CalculateSystem
六、过程中遇到的问题
- 问题1:在中缀转后缀时,刚开始没有考虑到优先级的问题,导致在单独调试时出现了转化结果与后缀表达式不符的情况
- 问题1的解决:首先将输入的算式长度缩短,并在草纸上画出详细过程进行分析,找出问题后建立了一个函数用于比较运算符的优先级
- 问题2:在输出正确率时,当正确率为小数是无法控制产生的位数,位数总是特别长
- 问题2解决办法:在API文档中找到了
String format
类可以用于转换 - 问题3:刚刚接触栈时,对他的命令和类中的具体功能不是很熟悉
- 问题3解决办法:在API文档中详细阅读了相关的介绍,并在网上查找了一些较为简单的和栈相关的代码进行熟悉
七、对结对伙伴的评价
我觉得我的小伙伴非常的聪明,反应也很迅速,我们二人的工作量基本相同,但是他往往比我更快完成,我偶尔有困惑的问题在经过讨论之后他总是能给出我一些很好的建议去解决问题,当然在编写的过程中也有意见不一样的地方,有的时候会进行很激烈的讨论。两个人之间的磨合还稍微有些欠缺,在以后的作业完成中还需要相互磨合
八、总结
本次结对作业可以说是大学学习中最为庞大的一项内容了,在讨论和编写的时候花费了大量的时间,并且总是遇到很多问题,需要花费大量的时间查找资料,调试的过程也非常心酸,但是能将这次的作业顺利完成也让我感觉非常有成就,同时让我对这门课的学习更加有兴趣,也明白了结对学习的重要性。当然我们的代码还存在欠缺,这周的有一些要求还没有实现,下周的目标是现将这周欠缺的任务补齐,然后再尝试一下把扩展需求完成,希望能让代码的功能更加全面
九、预估时间与实际时间
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
·Estimate | · 估计这个任务需要多少时间 | 790 | 1320 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 50 | 120 |
· Design Spec | · 生成设计文档 | 30 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 40 | 60 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 50 |
· Design | · 具体设计 | 40 | 70 |
· Coding | · 具体编码 | 500 | 750 |
· Code Review | · 代码复审 | 20 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 30 | 120 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 30 | 20 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 30 |
合计 | 790 | 1320 |