结对项目:四则运算题目的命令行程序

结对项目:四则运算题目的命令行程序

这个作业属于哪个课程 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 listSuffix(List list):求运算式转成的后缀表达式。
int letterPriority(Fraction symbol):获得运算符的优先级
Fraction calculate(List list):计算后缀表达式的结果。
Fraction calculateNum(Fraction num1, Fraction num2, String letter):计算四则基本运算的结果
Fraction getResult(List list):获得算式最后的计算结果。
ContrastService接口:
List<List> 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 listSuffix(List list) {
Stack letterStack = new Stack<>();
List 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 list) {
Stack 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.测试结果展示

全部测试通过

六、总结

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

posted @ 2020-10-12 21:55  瓦中意鲁  阅读(187)  评论(0)    收藏  举报