结对项目
这个作业属于哪个课程 | 网工1934-软件工程 |
---|---|
这个作业要求在哪里 | 结对项目 |
这个作业的目标 | <学习如何结对做一个软件项目,了解项目各流程,学会如何与搭档合作进行编程、测试、性能分析等来完成一个初步软件,在合作过程中找到更加高效的工作方法,同时继续学习如何写好一个博客,熟悉掌握github的使用。> |
搭档
姓名:李晓兰 学号:3219005401
姓名:郭海燕 学号:3219005400
github
一、项目需求
题目:实现一个自动生成小学四则运算题目的命令行程序
描述如下:
(1)0使用 -n 参数控制生成题目的个数,例如
Myapp.exe -n 10 将生成10个题目。
(2)使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如
Myapp.exe -r 10 将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。
(3)生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2。
(4)生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数。
(5)每道题目中出现的运算符个数不超过3个。
(6)程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,23 + 45 = 和45 + 23 = 是重复的题目,6 × 8 = 和8 × 6 = 也是重复的题目。3+(2+1)和1+2+3这两个题目是重复的,由于+是左结合的,1+2+3等价于(1+2)+3,也就是3+(1+2),也就是3+(2+1)。但是1+2+3和3+2+1是不重复的两道题,因为1+2+3等价于(1+2)+3,而3+2+1等价于(3+2)+1,它们之间不能通过有限次交换变成同一个题目。
生成的题目存入执行程序的当前目录下的Exercises.txt文件,格式如下:
1.四则运算题目1
2.四则运算题目2
……
其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。
(7)在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:
1.答案1
2.答案2
特别的,真分数的运算如下例所示:1/6 + 1/8 = 7/24。
(8)程序应能支持一万道题目的生成。
(9)程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:
Myapp.exe -e
统计结果输出到文件Grade.txt,格式如下:
Correct: 5 (1, 3, 5, 7, 9)
Wrong: 5 (2, 4, 6, 8, 10)
其中“:”后面的数字5表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。
二、项目开发流程分析
(一)开发环境
1、编程语言:java
2、项目集成工具:Intellij IDEA 2019.3.4
3、项目框架工具:maven
4、单元测试工具:JUnit4
5、性能分析工具:JProfiler 12.0.3.0
(二)实现算法原理
(1)中缀表达式转换为后缀表达式
若将中缀表达式1 + 23 + (4 * 5 + 6) * 7转为后缀表达式,则其转换后的表达式为1 2 3 * + 4 5 * 6 + 7 * +,中间以空格作为间隔。
具体步骤规则如下:
(1) 从头扫描中缀表达式,若为数字,直接输出,不入栈
(2) 如果遇到左括号,其优先级最高,直接入栈,但只有遇到右括号才出栈
(3) 如果遇到“+”“-”符号,其优先级最低,不断将比自己高级的符号弹出栈并输出,直到遇到左括号,左括号不出栈
(4) 如果遇到“”“÷”符号,只需将同级符号弹出栈并输出,加减号优先级低,左括号又不能弹出,所以遇到这三者都直接入栈
(5) 如果遇到右括号,只需将栈顶依次弹出并输出,直到遇到左括号,将左括号弹出
(6) 最后,把栈中剩余的符号全部弹出栈并输出
(2)后缀表达式的计算
具体步骤概括为:遍历表达式,遇到操作数则将操作数入栈,若遇到符号,则将栈顶的两个数抛栈并进行运算,运算结果再入栈。
实例:1 2 3 * + 4 5 * 6 + 7 * +;
步骤:
(1)将1、2、3分别入栈,遇到“”,则计算12=2,将2入栈;
(2)遇到“+”,则取栈顶2和3,计算2+3=5,将5入栈;再将4、5分别入栈,此时从栈顶往下为5、4、5;
(3)接着遇到“”,取栈顶5、4,计算54=20,将20入栈;
(4)将6入栈,此时从栈顶往下为6、20、5;
(5)接着遇到“+”,取栈顶6、20,计算6+20=26,将26入栈;
(6)将7入栈,此时从栈顶往下为7、26、5;接着遇到“”,取栈顶7、26,计算726=182,将182入栈;
(7)接着遇到“+”,取栈顶182、5,计算182+5=187,将187入栈,则最终计算结果为187。
(三)整体流程图
(四)项目框架及主要类
主要maven框架
(五)项目模块核心算法
(1)文件读写模块fileIO类
BufferedReader br1 = new BufferedReader(new FileReader(file1));
BufferedReader br2 = new BufferedReader(new FileReader(file2));
int i = 1;
String str = null;
while((str = br1.readLine()) != null){
if(str.equals(br2.readLine())){
correct.add(Integer.toString(i));//比较两文件,相等则将正确的题目序号加入集合
}else{ //错误则将错误的题目序号加入集合
wrong.add(Integer.toString(i));
}
i++;
}
//将正确集合的元素写入文件
this.writeFile(Grade,"Correct:"+correct.size()+"(");
//将错误集合的元素写入文件
this.writeFile(Grade,"Wrong:"+wrong.size()+"(");
(2)生成中缀表达式equationCreate类
随机生成数字、运算符、括号
public String numberCreate(int m){}
public String symbolCreate(){}
public boolean bracket(){}
对分数进行约分化简
public String Simplify(int a,int b){
String s = "";
int commonNum = 1 ,c;//公约数
c = a/b;
a = a % b;
if(c < 0){
a = a*(-1);//当c小于0时,有一个负号了,将余数的负号删去
}
for(int i =1;i <= a;i++){
if(a % i==0 && b % i ==0){
commonNum = i; //求出最大公约数
}
}
a = a / commonNum;
b = b / commonNum; //化简
if(a == 0){
s = Integer.toString(c);
}else if(c == 0){
s = Integer.toString(a)+"/"+Integer.toString(b);
}else{
s = Integer.toString(c)+"'"+Integer.toString(a)+"/"+Integer.toString(b);
}
return s;
}
(3)转化为后缀表达式beSuffix类
中缀转后缀
for (int i = 0; i < length; i++) {
char e ;
char ch = equ.charAt(i);
switch (ch){
case '(': //左括号优先级最高,直接入栈,但只有遇到有括号才出栈
s.push(ch);
break;
case '+':
case '-':
newStr += " ";
while(s.size() != 0){
e = s.pop();
if(e == '('){ //优先级最低,不断将把自己高级的弹出栈并输出,知道遇到左括号,左括号不出栈
s.push('(');//前面弹出栈,现在又压入栈
break;
}else{
newStr += e;
newStr += " ";}
}
s.push(ch);
break;
.........//乘除及右括号情况省略,具体查看源码
while(s.size() != 0){ //把栈中剩余的符号全部输出
newStr += " ";
newStr += s.pop();
}
return newStr;
计算后缀表达式
for (int i = 0; i < strings.length ; i++) {
if(strings[i].equals("+")||strings[i].equals("-")||strings[i].equals("*")||strings[i].equals("÷")){
String x2 = stack.pop();
String x1 = stack.pop();
String str = myCalculation(x1,x2,strings[i]);
stack.push(str);//把计算结果压入栈作为新的操作数
if(str.equals("无解")){
return str;
}
}else{
stack.push(strings[i]);//若为数字,直接入栈
}
(4)查重checkDuplicate类
找出答案相同的
for (int i = 0; i < answer.size()-1 ; i++) {
String str = "";
for (int j = i+1 ; j< answer.size();j++){
if(answer.get(i).equals(answer.get(j))){
if(checkEquation(equation.get(i),equation.get(j))){
str += (i+1)+","+equation.get(i)+"repeat"+(j+1)+","+equation.get(j)+" ";
}
}
}
if(str.length()>0){
repeat.add(str);
}
}
再比较表达式
for (int i = 0; i < strings.length ; i++) {
if(!equ2.contains(strings[i])){
return false;
//只查看是否出现此数字或符号,有局限性,但前面答案相同,表达式出现的每个数字或符号都有的概率比较小
}
}
(5)主模块mainCreate类
通过手动输入运算式数字的范围以及生成运算式的个数,生成Exercises.txt文件和Answers.txt文件,调用算法,进行查重和答案比对,将测试结果写入Grade.txt文件。
(6)运行结果
三、项目性能分析
概览
实时内存
由上图可知,运行时调用java.lang.String包的次数较多,主要是核心算法的实现,运算式的生成及转换,都是以字符串形式进行,符合性能分析标准。
四、单元测试
部分模块单元测试代码
(一)主模块测试mainTest类
进行10000道运算式的测试,通过静态输入参数,生成Exercises(1).txt文件和Answers(1).txt文件
部分测试代码
File f1 = new File("Exercises(1).txt");
File f2 = new File("Answers(1).txt");
int n=10000;
int m = 50;
for (int i = 0; i < n; i++) {
String str = equC.equations(n, m);//生成中缀表达式
String newStr;
String answer = suffix.calculateSuffix(suffix.becomeSuffix(str));
//转为后缀表达式并求解
newStr = i + 1 + ":" + str + "\r\n";//将题目运算式写入文件
fIO.writeFile(f1, newStr);
newStr = i + 1 + ":" + answer + "\r\n";//将答案写入文件
fIO.writeFile(f2, newStr);
}
测试说明:主要是设置运算式的个数为10000,数字范围为50,进行10000道运算式的测试,生成Exercises(1).txt文件和Answers(1).txt文件。
测试点
测试结果
(二)读取文件测试filesIOTest类
部分测试代码
@Test
public void readFileTest(){
String s = filesIO.readFile("D:\\Test2\\orig.txt");
String[] strings = s.split(" ");
for(String str : strings){
System.out.println(str);
}//测试路径正常,读取
}
测试说明:主要是测试文件读取功能,是否能正常读取文件路径,将内容转化为字符串,此处以原始文件路径作为测试样例。
测试点
(三)beSuffix测试beSuffixTest类
部分测试代码
@Test
public void becomeSuffixTest(){
beSuffix bs = new beSuffix();
String str = "5+(1*7)";
String newStr = bs.becomeSuffix(str);
System.out.println(newStr);
}
测试说明:这部分主要是测试后缀表达式的生成
@Test
public void calculateSuffixTest(){
beSuffix bs = new beSuffix();
String str = "517*+";
String end = bs.calculateSuffix(str);
System.out.println(end);
}
测试说明:这部分主要是测试计算后缀表达式的结果
测试点
覆盖率
(四)查重测试类checkDuplicateTest类
部分测试代码
@Test
public void checkEquationTest(){
checkDuplicate cd = new checkDuplicate();
String equ1 = "1+(2+3)";
String equ2 = "1+2+3";
boolean tag = cd.checkEquation(equ1,equ2);
System.out.println(tag);
}
测试说明:主要是测试两个计算式是否相同,排除括号及交换律、结合律的影响。
测试点
测试结果
(五)运算式生成测试类equationCreateTest类
部分测试代码
@Test
public void equationsTest(){
equationCreate ec = new equationCreate();
int m = 10;
int n=10;
String s1 = ec.equations(n,m);
String s2 = ec.equations(n,m);
String s3 = ec.equations(n,m);
System.out.println(s1);
System.out.println(s2);
System.out.println(s3);
}
测试说明:主要是测试随机生成3个运算式,进行单元模块的测试。
测试点
覆盖率
五、项目小结
李晓兰:在这次的结对项目中,我有了很多新的收获。我和队友采用的方式是一起进行编程,每个人编写一部分代码,另一个人在旁边进行代码审查,提供思路和可以优化的地方。通过这次的项目,我认为我们的编程能力都有所提高,也学习了一些新的算法。在合作的过程中,我们遇到了很多问题,比如代码中的一些bug,以及在向github提交时因为新分支无法推送等,有一些一个人发现不了的问题,另一个人就可以发现,这样也省去了很多代码审查的时间。当遇到问题时,我们可以一起想办法解决,虽然会有一些分歧,但是同时工作确实比自己一个人摸索的结果要好得多,也可以达到互补的效果。
郭海燕:此次结对的任务中,我体会到了团队合作时多和队友沟通对完成任务有非常大的帮助,使之后的工作对接很顺利。队友非常厉害,我不是很会写代码,所以她负责了比较多代码部分,甚至在我写代码过程中产生了一些问题,队友都会很耐心地帮我解决,自己在开发上还有很多要向队友学习的。在项目提交的时候再一次出现做个人项目时遇到的问题,都是git push失败,原因都是不能在新的分支上提交项目,这使我对此类问题更加了解和学到更多git和github的使用。
六、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 60 |
Estimate | 估计这个任务需要多少时间 | 50 | 50 |
Development | 开发 | 970 | 1080 |
Analysis | 需求分析 (包括学习新技术) | 200 | 250 |
Design Spec | 生成设计文档 | 60 | 65 |
Design Review | 设计复审 | 30 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 30 | 30 |
Design | 具体设计 | 70 | 85 |
Coding | 具体编码 | 280 | 310 |
Code Review | 代码复审 | 50 | 60 |
Test | 测试(自我测试,修改代码,提交修改) | 250 | 250 |
Reporting | 报告 | 130 | 135 |
Test Repor | 测试报告 | 60 | 60 |
Size Measurement | 计算工作量 | 10 | 15 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 60 | 60 |
合计 | 1160 | 1275 |