小学生究极进化---四则混合运算
MathExam—— 在原有的V2.0.0版本上加入三年级四则混合运算题
- 211606335 吴沂章
- 211606318 林锃寒
一、预估与实际
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
? Estimate | ? 估计这个任务需要多少时间 | 5 | 3 |
Development | 开发 | ||
? Analysis | ? 需求分析 (包括学习新技术) | 90 | 720 |
? Design Spec | ? 生成设计文档 | 20 | 20 |
? Design Review | ? 设计复审 | 10 | 30 |
? Coding Standard | ? 代码规范 (为目前的开发制定合适的规范) | 30 | 10 |
? Design | ? 具体设计 | 30 | 20 |
? Coding | ? 具体编码 | 360 | 600 |
? Code Review | ? 代码复审 | 20 | 300 |
? Test | ? 测试(自我测试,修改代码,提交修改) | 50 | 20 |
Reporting | 报告 | ||
? Test Repor | ? 测试报告 | 30 | 20 |
? Size Measurement | ? 计算工作量 | 30 | 20 |
? Postmortem & Process Improvement Plan | ? 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 1793 |
二、需求分析
-
特点1
- 运算符在2~4个
-
特点2
- 减法运算的结果不能有负数
-
特点3
- 除法运算除数不能为0,不能有余数
-
特点4
- 可以加括号
经过分析,我认为,这个程序应当:
-
一个式子中应要添加多个运算符
-
计算符号判断,生成随机数
-
通过调度场和逆波澜算法来实现运算
三、设计
1. 设计思路
-
第一步实现四则运算题目的生成
-
第二步实现字符串写入文本
-
第三步实现从命令行接受参数并传入程序运行
-
第四步实现 中缀表达式 ------转换------> 后缀表达式
-
第五步实现对后缀表达式的计算方法
-
最后修改若干Bug,规范代码名,完善代码,提升质量
-
...
- 代码类图
- 逆波兰函数流程图:
2. 实现方案
-
准备工作:先在Github上创建仓库,克隆到本地,创建一个Pair文件夹
-
在MyEclipse中创建一个类,包含主方法和各函数
-
准备工作:先在Github上创建仓库,克隆到本地。
-
技术关键点:
-
如何令用户在输入 -n n -grade grade 和 -grade grade -n n 的时候都可以成功运行。
-
如何在运算方法中实现"()"的优先运算
-
如何在四则运算中添加括号。
-
如何实现逆波澜算法
-
如何记录错题。
-
...
-
四、编码
- 本次代码未实现以下功能:
- 除数为0的错误
- 差值为负值的错误
- 四则运算题目的数值的范围为[0,10),不能是2位数
1. 调试日志
- 日志一:
- 没有考虑定义的运算符乘号小写字母"x"在入栈时被判定为符合要求的字符,导致判定出错
private static boolean isOperator(String operator){
if (operator.equals("+")||operator.equals("-")||operator.equals("×")||operator.equals("÷")||operator.equals("(")||operator.equals(")")) {
return true;
}
return false;
}
- 解决方案:将所有乘法中的符号统一定义为输入法中的“×”
无对应的解决方案代码
- 日志二:
- 生成三年级题目时调用i值导致抛异常为空值
for (int j = 0; j < count; j++) {
....省略部分代码
str_ArithmeticProblem[i] = "( " +n1 + " " + cs[c1] + " " + n2 +" ) " + " " + cs[c2] + " " + n3;
....省略部分代码
}
- 解决方案:将对应的循环值j放入str_ArithmeticProblem[j]中
for (int j = 0; j < count; j++) {
....省略部分代码
str_ArithmeticProblem[j] = "( " +n1 + " " + cs[c1] + " " + n2 +" ) " + " " + cs[c2] + " " + n3;
....省略部分代码
}
2. 关键代码
-
调度场算法
-
逆波兰函数
//调度场算法——[中缀表达式转后缀表达式]
private void toPostfixExpression(String str_mix){
int len = str_mix.length();
char c,nextChar;
String sc;
for (int i = 0 ; i <= len-1 ; i++) {
c = str_mix.charAt(i);
sc = String.valueOf(c);
if(isOperator(sc)) //判断是否是操作符
{
if(operators.isEmpty()){ //判断为空栈,入栈
operators.push(sc);
} else {
if(priority(operators.peek()) < priority(sc) && !sc.equals(")")){
//栈顶操作符优先级小于当前操作符优先级且操作符不为右括号,入栈
operators.push(sc);
} else if(priority(operators.peek()) >= priority(sc) && !sc.equals(")")){
while(!operators.empty() && !operators.peek().equals("(") //栈不为空,当前栈顶操作符不为左括号
&& priority(operators.peek()) >= priority(sc)){ //操作符优先级小于等于当前栈顶操作符优先级
do {
operator_Add = operators.pop();
postfixExpression.append(operator_Add);
operand.push(operator_Add);
} while (false); } // 栈顶操作符是左括号时停止压栈
operators.push(sc); //否则直接入栈
} else if(sc.equals(")")){ //当前扫描到的操作符为右括号(不做入栈操作),依次压栈相匹配的左括号内容
do {
operator_Add = operators.pop();
postfixExpression.append(operator_Add);
operand.push(operator_Add);
} while (!operators.peek().equals("("));
operators.pop(); //弹出栈顶无用操作符左括号
}
}
}else { //非操作符
if(!sc.equals(" ")){
postfixExpression.append(sc);
operand.push(sc);
}
}
}
while(!operators.empty()){ //结束字符串扫描后操作符的栈不为空则则压栈
operator_Add = operators.pop();
postfixExpression.append(operator_Add);
operand.push(operator_Add);
}
}
//逆波兰函数
private int reversePolish() {
// TODO Auto-generated method stub
char c;
int len = postfixExpression.toString().length();
for (int i = 0; i < len; i++) {
c = postfixExpression.charAt(i);
if(!isOperator(String.valueOf(c))){ //判断非操作符,入栈
postfixNumber.push(Integer.parseInt(String.valueOf(c)));
} else{
int m = postfixNumber.pop();
int n = postfixNumber.pop();
String operator = String.valueOf(c);
postfixNumber.push(Calculation(n, m, operator));
}
}
return postfixNumber.pop();
}
3. 代码规范
本次实验使用的代码规范:
-
类名使用 UpperCamelCase 风格
-
方法名、参数名、成员变量、局部变量都统一使用 lowerCamelCase 风格,遵循驼峰形式
-
变量和常量的命名方式:
- 非公有(private/protected/default)变量前面要加上小写m
- 静态变量(static)前面加上小写s
- 其它变量以小写字母开头
- 静态常量(static final)全大写
-
类型与中括号紧挨相连来定义数组
-
大括号的使用约定。如大括号内为空,则简介地写成{}即可,不需要换行;如果是非空代码块则:
- 左大括号前不换行
- 左大括号后换行
- 右大括号前换行
- 右大括号后还有 else 等代码则不换行;表示终止的右大括号后必须换行
-
左小括号和字符之间不出现空格;同样的,有小括号和字符之间也不出现空格
4. 结对编程的过程
-
我们采取的是Ping-Pong式的结对方式
-
最初先是一起讨论这次项目的编码流程,研究用何种方式实现效果,并模拟程序运行得出这次的代码难点有哪些
-
然后针对代码需要实现的功能进行分工,确立两个人的任务
-
编程过程中互相交换意见,同步进程
-
最后融合代码,并对代码进行复审
五、测试
序号 | 测试代码 | 预期输出结果 | 实际输出结果 |
---|---|---|---|
1 | java MathExam -n 10 -grade 1 | 输出10道一年级算术题 | 和预期结果相符 |
2 | java MathExam -n 10 -grade 2 | 输出10道二年级算术题 | 和预期结果相符 |
3 | java MathExam -n 10 -grade 3 | 输出10道三年级算术题 | 和预期结果相符 |
4 | java MathExam -grade 3 -n 10 | 输出10道三年级算术题 | 和预期结果相符 |
5 | java MathExam -grade 2 -n 10 | 输出10道二年级算术题 | 和预期结果相符 |
6 | java MathExam -grade 1 -n 10 | 输出10道一年级算术题 | 和预期结果相符 |
7 | java MathExam -g 3 -n 10 | 不符合参数类型输入规范,结束程序 | 和预期结果相符 |
8 | java MathExam -grade 3 -c 10 | 不符合参数类型输入规范,结束程序 | 和预期结果相符 |
9 | java MathExam 10 -grade 1 | 不符合参数类型输入规范,结束程序 | 和预期结果相符 |
10 | java MathExam -g 10 -c 1 | 不符合参数类型输入规范,结束程序 | 和预期结果相符 |
六、总结
- 接到项目一定要对其进行编码设计,为接下来的流畅度打好基础
- 随着技术难点的增加,代码复杂度也随之升高,注释必不可少
- 先对本次项目进行需求分析,避免重复修改代码时产生不必要的bug
- 结对编程的过程中,必须要做到分工明确,对代码要达成统一的意见,而不是各写各的
- 结对编程中两人的节奏需保持一致,否则一个人写完了自己的任务,开始玩起了游戏,另一个便会浮躁不安
- 每次版本更新后都尽量提交到GitHub进行托管,比如说这次遇到了对代码更改后无法恢复原点,就可以借助GitHub的托管进行恢复