结对项目
结对项目
这个作业属于哪个课程 | 软件工程 |
---|---|
这个作业要求在哪里 | 作业要求 |
这个作业的目标 | 学会与搭档合作完成项目 |
合作者(学号):
陈金海:3118001626
林凡(其他专业)
GitHub地址: https://github.com/alanthegoat/AutogenerationOfArithmetic
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 60 |
Estimate | 估计这个任务需要多少时间 | 40 | 30 |
Development | 开发 | 600 | 840 |
Analysis | 需求分析 (包括学习新技术) | 20 | 40 |
Design Spec | 生成设计文档 | 15 | 20 |
Design Review | 设计复审 | 15 | 10 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
Design | 具体设计 | 200 | 220 |
Coding | 具体编码 | 250 | 230 |
Code Review | 代码复审 | 100 | 200 |
Test | 测试(自我测试,修改代码,提交修改) | 300 | 240 |
Reporting | 报告 | 120 | 120 |
Test Repor | 测试报告 | 100 | 60 |
Size Measurement | 计算工作量 | 10 | 5 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 1880 | 2385 |
1.主要类与函数
- src
- com.alanthegoat
- autogenerationofarithmetic
- GenerationOfRandomArithmetic.java
- GenerationOfRandomNumber.java
- calculation
- CalculationOfRandomArithmetic.java
- outputfile
- OutputAnser.java
- OutputArithmetic.java
- utils
- BinaryTree.java
- LinkedStack.java
- Fraction.java
- Utils.java
- GetMaxGcd.java
- src
- com.alanthegoat
- grade
- Grade.java
>主要模块功能:
>1.GenerationOfRandomArithmetic 随机生成表达式
>2.GenerationOfRandomNumber 随机生成数值和运算符,供1使用
>3.CalculationOfRandomArithmetic 计算表达式值
>4.OutputAnser 输出答案
>5.OutputArithmetic 输出表达式
>6.Grade 改题目,判断是否正确
graph LR
A[generateArithmetic] -->B[calculationOfRandomArithmetic]
A -->C[outputArithmetic]
B -->D[outputAnswer]
2.代码分析
graph LR
A[生成随机表达式] -->C{判断}
C -->|false| A[生成随机表达式]
C -->|true| E[计算结果]
E[计算结果]-->写入文件
2.1生成随机表达式(部分代码)
public String generateArithmetic(BinaryTree<String> binaryTree,int bound){
random = new Random();
//随机生成运算符数量
operationNum = random.nextInt(4);
if(operationNum==0)
operationNum++;
//根据运算符数量决定数值数量
switch (operationNum){
case 1: numberNum = 2;break;
case 2: numberNum = 3;break;
case 3: numberNum = 4;break;
}
int flag = 0;
Random random = new Random();
Number number = null;
Character character = null;
Double decimalNumber = null;
Integer integer = null;
//随机产生数值,并生成表达式树
if(operationNum==1){
character = GenerationOfRandomNumber.getRandomOperation();
binaryTree.insert(character.toString());
for (int i = 0; i < numberNum; i++) {
flag = random.nextInt(2);
number = decimalNumberOrInteger(flag,bound);
if(number instanceof Integer){
integer = number.intValue();
binaryTree.insert(integer.toString());
}
else {
decimalNumber = number.doubleValue();
//如果是小数,则转换成真分数
binaryTree.insert(Utils.decimalNumberToProperFraction(decimalNumber));
}
}
}else{
for (int i = 0; i < operationNum; i++) {
character = GenerationOfRandomNumber.getRandomOperation();
binaryTree.insert(character.toString());
}
for (int i = 0; i < numberNum; i++) {
flag = random.nextInt(2);
number = decimalNumberOrInteger(flag,bound);
if(number instanceof Integer){
integer = number.intValue();
binaryTree.insert(integer.toString());
}
else {
decimalNumber = number.doubleValue();
binaryTree.insert(Utils.decimalNumberToProperFraction(decimalNumber));
}
}
}
//添加括号
String arithmetic = binaryTree.inorderTraversal();
StringBuilder sb = new StringBuilder();
String[] strings = arithmetic.split(" ");
if(operationNum==2){
if(getPriority(strings[3])>getPriority(strings[1])){
sb.append("(");
for (int i = 0; i < strings.length; i++) {
if(i==3)
sb.append(")");
sb.append(strings[i]);
}
}
}
2.2测试(生成10个数值范围在10以内的表达式)
public static void main(String[] args){
int count = 1;
GenerationOfRandomArithmetic generationOfRandomArithmetic = new GenerationOfRandomArithmetic();
BinaryTree<String> binaryTree;
for (int i = 0; i < 10; i++) {
binaryTree = new BinaryTree<>();
System.out.println(count++ + ". " +generationOfRandomArithmetic.generateArithmetic(binaryTree,10));
}
}
2.3结果
1. 1'9/10 + 2
2. 7'2/5 + 1
3. 9'9/10 + 9 - 9
4. 6'3/5 × 4 - 2 × 7'4/5
5. 4 ÷ 1/10
6. (7'3/10-5'7/10)÷6
7. 7/10 ÷ 9
8. 4'3/5 ÷ 5
9. (9/10+8'3/10)÷9/10
10. 8÷1÷(5'1/2+0)
2.4计算表达式(部分代码)
public class CalculationOfRandomArithmetic {
//arithmetic为后缀表达式
public String calculationOfRandomArithmetic(String arithmetic){
//获取表达式中的数值
String[] strings = arithmetic.split(" ");
//创建一个栈来计算表达式
LinkedStack<String> linkedStack = new LinkedStack<>();
String num1,num2;
Fraction fraction1,fraction2;
//遍历表达式
for (int i = 0; i < strings.length; i++) {
//是整数直接压栈
if (Utils.isInteger(strings[i])) {
linkedStack.push(strings[i]);
}
//是分数先把真分数转换成假分数,方便后面计算
else if (Utils.isDecimal(strings[i])) {
linkedStack.push(Utils.properFractionToFraction(strings[i]).toString());
}
//是运算符,弹出两个数值,根据数值类型进行计算
else {
num1 = linkedStack.pop();
num2 = linkedStack.pop();
//若两个出栈数值至少有一个是分数的话,则统一转成分数再运算。
if(Utils.isDecimal(num1)||Utils.isDecimal(num2)){
fraction1 = Utils.fractionize(num1);
fraction2 = Utils.fractionize(num2);
switch (strings[i]) {
case "+":
fraction1 = fraction2.add(fraction1);
linkedStack.push(Utils.properFractionToFraction(fraction1.toString()).toString());
break;
case "-":
fraction1 = fraction2.sub(fraction1);
//子表达式中运算出现负数,结果返回null,不写入文件
if(fraction1.toString().startsWith("-"))
return null;
linkedStack.push(Utils.properFractionToFraction(fraction1.toString()).toString());
break;
case "×":
fraction1 = fraction2.muti(fraction1);
linkedStack.push(Utils.properFractionToFraction(fraction1.toString()).toString());
break;
case "÷":
//除数为0,结果返回null,不写入文件中
if(fraction1.getNumerator()==0)
return null;
fraction1 = fraction2.div(fraction1);
linkedStack.push(Utils.properFractionToFraction(fraction1.toString()).toString());
break;
}
}
//后面还有两个整数的运算...
3.性能分析
3.1 用时分析(生成1000道题目和计算答案并写入文件中)
发现把表达式和答案写到文件时花费很多时间,如close和init,查看代码,发现在写入文件时,每写入一个表达式或者答案之前,都重新初始化输出流和文件流,最后还关闭了流,浪费了大量无关的时间。
3.2改进代码(把重复的工作提取出来)
public class OutputArithmetic {
static int count = 1;
static private final String filePath ="d://Exercises.txt";
static private BufferedWriter bufferedWriter;
static {
try {
bufferedWriter = new BufferedWriter(new FileWriter(filePath,true));
} catch (IOException e) {
e.printStackTrace();
}
}
public static void outputArithmetic(String arithmetic) throws IOException {
bufferedWriter.write((count++) +". " + arithmetic+" = ");
bufferedWriter.newLine();
}
public static void close() throws IOException {
bufferedWriter.close();
}
}
3.3改进后的消耗!
原来的生成1000道题目计算答案再写出文件需要600ms,现在只需353ms,文件的写出工作只占6.7%和0.7%,不再消耗那么多时间,速度提高了0.3秒。
3.4改进判断条件
改进后的时间是计算表达式结果和生成表达式结果花费最多时间,分别为%37.3和%26.4,发现在计算表达式值时,判断数值是否是分数(Utils.isDecimal(String s))时和分数的转换,消耗了很多时间。
原来代码如下:
public static boolean isDecimal(String str){
if(str==null)
return false;
return str.matches("\\d+/\\d+")||str.matches("\\d+'\\d+/\\d+");
}
可以简化判断表达式如下
public static boolean isDecimal(String str){
if(str==null)
return false;
return str.matches(".+/.+");
}
速度提升了4%。
3.5代码改进(部分代码)
在原来代码的基础上添加一个分数类Fraction
public class Fraction {
private int numerator; // 分子
private int denominator; // 分母
private Integer integer;//若是假分数,则转换成代分数的整数部分
public Fraction() {
}
public void setInteger(int integer) {
this.integer = integer;
}
public Fraction(int a, int b) {
if (a == 0) {
numerator = 0;
denominator = 1;
} else {
setNumeratorAndDenominator(a, b);
}
}
private void setNumeratorAndDenominator(int a, int b) { // 设置分子和分母
int c = f(Math.abs(a), Math.abs(b)); // 计算最大公约数
numerator = a / c;
denominator = b / c;
if (numerator < 0 && denominator < 0) {
numerator = -numerator;
denominator = -denominator;
}
}
public Fraction add(Fraction r) { // 加法运算
Fraction result = null;
int a = r.getNumerator();
int b = r.getDenominator();
int newNumerator = numerator * b + denominator * a;
int newDenominator = denominator * b;
//若运算结果是假分数,则通过setInteger()和操作转成代分数。
if(newNumerator>newDenominator){
a = newNumerator/newDenominator;
newNumerator = newNumerator%newDenominator;
result = new Fraction(newNumerator, newDenominator);
result.setInteger(a);
return result;
}
result = new Fraction(newNumerator, newDenominator);
return result;
}
//减法、乘法、除法运算...
//分数类的toString方法,分两种情况,1.若是假分数,则以代分数的形式输出 2.若是真分数,则直接输出。
public String toString() {
if(integer==null)
return numerator+"/"+denominator;
return integer+"'"+numerator+"/"+denominator;
}
有了这个类,在计算表达式值时,对于分数和分数或者分数和整数的运算可以简化,不用在进行额外的转化,提高计算效率。
4.打分模块
4.1代码
public static void grade() throws IOException {
result1 = "correct:";
result2 = "wrong:";
sb1.append("(");
sb2.append("(");
String str = null, str1 = null;
String[] strings = null, strings1 = null;
//判断文件末尾
while ((str = br2.readLine()) != null && (str1 = br1.readLine()) != null) {
strings = str.split("=");
strings1 = str1.split("\\. ");
if (strings[1].equals(strings1[1])) {
rightCount++;
sb1.append(strings1[0] + ",");
} else {
wrongCount++;
sb2.append(strings1[0] + ",");
}
}
sb1.append(")");
sb2.append(")");
//构造最终结果
result1 += rightCount + sb1.toString();
result2 += wrongCount + sb2.toString();
bw.write(result1);
bw.newLine();
bw.write(result2);
br1.close();
br2.close();
bw.close();
}
4.2测试
先在命令行中生成表达式并生成答案文件
命令如下:
E:\IdeaProjects\AutogenerationOfArithmetic\out\artifacts\AutogenerationOfArithmetic_jar>java -jar AutogenerationOfArithmetic.jar -n 5 -r 10 表示生成5道题目,数值范围在10以内
<生成的题目如下:
答案如下:
自行书写答案,故意写错几个
在命令行中使用命令进行改题评分
命令如下:
java -jar Grade.jar
结果如下:
结果正确!
5.项目小结
5.1我的项目小结:
在本次结对项目中我主要负责书写代码,在与同伴书写代码之前,我们共同商讨了代码的主要结构和代码的实现方法,决定了项目的骨架,比如需要哪些包,书写哪些类,书写哪些方法。
这很重要,因为结对项目不是一个人的工作,需要相互合作交流才能更好的完成项目。若做项目前不交流,则后面很有可能每个人编写的接口都不适合另一个人书写的代码。
软件工程的目标是:在给定成本、进度的前提下,开发出具有适用性、有效性、可修改性、可靠性、可理解性、可维护性、可重用性、可移植性、可追踪性、可互操作性和满足用户需求的软件产品。
追求这些目标有助于提高软件产品的质量和开发效率,减少维护的困难。在本次项目中,我遵循了代码的可重用性、可互操作性、可修改性。更加理解了软件工程这门课的重要性。
5.2同伴的项目小结:
在修改代码时,需要注意不要改变原来的接口。在debug代码时,灵活运用idea的debug模式可以更加轻松找出bug,提高效率。
合作项目更加考验一个人的写代码水平。希望以后能够多多练习这种模式。