结对编程项目—四则运算 第二周 整体总结
20175214林郅聪 结对编程项目—四则运算 第二周 整体总结
一、需求分析
1.题目要求:
-
实现一个命令行程序,要求:
-
自动生成小学四则运算题目(加、减、乘、除)
- 支持整数
- 支持多运算符(比如生成包含100个运算符的题目)
- 支持真分数
-
统计正确率
-
2.扩展需求
- 可以实现多语言,包括简体中文,繁体中文和英文
- 生成题目去重
- 输入题目到文件,从文件中读取题目并判断对错
3.深度需求
本次结对项目要求完成小学生的四则运算,除了题目中要求的内容外,还可以包含以下方面
- 进行难度更高的运算,比如嵌套运算或其他运算符例如求方幂、求整、取余等
- 由于是通过程序完成,在给出测试的正确率的情况下可以判断正确率是否达到合格标准,若未达到则继续进行直到达到标准为止
- 让学生自由选择题目难度,根据不同难度给出相应评价
二、设计思路
在开始本周任务前,我对我们的程序进行了简要的分析,和上周相比有如下的改变
- 增加了真分数的运算,并控制分数的格式(在后面会有说明)
- 给出了多语言的支持:简体中文、繁体中文、英文
- 生成了题目去重的功能:在原来的基础上进行了适当的修改,因为当生成算式较短时比较容易重复,但这一部分的代码还存在一些问题,目前只能判断两道题目是否完全相同,不能排除
1 + 2
和2 + 1
这种重复的情况
UML类图
三、变动的代码
- 首先是分数的形式,选择将分号“/”也当做随即字符和加减乘除一起随机产生,而在中缀转后缀运算式将分号的优先级设置为仅低于有括号,这样可以区别分号“/”和除号“÷”
public int compareValue(char chi) {
int number = 0;
switch (chi) {
case '(':
number = 1;
break;
case '+':
case '-':
number = 2;
break;
case '*':
case '÷':
number = 3;
break;
case '/':
number = 4;
break;
case ')':
number = 5;
break;
default:
number = 0;
break;
}
return number;
}
- 如何产生真分数?分数的产生很容易,那如何控制产生真分数呢?和其他小组的同学讨论研究后发现他们的方式都不适合我们,因为在产生算式用到的方法不同,分数的生成方式也存在差异,因此他们声称分数的方法并不适合我们。在和结对伙伴讨论后,我们决定先产生分子,根据分子的范围控制分母的大小
else if (rand == 0 && flag2 % 2 == 0 || flag5 % 3 == 0 && flag2 % 2 == 0 { //插入数字
if(flag6==1) //当产生了分号时
n = num.GetNumber(a-n)+n; //控制分号后面的数比分号前面的数大但不会超过给出的最大值
else n = num.GetNumber(a);
q += n;
flag2++;
flag5 = 0;
countTotal++;
- 接下来是真分数的产生和运算部分,关于这一部分,在编写之前花了很长时间思考,因为分数在运算时涉及到了压栈的问题,而在运算时如果将分子分母和分号分开压栈那么就失去了本来的意义,如果将分数控制为字符串的形式进行压栈操作,那么之前的代码需要全部重写,最后在翻看教材的时候发现的书上第四章Example4_23中给出了一个有理数Rational类,可以通过该类进行分数的运算,而这种方法也正适合我们之前写的程序。在对书上的例子进行少许修改后,应用到我们的程序中
Rational div(Rational r) { //除法运算
int a=r.getNumerator();
int b=r.getDenominator();
int newNumerator=numerator*b;
int newDenominator=denominator*a;
Rational result=new Rational();
if(a==0) {
System.out.println("该算式无解");
result.setNumerator(0);
}
else {
result.setNumerator(newNumerator);
result.setDenominator(newDenominator);
}
return result;
}
-
简要说明:这一部分的程序与书上基本相同,但由于书上是控制输入,因此不需要排除分母为0的情况,而我们的程序中数字时随机产生的,可能会在运算的过程中产生了计算出的结果是0作为了分母,因此需要控制这种情况
-
多语言选择:在完成这一部分内容时,我们选择了用抽象类的方法,将每一种提示作为一个抽象方法,三种语言作为子类重写其中的方法
package CalculateSystem;
public abstract class Language {
public abstract String getQuestionNumber();
public abstract String getMaxNumber();
public abstract String getMaxOperator();
public abstract String getInputAnswer();
public abstract String getRight();
public abstract String getWrong();
public abstract String getRate();
public abstract String getContinue();
public abstract String getThanks();
}
下面是子类English中重写的方法
public class English extends Language {
public String getQuestionNumber() {
return "Enter the number of questions you want to do:";
}
public String getMaxNumber() {
return "Please enter the maximum number you want to operate:";
}
public String getMaxOperator() {
return "Please enter the length of the expression you want to operate:";
}
public String getInputAnswer() {
return "Enter your answer :(floating point)";
}
public String getRight() {
return "Congratulations! Your answer is right";
}
public String getWrong() {
return "Wrong answer! The correct answer is";
}
public String getRate() {
return "Test over! Your accuracy is";
}
public String getContinue() {
return "Do you want to continue? Please enter 1 to continue and 0 to exit";
}
public String getThanks() {
return "Test over, thanks for using!";
}
}
- 题目去重:这一部分的代码的功能 并不全面,只限于判断两道题目是否完全相同,并不能判断其中是否含有没有意义的重复
GetQuestion q = new GetQuestion();
String question1 = q.get(maxnum,maxop);
if(question1.equals(question2)) { //判断时候和前一题是否相等,相等则不计入次数,重新产生等式
i--; continue;
}
System.out.println(question1);
question2 = question1; //不相同则把该题目赋值给question2用于和下一题进行比较
四、遇到的问题
- 开始时我和伙伴在考虑如何生成真分数的同时,也在考虑如何保证过程中也都控制真分数,后来发现没有必要纠结这个问题,只需要控制在生成时生成简单的真分数,例如
13/15
即可,因为在运算过程中给存在括号优先级的问题,例如(15*3+8)/16
并不需要在运算过程中控制都是真分数 - 在进行压栈和弹栈运算时,将弹出的数转化为有理数时,发现第一个弹出的数总是会被第二个数覆盖,在探讨测试后发现是因为定义了一个变量num,他的作用是,当栈中的数不是运算符的时候,要将其放回。这个变量定义在了
if-else
语句外,因此只有最开始初始化了变量,在运算过程中一直使用后面的数覆盖前面的数,因此弹出的两个数会一样,更该方法是将变量的初始化放在else语句中,每次判断时,都要初始化,这样不会对之前的产生影响。
while (token.hasMoreTokens()) {
temp = token.nextToken();
if (Isop(temp) == 1)//遇到操作符,弹出栈顶的两个数进行运算 {
op2 = (Rational) stack.pop();
op1 = (Rational) stack.pop();//弹出最上面两个操作数
result = cal(temp.charAt(0), op1, op2);//根据运算符进行运算
stack.push(result);//将计算结果压栈
}
else {
Rational num = new Rational(); //每次重新初始化
num.setNumerator(Integer.parseInt(temp));
stack.push(num);//操作数入栈
}
}
- 产生分数时会产生
5/7/8
的形式,事实上这种分数产生的意义不大,我们希望避免这种情况,几经尝试后选择在产生问题的类中再加flag变量进行控制
五、运行过程截图
六、代码托管
https://gitee.com/fzlzc/java2019/tree/master/src/CalculateSystem
七、对结对伙伴的评价
我觉得我的小伙伴非常棒,这周他主要负责添加真分数功能和语言包,这部分基本由他独立完成,我只负责对接,最后拼接虽然有些磕磕绊绊,有许多没有发现的bug错误,但是一一纠正检验,一起讨论学习的氛围让我觉得十分酣畅,而且成果还是比较让我们满意的,基本完成了预期,希望以后可以继续这样快乐合作,快乐编程。
八、预估时间与实际时间
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
•Estimate | • 估计这个任务需要多少时间 | 570 | 815 |
Development | 开发 | ||
• Analysis | • 需求分析 (包括学习新技术) | 30 | 40 |
• Design Spec | • 生成设计文档 | 30 | 30 |
• Design Review | • 设计复审 (和同事审核设计文档) | 20 | 40 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 20 | 60 |
• Design | • 具体设计 | 20 | 25 |
• Coding | • 具体编码 | 360 | 500 |
• Code Review | • 代码复审 | 20 | 20 |
• Test | • 测试(自我测试,修改代码,提交修改) | 30 | 50 |
Reporting | 报告 | ||
• Test Report | • 测试报告 | 20 | 20 |
• Size Measurement | • 计算工作量 | 10 | 10 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 10 | 20 |
合计 | 570 | 815 |
即便不高谈理想,也要心存信仰。