20175205 结对编程项目-四则运算 阶段总结
一、需求分析(描述自己对需求的理解,以及后续扩展的可能性)
- 实现一个命令行程序,要求:
- 自动生成小学四则运算题目(加,减,乘,除)
- 支持整数
- 支持多运算符(比如生成包含100个运算符的题目)
- 支持真分数
- 统计正确率
- 自动生成小学四则运算题目(加,减,乘,除)
- 扩展需求
- 文件:
- 处理生成题目并输出到文件
- 完成题目后从文件读入并判断
- 多语言支持:
简体中文
,繁體中文
,English
- 生成题目去重
- 文件:
二、设计思路
- 第一阶段:在没开始一起讨论之前
在还没坐在一起讨论方法之前,我自己大概想了一个框架,并给予实现,但是遇到了很多问题。
- 问题一:怎么生成随机数,和随机符号?
问题一的解决:java.lang.Math中包含了一个Math的类,其中的一个方法可用于生成随机数,而且需要注意用此函数返回的是double型,大于等于 0.0 且小于 1.0,因此若要生成随机符号,即要生成四个随机数,每一个随机数对应输出一个符号即可。
问题二:运行结果产生的随机数和符号竟然每次都是0
问题二的解决:出现问题之后我就用Debug调试,发现产生随机数的地方出现了问题,原代码是
int num = (int)Math.random()*100
,仔细一想,从左到右运算,先强转后*100,因此产生的随机数永远是0,解决方法就是加一个括号int num = (int)(Math.random()*100)
- 问题三:将String类型字符串s赋值为null后,将字符串与其他字符串拼接后得到结果出现了null字符串与其他字符连接的样式
String s = null;
s += "hello";
System.out.println(s);
-问题三的解决:先应用String.valueOf 得出s的value值,再通过StringBuilder拼接hello,因此将value与hello进行了拼接,参考博客
String s = null;
s = (new StringBuilder(String.valueOf(s))).append("hello").toString();
System.out.println(s);
- 问题四:不能实现多个运算符和括号的插入,及如何将中缀表达式转化为后缀表达式的代码实现
- 第二阶段:我带着对问题四的疑惑和结对伙伴一起讨论
- 在和小伙伴的讨论下,他给我了灵感,他告诉我既然我可以产生一个式子包含一个运算符,那么我把两个算式拼在一起不就完成了多运算符,我将这一想法付诸于实践,但是发现两个运算符之间的符号还没办法搞定,因此在我们的讨论下作出改进,可以随机产生某个数字以内的运算符号数choice,那么这个四则运算就一定包含2*choice+1的字符,用这一个思想,完成了随机产生多运算符的四则运算。
import java.util.Scanner;
public class Question { //产生问题
Question(){}
void Question(int n){
int num1,num2; //两个随机数
char c; //随机符号
int result;
int count=0; //记录共产生了几个符号
String s = "";
int choice;
Scanner in = new Scanner(System.in);
RandomNum num = new RandomNum();
RandomChar ch = new RandomChar();
for(int i=1;i<=n;i++){ //产生题目
System.out.println("题目"+i+":");
choice = (int)(Math.random()*5)+1;
for(int j=1; j<=choice+1; j++){ //产生随机数
s = s+num.RandomNum();
if(count<choice){
s = s+" "+ch.RandomChar()+" ";
count++;
}
}
System.out.print(s+" = ");
result = in.nextInt();
s = "";
count = 0;
}
System.out.println("完成"+n+"道题目");
}
}
- 完成了多个随机运算符,但是还不能实现括号的随机插入,这时候机智的小伙伴又给我提出了方案,
num1 op1 num2 op2 num3
只要满足op1是+或-而 op2满足*或÷
就可以随机的在num1和num2左右两边同时加括号
package MathTest;
import java.lang.*;
import java.util.Scanner;
public class Question { //产生问题
Question(){}
void Question(int n){
int num1,num2; //两个随机数
char c; //随机符号
int result;
int count=0; //记录共产生了几个符号
String s = "";
String str[] = new String[100];
String ZZ = "";
int choice;
int e=100; //多少范围以内的加减乘除
Scanner in = new Scanner(System.in);
RandomNum num = new RandomNum();
RandomChar ch = new RandomChar();
for(int i=1;i<=n;i++){ //产生题目
System.out.println("题目"+i+":");
choice = num.RandomNum(5)+1;
for(int j=1; j<=choice+1; j++){ //产生随机数
s = s+num.RandomNum(e);
if(count<choice){
s = s+" "+ch.RandomChar()+" ";
count++;
}
}
switch ((num.RandomNum(2))){
case 0:
System.out.print(s+" = ");
break;
case 1:
if(choice>1){
str = s.split(" "); //字符串转化为字符串数组
for(int k=1;k!=str.length-2;k=k+2){
if((str[k].equals("+")||str[k].equals("-"))&&(str[k+2].equals("*")||str[k+2].equals("÷"))){
for(int q=0;q<k-1;q++)
ZZ = ZZ+str[q];
ZZ =ZZ+ "(";
for(int p=k-1;p<k+2;p++)
ZZ = ZZ+str[p];
ZZ = ZZ+")";
for(int m=k+2;m<str.length;m++)
ZZ = ZZ+str[m];
}
}
if(ZZ=="")
System.out.print(s+" = ");
else
System.out.print(ZZ+" = ");
break;
}
else{
System.out.print(s+" = ");
break;
}
}
result = in.nextInt();
s = "";
str = null;
ZZ = "";
count = 0;
choice = 0;
}
System.out.println("完成"+n+"道题目");
}
}
- 计算式的问题解决后,我们就着手于如何让计算机算出正确答案,也就是如何将产生的中缀表达式转变成后缀表达式让计算机计算。根据上学期离散课上所学的逆波兰符号法,二叉树的应用这一部分知识,参考博客,总结出:中缀转后缀的方法
利用两个栈S1,S2:其中S1存放操作符,S2存放操作数
从左往右遍历中缀表达式,如果遇到数字,则放入S2中,如果遇到操作符,则放入S1中。在放操作符的时候有一定的规则,如果栈为空或栈顶元素为(,则直接压栈。如果是(,也直接压栈;如果栈顶元素为普通操作符,则比较优先级,如果待压栈的操作符比栈顶操作符优先级高,则直接压栈,否则将S1中的栈顶元素出栈,并压入S2中,再接着比较S1栈顶元素的优先级。如果遇到),则依次弹出S1栈顶的运算符,并压入S2,直到遇到左括号为止,此时将这一对括号丢弃。最后将S1中剩余的运算符依次弹出并压入S2,逆序输出S2(从栈底到栈顶)便得到了后缀表达式。(注意:等号的优先级最低,因为要到最后才进行赋值操作)
三、总结思路
- 首先解决怎么生成随机数,及随机运算符的问题
- 怎么产生多个随机运算符
- 括号怎么随机插入四则运算中
- 如何让计算机计算中缀表达式
四、实现过程中的关键代码解释
- 产生题目的代码
for (int j = 1; j <= choice + 1; j++) { //产生题目,一共应该2*choice+1个元素
s = s + num.RandomNum(e); //产生一个数字
if (count < choice) {
s = s + " " + ch.RandomChar() + " "; //在原基础上再产生字符
count++;
}
}
- 随机产生括号的代码
switch (num.RandomNum(2)) { //随机产生括号
case 0: //不带括号
key = com.stringToArithmetic(s);
System.out.print(s + " = ");
break;
case 1: //带括号
if (choice > 1) {
str = s.split(" "); //字符串转化为字符串数组
Stack<String> Z = new Stack<String>();
String temp = new String();
for (int k=0; k!=str.length-2; k++) { //遍历前length-2个元素
if ((str[k].equals("+") || str[k].equals("-"))) { //如果是加减
int b = k+2;
int a = k+1;
if (str[b].equals("*") || str[b].equals("÷")) { //下一个符号是乘除
temp = Z.pop();
Z.push("(");
Z.push(temp);
Z.push(str[k]);
Z.push(str[a]);
Z.push(")");
k++;
a = k;
b = k;
} else //下一个符号是加减
Z.push(str[k]);
}
else //如果是乘除数字就直接入栈
Z.push(str[k]);
}
for(int m=str.length-2;m<str.length;m++)
Z.push(str[m]);
String[] ZZ = new String[Z.size()];
int p = Z.size() - 1;
while (Z.size() != 0) { //将栈中的元素放到数组中
temp = Z.pop();
ZZ[p] = temp;
p--;
}
String t = new String();
t = "";
for (int q = 0; q < ZZ.length; q++)
t = t + ZZ[q] + " ";
key = com.stringToArithmetic(t);
System.out.print(t + " = ");
ZZ = null;
t = "";
break;
}
else{
key = com.stringToArithmetic(s);
System.out.print(s + " = ");
break;
}
}
- 将中缀表达式转换为后缀表达式
public static String infixToSuffix(String exp) {
Stack<String> s = new Stack<String>(); // 创建操作符堆栈
String suffix = ""; // 要输出的后缀表达式字符串
String str[] = exp.split(" ");
int length = str.length; // 输入的中缀表达式的长度
String temp="";
for (int i = 0; i < length; i++) { // 对该中缀表达式的每一个字符并进行判断
switch (str[i]) {
case " ":break; // 忽略空格
case "(":
s.push(str[i]); // 如果是左括号直接压入堆栈
break;
case "+":
case "-":
if(s.size() != 0){ // 碰到'+' '-',将栈中的所有运算符全部弹出去,直至碰到左括号为止,输出到队列中去
temp = s.pop();
if (temp.equals("(")) { // 将左括号放回堆栈,终止循环
s.push(temp);
s.push(str[i]);
break;
}
else{
s.push(str[i]);
suffix = suffix+temp+" ";
break;
}
}
else{
s.push(str[i]); // 说明是当前为第一次进入或者其他前面运算都有括号等情况导致栈已经为空,此时需要将符号进栈
break;
}
// 如果是乘号或者除号,则弹出所有序列,直到碰到加好、减号、左括号为止,最后将该操作符压入堆栈
case "*":
case "/":
if(s.size()!=0){
temp = s.pop();
if(temp.equals("+")||temp.equals("-")||temp.equals("(")){
s.push(temp);
s.push(str[i]);
break;
}
else{
s.push(str[i]);
suffix = suffix+temp+" ";
break;
}
}
else {
s.push(str[i]); //当前为第一次进入或者其他前面运算都有括号等情况导致栈已经为空,此时需要将符号进栈
break;
}
// 如果碰到的是右括号,则距离栈顶的第一个左括号上面的所有运算符弹出栈并抛弃左括号
case ")":
while (!s.isEmpty()) {
temp = s.pop();
if (temp.equals("(")) {
break;
} else {
suffix = suffix+temp+" ";
}
}
break;
// 默认情况,如果读取到的是数字,则直接送至输出序列
default:
suffix = suffix+str[i]+" ";
break;
}
}
// 如果堆栈不为空,则把剩余运算符一次弹出,送至输出序列
while (s.size() != 0) {
suffix = suffix+s.pop()+" ";
}
//
return suffix;
}
五、测试方法
六、运行过程截图
七、代码托管地址
八、对结对的小伙伴做出评价
第一次体验结对变成非常有意思,在编程过程中,我认为我扮演的角色就像《飞驰人生》的驾驶员,主要负责前进,踩油门,也就是编写代码;而小伙伴扮演的角色就是副驾驶,我的眼睛,为我指明方向,尤其在我瓶颈不知如何是好的时候,小伙伴就会提出新的思路,让我可以继续编写。
九、总结
在这次结对编程中,我遇到了非常多问题,都非常让人头疼,好多天晚上都是带着问题睡觉,第二天早上就早早起来改代码,努力想完成一份健全正确的代码,在这个过程中我体会到不一样的快乐;而且每遇到一个问题,我不得不查相关知识,纸上得来终觉浅,我将一些思想实现的过程学到了会多知识,比如String类字符数组不能直接改变下标,通过学习中缀转后缀的思想,将其代码实现。在编程过程中遇到各种问题,更加熟练的学会用Debug调试,找出错误并加以改正。这次编程非常有趣,准备着手下周的任务。
十、预估与实际
psp2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 预估耗时(分钟) | 实际耗时(分钟) |
• Estimate | • 估计这个任务需要多少时间 | 1000 | 1500 |
Development | 开发 | ||
• Analysis | • 需求分析(包括学习新技术) | 30 | 90 |
• Design Spec | • 生成设计文档 | 30 | 40 |
• Design Review | • 设计复审 | 60 | 60 |
• Coding Standard | • 代码规范 (为目前的开发制定合适的规范) | 60 | 60 |
• Design | • 具体设计 | 60 | 60 |
• Coding | • 具体编码 | 600 | 1000 |
• Code Review | • 代码复审 | 60 | 60 |
• Test | • 测试(自我测试,修改代码,提交修改) | 30 | 60 |
Reporting | 报告 | ||
• Test Repor | • 测试报告 | 20 | 20 |
• Size Measurement | • 计算工作量 | 10 | 10 |
• Postmortem & Process Improvement Plan | • 事后总结, 并提出过程改进计划 | 40 | 40 |
合计 | 1000 | 1500 |