文章目录
一.简介
个人信息
姓名 | 学号 | gitCode地址 |
---|---|---|
黄翊森 | 3121005168 | GitCode黄翊森 |
王宗奎 | 3121005183 | GitCode王宗奎 |
作业
这个作业属于哪个课程 | 21级软件工程 |
---|---|
这个作业要求在哪里 | 结对编程:小学四则运算 |
这个作业的目标 | 实现一个自动生成小学四则运算题目的命令行程序 |
其他参考文献 | CSDN |
二.PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 60 |
· Estimate | · 估计这个任务需要多少时间 | 1200 | 1200 |
Development | 开发 | 800 | 800 |
· Analysis | · 需求分析 (包括学习新技术) | 30 | 25 |
· Design Spec | · 生成设计文档 | 20 | 15 |
· Design Review | · 设计复审 | 15 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 10 |
· Design | · 具体设计 | 240 | 300 |
· Coding | · 具体编码 | 120 | 150 |
· Code Review | · 代码复审 | 40 | 20 |
· Test | · 测试(自我测试,修改代码,提交修改) | 20 | 15 |
Reporting | 报告 | 50 | 50 |
· Test Report | · 测试报告 | 20 | 20 |
· Size Measurement | · 计算工作量 | 10 | 10 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 20 | 20 |
· 合计 | 960 | 885 |
三.接口的设计与实现过程
3.1系统主体设计
- 程序分为两个模块,主实现和工具包。
- 主实现内包括出题模块和判断正确与否的模块。
- 工具包内包括分词工具,以及文件处理工具。
3.2各个模块设计
3.2.1 操作数类
-
Operator类。
-
操作数类是该程序的主体类。
-
有两个主要的属性,分子以及分母。
-
操作数类中有个8个方法实现:
1.求该操作数分子分母的最大公因数
2.将分子除以最大公约数,得到最简真分数的分子
3.将分母除以最大公约数,得到最简真分数的分母
4.运算操作。包括加减乘除(共4个方法)。
5.重写toString方法。
3.2.2 计算类
- Calculate类。
- 计算类包括三个方法:
1.将传入的中缀式的题目转换成后缀式。
2.将后缀式题目进行分词,对分词后的存有操作数和操作符的栈进行出入栈处理,调用计算方法。
3.计算的实现(返回float类型)。
3.2.3 出题类
- 出题类在用户输入生成题目的数量后,开始生成题目,并对题目进行一些逻辑处理。
3.2.4 判断类
- 判断类会根据用户输入的答案和正确答案,进行比对,将结果写入成绩文件。
3.2.5 分词类
- HandleFractionUtils类。
- 将题目分词,分别将操作数、操作符放入栈中。
3.2.6 文件操作类
- FileUtils类。
- 将之前已经生成过了的题目和答案清除掉,防止影响后续测试结果。
四.接口部分的性能改进
4.1 性能分析图
出题模块
- 以20道题目为例:
判断正误模块
4.2 性能改进
- 完成地比较仓促,没有怎么对性能进行改进。
五.代码说明
5.1 生成中缀表达式
- 中缀表达式,即日常所见的四则运算表达式。
- 为了测试方便,将数值输入范围限定到0~9之间,并随机输入,总题目数和分数题目的个数也随机。
Random random = new Random();
Operator operator = new Operator();
HandleFractionUtils fractionUtils = new HandleFractionUtils();
Scanner scanner = new Scanner(System.in);
OperatorUtils operatorUtils = new OperatorUtils();
int i,j;
//操作符数量。
int operationCount;
//操作符。
char operation;
int isUsedFraction;
int number1, tempNumber;
int number = 0;
FileUtils.clearFile("QuestionsAnswers.txt");
FileUtils.clearFile("Questions.txt");
System.out.println("输入生成的题目的数量:");
int questionsCount = scanner.nextInt();
outer:
for (i = 1; i <= questionsCount; i++) {
StringBuilder questionBuilder = new StringBuilder();
isUsedFraction = random.nextInt(2) + 1;
operationCount = random.nextInt(3) + 1;
if (isUsedFraction % 2 == 0) {
number1 = random.nextInt(10);
questionBuilder.append(number1);
for (j = 1; j <= operationCount; j++) {
//获得随机的运算符
operation = operatorUtils.makeOperator();
questionBuilder.append(operation);
tempNumber = random.nextInt(10);
if (operation == '÷' && tempNumber == 0) {
tempNumber = random.nextInt(9) + 1;
}
if ((j == 1 && operation == '÷' && number1 % tempNumber != 0) || (operation == '÷' && number % tempNumber != 0)) {
i--;
continue outer;
}
number = tempNumber;
questionBuilder.append(number);
//排除负数的情况
if (Calculate.calculateWithEnding(Calculate.moveIntOperation(questionBuilder.toString())) < 0) {
i--;
continue outer;
}
}
}
//若有余数,则取分数。
else {
for (j = 1; j <= operationCount + 1; j++) {
//分母为4到8的随机数
operator.setDenominator(random.nextInt(5) + 4);
//分子为1到3的随机数
operator.setNumerator(random.nextInt(3) + 1);
/*operator.handleNumerator(operator);
operator.handleDenominator(operator);*/
int num1 = operator.getNumerator();
int num2 = operator.getDenominator();
if (j <= operationCount) {
//生成运算符
operation = operatorUtils.makeOperator();
//字符串拼接,生产题目
questionBuilder.append(num1).append('/').append(num2).append(" ").append(operation).append(" ");
} else {
questionBuilder.append(num1).append('/').append(num2);
String tempStr = fractionUtils.toHandle(Calculate.moveFractionOperator(questionBuilder.toString()));
if (tempStr == null) {
i--;
continue outer;
} else if (tempStr.charAt(0) == '-') {
i--;
continue outer;
}
}
}
}
5.2计算答案
- 计算答案的代码。
/**
* 用后缀式的题目进行计算。
*
* @param endingQuestion 以后缀式表达的题目
* @return 返回结果
*/
public static float calculateWithEnding(String endingQuestion) {
Stack<Float> endingStack = new Stack<>();
char[] endingArr = endingQuestion.toCharArray();
//如果是操作数,则推到堆栈
for (Character ch : endingArr) {
if (ch >= '0' && ch <= '9') endingStack.push((float) (ch - '0'));
// 如果是运算符,则计算结果
// 在堆栈中有前2个操作数的情况下,将结果推送到堆栈中
else endingStack.push(getResult(ch, endingStack.pop(), endingStack.pop()));
}
//返回表达式结果
return endingStack.pop();
}
/**
* 获得结果
*
* @param operation 运算符
* @param f1 操作数1
* @param f2 操作数2
* @return 返回结果
*/
public static float getResult(char operation, float f1, float f2) {
if (operation == '+') {
return f2 + f1;
} else if (operation == '-') return f2 - f1;
else if (operation == '*') return f2 * f1;
else if (operation == '÷') return f2 / f1;
else return (float) -0;
}
六.单元测试展示
-
以输入20道题目为例子,生成的题目如下:
-
答案如下:
-
用户答题情况(假设用户3、8、10、12题出错)以及结果判断如下:
七.个人小结
黄翊森
这是我第一次与其他人合作完成一个项目,一定程度上提高了我的团队协作能力。本次任务相比上次任务而言,难度有所增加,在面对一些问题时,也参考了许多资料,比如使用逆波兰函数计算结果,将算术的中缀形式改成后缀形式,减少了处理运算部分逻辑实现的难度;同时,本次项目又一次运用了分词的操作,能让我更好地理解Java中的Tokenizer类,对其一些基本用法了解更为透彻,不再像第一次使用时那样茫然无措。总而言之,此次项目经历也使我的代码能力有所提高,收获颇丰。
对于结对编程,给我带来许多好处,首先,面对问题时,能有人与我一同解决,例如在计算最大公约数时,出现最大公约数为0的情况,我们两个人各自检查代码,结合互联网上的资料,一起将这个问题解决;同时,也能让我们在分配好任务的前提下,合理高效地完成各自的任务,本次我主要完成计算模块的实现以及操作数类的定义和方法实现,最后再将两个人的代码相互融合,顺利完成本次任务。
王宗奎
本次结对编程,让我体会到合作编程的优势,我们两个人在同一个宿舍,在任务分配和遇到问题时,都能方便地进行沟通解决,不需要再一个人孤军奋战,很大程度上提高了完成任务的效率。同时,本次编程任务也提高了我的编程能力,更好地掌握了Java语言,也让我养成良好的编程习惯,对方法和类的命名规范有所了解。由于时间较为紧迫,还有部分问题还尚待解决,也算是遗憾,但还是让我收获颇丰。