MathExamLv2——林志松 211406285 李明康 211606314
一、预估与实际
Planning | 计划 | ||
• Estimate | • 估计这个任务需要多少时间 | 20 | 30 |
Development | 开发 | ||
• Analysis | • 需求分析 (包括学习新技术) | 60 | 240 |
• Design Spec | • 生成设计文档 | 20 | 40 |
• Design Review | • 设计复审 | 5 | 5 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
• Design | • 具体设计 | 20 | 40 |
• Coding | • 具体编码 | 120 | 780 |
• Code Review | • 代码复审 | 30 | 20 |
• Test | • 测试(自我测试,修改代码,提交修改) | 60 | 300 |
Reporting | 报告 | ||
• Test Repor | • 测试报告 | 60 | 70 |
• Size Measurement | • 计算工作量 | 5 | 5 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 10 | 10 |
合计 | 420 | 1590 |
二、需求分析
我通过作业要求的方式了解到,小学三年级数学有如下的几个特点:
特点1
- 运算符在2~4个
特点2
- 可以加括号
特点3
- 减法运算的结果不能有负数
特点4
- 除法运算除数不能为0,不能有余数
经过分析,我认为,这个程序应当:
在作业一的基础上新增带括号的四则运算。
通过调度场和逆波澜算法来实现运算。
三、设计
1. 设计思路
程序有一个类,十二个方法:
private static void transferToPostfix(LinkedList《string》 list) //中缀表达式转为后缀表达式
private static void calculate() //根据后缀表达式计算结果
private static boolean isOperator(String oper) //判断是否操作符
private static int priority(String s) //计算操作符的优先级
private static int cal(int num1,int num2,String operator) //进行计算
private static void gradeOne(int n) //一年级的题目
private static void gradeTwo(int n) //二年级的题目
private static void gradeThree(int n) //三年级的题目
private static BufferedReader in() //输入方法
private static void out(String str) //输出方法
private static void wrongJudgement(String[] args, int n, int grade) //除错处理
public static void main(String[] args) //传说中的main函数~~~~
算法的关键:
调度场算法。
逆波澜算法。
以上算法参考来自于This link
另外附上 calculate()方法的程序流程图:
2. 实现方案
准备工作:先在Github上创建仓库,克隆到本地。
技术关键点:
如何令用户在输入 -n n -grade grade 和 -grade grade -n n 的时候都可以成功运行。
如何在四则运算中添加括号。
如何实现调度场算法
如何实现逆波澜算法
1. 调试日志
添加括号时括号是否存在与四则运算本身毫无影响,后续通过if来判定只有同时出现“-+”和“*/”括号才满足条件出现。
括号的添加位置产生错误,出现类似 100 ( + 200 ) * 300的错误四则运算,后续通过indexOf的方法来查找 “+-”的位置然后向左和向左查询空格的下标来添加括号。
调度场算法的判断运算符优先级时理解错误,但是运算符栈的输入错误,后续通过修改transferToPostfix(LinkedList《string》 list)中的if来修正。
逆波澜算法的多次运算问题,用于保存答案的String sum[i]数组和保存四则运算的StringBuilder sb 字符串没有置空导致答案出现混乱。
2. 关键代码
private static void transferToPostfix(LinkedList<String> list){
Iterator<String> it=list.iterator();
while (it.hasNext()) {
String s = it.next();
if (isOperator(s)) {
if (operators.isEmpty()) {
operators.push(s);
}
else {
//如果读入的操作符为非")"且优先级比栈顶元素的优先级高,则将操作符压入栈
if (priority(operators.peek())<priority(s)&&!s.equals(")")) {
operators.push(s);
}
//否则执行讲栈顶元素弹出后再将操作符入栈
else if(!s.equals(")")&&priority(operators.peek())>=priority(s)){
while (operators.size()!=0&&priority(operators.peek())>=priority(s)
&&!operators.peek().equals("(")) {
if (!operators.peek().equals("(")) {
String operator=operators.pop();
sb.append(operator).append(" ");
output.push(operator);
}
}
operators.push(s);
}
//如果读入的操作符是")",则弹出从栈顶开始第一个"("及其之前的所有操作符
else if (s.equals(")")) {
while (!operators.peek().equals("(")) {
String operator=operators.pop();
sb.append(operator).append(" ");
output.push(operator);
}
//弹出"("
operators.pop();
}
}
}
//读入的为非操作符
else {
sb.append(s).append(" ");
output.push(s);
}
}
//将字符链接成后缀表达式
if (!operators.isEmpty()) {
Iterator<String> iterator=operators.iterator();
while (iterator.hasNext()) {
String operator=iterator.next();
sb.append(operator).append(" ");
output.push(operator);
iterator.remove();
}
}
calculate();
//Collections.reverse(output);
}
private static void calculate(){
LinkedList<String> mList=new LinkedList<>();
String[] postStr=sb.toString().split(" ");
//逐个对字符进行判断
for (String s:postStr) {
if (isOperator(s)){
if (!mList.isEmpty()){
int num1=Integer.valueOf(mList.pop());
int num2=Integer.valueOf(mList.pop());
//如果除数等于0,则抛弃这道题
if (s.equals("/") && num1==0){
answer="wrong";
sb.setLength(0);
return;
}
//如果相除后有余数,则抛弃这道题
if (s.equals("/")&& (num2%num1!=0)){
answer="wrong";
sb.setLength(0);
return;
}
//如果相减后为负数,则抛弃这道题
if (s.equals("-")&& num2<=num1){
answer="wrong";
sb.setLength(0);
return;
}
int newNum=cal(num2,num1,s);
mList.push(String.valueOf(newNum));
}
}
else {
//数字则压入栈中
mList.push(s);
}
}
if (!mList.isEmpty()){
answer=mList.pop();
}
sb.setLength(0);
}
3. 代码规范
请给出本次实验使用的代码规范:
- 第一条:驼峰式命名风格
- 第二条:不允许任何魔法值(即未经预先定义的常量)直接出现在代码中。
- 第三条:大括号的使用约定。如果是大括号内为空,则简介地写成{}即可,不需要换行;如果是非空代码块则:左大括号前不换行。左大括号后换行。右大括号前换行。右大括号后还有 else 等代码则不换行;表示终止的右大括号后必须换行。
- 第四条:单行字符数限制不超过120个
- 第五条:没有必要增加若干空格来是耨一行的字符与上一行对应位置的字符对齐。
- 第六条:在一个switch块内,每一个case要么通过break/return等来终止,要么注释说明程序将继续执行到哪一个case为止;在一个switch块内,都必须包含一个default语句并且放在最后,即使空代码。
- 第七条:在 if/else/for/while/do 语句中必须使用大括号。
- 第八条:避免采用取反逻辑运算符。
五、测试
java MathExamLv2 -n 100 -grade 1 | 成功执行 | 成功执行 |
java MathExamLv2 -n 100 -grade 2 | 成功执行 | 成功执行 |
java MathExamLv2 -n 100 -grade 3 | 成功执行 | 成功执行 |
java MathExamLv2 -grade 1 -n 100 | 成功执行 | 成功执行 |
java MathExamLv2 -grade 2 -n 100 | 成功执行 | 成功执行 |
java MathExamLv2 -grade 3 -n 100 | 成功执行 | 成功执行 |
java MathExamLv2 -n 100 | 请按照 -n n -grade grade 或者 -grade grade -n n的方式输入4个参数! | 请按照 -n n -grade grade 或者 -grade grade -n n的方式输入4个参数! |
java MathExamLv2 -grade 100 | 请按照 -n n -grade grade 或者 -grade grade -n n的方式输入4个参数! | 请按照 -n n -grade grade 或者 -grade grade -n n的方式输入4个参数! |
java MathExamLv2 -grade 3 -n 1000000000000000000 | 输入的题目数量太多了! | 输入的题目数量太多了! |
java MathExamLv2 -grade 4 -n 100 | grade只能取1~3! | grade只能取1~3! |
六、总结
通过这一段时间的结对编程,我发现编程耗时最多的方面就是debug。在我得出设计思路,写成代码之后,由于个人的疏忽,输入的错误,以及设计思路的偏差,往往会让我的程序陷入无止尽的BUG海洋中,有些只是很简单的小BUG,但是自己却难以发现,导致消耗我大量的时间。但是由于身边有个领航员角色的存在,在编写代码时,一旦出现输入错误,就会有人及时的提醒。并且,在设计代码时,有个同伴可以一起讨论,融合两个人不同的见解和观点,我们可以得出简单高效的设计书思路。
而且结对编程可以很大程度上提高编程效率,而且两人轮流编程,不会太过疲惫。我现在十分乐于进行结对编程,这回极大的提高我编程的效率,是编程不再那么枯燥,debug之路也不会那么恐怖。