结对项目

结对项目

这个作业属于哪个课程 软件工程
这个作业要求在哪里 作业要求
这个作业的目标 学会与搭档合作完成项目

合作者(学号):

陈金海: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,提高效率。
合作项目更加考验一个人的写代码水平。希望以后能够多多练习这种模式。
posted @ 2020-10-12 17:52  alanthegoat  阅读(187)  评论(0编辑  收藏  举报