结对项目:四则运算题目的命令行程序
结对项目:四则运算题目的命令行程序
| 这个作业属于哪个课程 | https://edu.cnblogs.com/campus/gdgy/Networkengineering1834 |
|---|---|
| 这个作业要求在哪里 | https://edu.cnblogs.com/campus/gdgy/Networkengineering1834/homework/11148 |
| 这个作业的目标 | 实现一个自动生成的小学四则运算题目的命令行程序 |
一、项目详情
GitHub项目地址:https://github.com/MANGLULU/pairing-project.git
结对成员:邓发连(3118005321) 方泽凯(3118005322)
二、PSP 表格
| PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
|---|---|---|---|
| Planning | 计划 | 30 | 60 |
| · Estimate | · 估计这个任务需要多少时间 | 10 | 10 |
| Development | 开发 | 120 | 180 |
| · Analysis | · 需求分析 (包括学习新技术) | 100 | 80 |
| · Design Spec | · 生成设计文档 | 30 | 30 |
| · Design Review | · 设计复审 | 30 | 30 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 60 | 60 |
| · Design | · 具体设计 | 60 | 60 |
| · Coding | · 具体编码 | 200 | 360 |
| · Code Review | · 代码复审 | 60 | 60 |
| · Test | · 测试(自我测试,修改代码,提交修改) | 100 | 180 |
| Reporting | 报告 | 60 | 90 |
| · Test Report | · 测试报告 | 20 | 20 |
| · Size Measurement | · 计算工作量 | 20 | 20 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 30 |
| · 合计 | 990 | 1270 |
三、接口的设计与实现过程
1.算法原理
逆波兰表达式:
1、初始化两个栈:运算符栈s1和储存中间结果的栈s2
2、从左到右扫描中缀表达式;
3、遇到操作数,将其压s2;
4、遇到运算符时,比较其与s1栈顶运算符优先级:
(1)、如果s1为空,或者栈顶运算符为左括号“(”,则直接将此运算符入栈;
(2)、否则,若优先级比栈比栈顶运算符的高,也将运算符压放s1;
(3)、否则,将s1栈顶的运算符弹出并压入到s2,再次转到4.1与s1中新的栈顶运算符想比较;
5、遇到括号时:
(1)、如果是左括号“(”,则直接压入s1。
(2)、如果是右括号“)”,则依次弹出s1栈顶的运算符,并压入s2,直到遇到左括号为止,此时将这一对括号丢弃;
6、重复步骤2至5,直到表达式的最右边;
7、将s1中剩余的运算符依次弹出并压入s2;
8、依次弹出s2中的元素并输出,结果的逆序即为中缀表达式对应的后缀表达式;
2.接口设计
FileFunction类:文件写入读出
void saveFile(String path, StringBuilder context):将context写入路径为path的文件。
Map<Integer, String> readFile(String path):将path的文件读出储存到Map集合。
Calculate接口:计算四则运算的结果
List<Fraction> listSuffix(List<Fraction> list):求运算式转成的后缀表达式。
int letterPriority(Fraction symbol):获得运算符的优先级
Fraction calculate(List<Fraction> list):计算后缀表达式的结果。
Fraction calculateNum(Fraction num1, Fraction num2, String letter):计算四则基本运算的结果
Fraction getResult(List<Fraction> list):获得算式最后的计算结果。
ContrastService接口:
List<List<Integer>> getTitle(Map<Integer, String> map1, Map<Integer, String> map2):获得答对和答错题目的题号。
void writeResult(String answerFilePath, String actualFilePath):将检查后的结果写入批改文件。
DigitalProcessingService接口:
int getCommonMeasure(int element, int denominator):求最大公约数。
String getProperFraction(Fraction fraction):化简分数。
Fraction getNumber(int number):获得随机数。
String getSymbol():获得随机符号。
Fraction类:
Fraction ADD(Fraction number1, Fraction number2):加法。
Fraction Subtract(Fraction number1, Fraction number2):减法。
Fraction Multiply(Fraction number1, Fraction number2):乘法。
Fraction Division(Fraction number1, Fraction number2):除法。
QuestionsController类:
void getQuestions(int count, int number):调用相关方法生成符合要求的四则运算表达式。
3.程序流程
逆波兰表达式流程图:

项目流程图:

4.项目结构:

4.关键代码分析
定义实体类:将所有数值用分数形式表示,再通过化简成整数,分数,真分数。
/**
* @Author DengFaLian
* @Date 2020/10/2 22:33
* @Version 1.0
*/
public class Fraction {
/*分子*/
private int element;
/*分母*/
private int denominator;
/*符号*/
private String symbol;
/*getter and setter*/
/**
* 加法
* @param number1
* @param number2
* @return
*/
public Fraction ADD(Fraction number1, Fraction number2) {
Fraction answer = new Fraction();
answer.setElement(number1.getElement() *
number2.getDenominator() + number1.getDenominator() *
number2.getElement());
answer.setDenominator(number1.getDenominator() *
number2.getDenominator());
return answer;
}
/**
* 减法
* @param number1
* @param number2
* @return
*/
public Fraction Subtract(Fraction number1, Fraction number2)
{
Fraction answer = new Fraction();
if ((number1.getElement() * number2.getDenominator() - number1.getDenominator() * number2.getElement()) < 0)
throw new RuntimeException("运算过程中不能有负数");//如果运算结果是负数,抛出异常。
answer.setElement(number1.getElement() * number2.getDenominator() - number1.getDenominator() * number2.getElement());
answer.setDenominator(number1.getDenominator() * number2.getDenominator());
return answer;
}
/**
* 乘法
* @param number1
* @param number2
* @return
*/
public Fraction Multiply(Fraction number1, Fraction number2)
{
Fraction answer = new Fraction();
answer.setElement(number1.getElement() * number2.getElement());
answer.setDenominator(number1.getDenominator() * number2.getDenominator());
return answer;
}
/**
* 除法
* @param number1
* @param number2
* @return
*/
public Fraction Division(Fraction number1, Fraction number2)
{
Fraction answer = new Fraction();
if (number2.getElement() == 0) throw new RuntimeException("除数不能为0");
answer.setElement(number1.getElement() * number2.getDenominator());
answer.setDenominator(number1.getDenominator() * number2.getElement());
return answer;
}
获得逆波兰表达式
@Override
public List<Fraction> listSuffix(List<Fraction> list) {
Stack<Fraction> letterStack = new Stack<>();
List<Fraction> result = new ArrayList<>();
for (Fraction str:list){
/*操作数*/
if(str.getSymbol() == null){
result.add(str); //加入逆波兰表达式集合
continue;
}
/*左括号*/
else if(str.getSymbol().equals("(")){
letterStack.push(str); //入栈
continue;
}
/*右括号*/
else if(str.getSymbol().equals(")")){
while(!letterStack.peek().getSymbol().equals("(")){
result.add(letterStack.pop()); //出栈到逆波兰表达式集合
}
letterStack.pop(); //“(”出栈
continue;
}
/*运算符*/
else if(letterStack.isEmpty() || letterStack.peek().getSymbol().equals("(")){
letterStack.push(str); //入栈
continue;
}
else if(letterPriority(str) > letterPriority(letterStack.peek())){
letterStack.push(str); //优先级高的入栈
}
else{
while(!letterStack.isEmpty() && !letterStack.peek().getSymbol().equals("(") && letterPriority(str)<=letterPriority(letterStack.peek())){
result.add(letterStack.pop()); //把栈中优先级低的元素出栈到逆波兰表达式
}
letterStack.push(str);//优先级高的入栈
}
}
while (!letterStack.isEmpty()){
result.add(letterStack.pop()); //栈中剩余的元素出栈,加入到逆波兰表达式
}
return result;
}
计算逆波兰表达式的结果:
public Fraction calculate(List<Fraction> list) {
Stack<Fraction> calculateStack = new Stack<>();
Fraction num1 = null;
Fraction num2 = null;
for (Fraction str:list){ //遍历逆波兰表达式
if(str.getSymbol() == null){
calculateStack.push(str); //将数值入栈
}else{
num1 = calculateStack.pop(); //有运算符则出栈两个数值
num2 = calculateStack.pop();
calculateStack.push(calculateNum(num2,num1,str.getSymbol()));//进行运算,并且将结果重新入栈
}
}
return calculateStack.pop();//返回最后结果
}
四、性能分析
.类的内存消耗:从图中可以看出,程序所消耗的内存比较小,程序对内存的要求不高。

.CUP Load

.堆内存:从图中可以看到项目中char和string类型使用的比较多,其中char类型主要用于运算符号的判断和括号的判断,String类型运用与整个程序中,所以占比较大。

.程序中消耗最大的方法:从图中可以看出,程序中消耗最大的方法是getQuestions()方法,,因为大多数的方法都是为生成题目所用。

五、计算模块部分单元测试展示
1.测试代码
测试类结构:

测试方法:






2.生成1w道题目的测试结果

3.部分结果展示
题目:Exercise.txt :题目生成后写入Exercise.txt文件

答案:Answer.txt :题目生成的同时生成答案写入Answer.txt文件。

答题:ExerciseFile.txt :生成题目后,根据题目写出答案写入ExerciseFile.txt文件,方法当前目录下

批改结果:Grade.txt :输入批改命令后生成结果写入Grade.txt文件

4.测试结果展示
全部测试通过

六、总结
邓发连:
这次的结对项目总体来说是比较成功的,首先为了方便,我们先通过沟通选定了实现的方法,通过讨论先把接口写好,每个接口对应相应的功能。然后我写方法的具体实现,方泽凯写方法的测试。测试后的结果反馈给我修改代码。结对编程没有个人编程那么方便,想写什么就写什么,但是它可以让自己认识到自身的不足,一个人的想法还是有限的。这次的结对项目让我学会了如何和别人高效的合作,怎样才能提高合作开发的效率,同时也认识到了结对编程的好处。
方泽凯:
这是我第一次接受结对编程,受益良多。在结队编程时,邓发连同学将需求列出,根据难度不同从而安排开发流程,然后我们一起讨论,有时候自己卡住的时候,别人的建议会让自己思维变得更加清晰,在写代码的过程中,两个人的思维碰撞会产生意外的结果,有一些细节的地方自己没有办法想到,就可以由另外一个人填补,两个人提高了程序的设计与编写的速度。最大的收获是提高了沟通能力,简化开发流程,提高效率。

浙公网安备 33010602011771号