20175214林郅聪 结对编程项目—四则运算 第一周 阶段总结
一、需求分析
1.题目要求:
实现一个命令行程序,要求:
自动生成小学四则运算题目(加、减、乘、除)
- 支持整数
- 支持多运算符(比如生成包含100个运算符的题目)
- 支持真分数
- 统计正确率
扩展需求
文件
- 处理生成题目并输出到文件
- 完成题目后从文件读入并判题
- 多语言支持
简体中文
,繁體中文
,English
生成题目去重
二、设计思路
我们的设计和分工
结对项目,当然要两个人一起完成,为了缩短我们的工作时间,我们进行了简单的分工
我负责的部分:
- 运算式的产生以及括号的插入位置的随机情况 (GetQuestion&&GetBracket)
- 计算并生成正确答案(GetAnswer)
伙伴负责的问题:
- 随机数和随机运算符的产生(GetNumber&&GetOperator)
- 问题的建立和正确率的统计 (SetQusetion(这里主要是确定运算数字的范围和运算符的个数并统计正确数目))
- 中缀表达式转换为后缀表达式(MidToEnd)
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/fzlzc/java2019/tree/master/src/CalculateSystem
六、过程中遇到的问题
- 问题1:在添加括号时,括号添加的位置过于随机,会出现在一个数字两边有多个括号的形式,例如(((8)))这种
- 问题1的解决:反复调试代码,观察括号出现的位置和存在的问题,利用flag变量控制括号生成的位置和数目,最后通过五个flag变量让括号产生的位置只能载间隔运算符的数 两侧
- 问题2:在计算答案时,出现了分母为0的情况
- 问题2解决办法:因为后缀表达式计算过程与正常的不同,虽然控制了不产生随机数0但在计算除法向下取整时可能会出现分母为0的情况,因此选择进行判断,当出现这种情况时给出提示直接终止程序
- 问题3:不知道如何将转换好的后缀表达式放入栈中
- 问题3解决办法:因为在生成问题时,字符与数字之间采用了空格分隔,在类库查找后,发现了一个类可以将由规律的字符串放入栈中,代码为
StringTokenizer token = new StringTokenizer(q, " ");
七、对结对伙伴的评价
我认为我的搭档工作热情很高,责任心强,互相分配的工作完成得很好,在一起讨论时给我很大启发,是一个很好的小伙伴,但在完成过程中也会出现意见的分歧,希望在接下来的任务中能够继续磨合
八、总结
本次任务是我第一次通过代码完成四则运算,完成的过程十分辛苦,比预想的困难许多,但两个人完成也相对节省了很多的时间,通过此次作业我也认识到了老师让我们组队学习的必要性和重要的意义,也希望能通过结对学习收获更多的知识。当然我们的代码还存在欠缺,这周的有一些要求还没有实现,下周的目标是现将这周欠缺的任务补齐,然后再尝试一下把扩展需求完成,希望能让代码的功能更加全面
九、预估时间与实际时间
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 |