zoukankan      html  css  js  c++  java
  • 软工项目二:结对项目

    一、GitHub地址:https://github.com/hhhh344/Arithmetic

    项目合作者:3118005001 胡鹤腾 3118005003 黄济成
    项目使用网址:http://120.78.187.151:8081/
    (注:因为校验答案的算法有bug,如果上传了不合规格的文件就会炸,所以这里面只有生成表达式的功能)

    二、题目叙述

    2.1题目数字以及运算符要求:

    • 真分数:1/2, 1/3, 2/3, 1/4, 1’1/2, …。
    • 自然数:0, 1, 2, …。
    • 运算符:+, −, ×, ÷。
    • 括号:(, )。
    • 等号:=。
    • 分隔符:空格(用于四则运算符和等号前后)。
    • 算术表达式:

        e = n | e1 + e2 | e1 − e2 | e1 × e2 | e1 ÷ e2 | (e),

        其中e, e1和e2为表达式,n为自然数或真分数。

    • 四则运算题目:e = ,其中e为算术表达式。

    2.2 生成题目具体操作过程及格式:

    • 使用 -n 参数控制生成题目的个数,例如: Myapp.exe -n 10 将生成10个题目。
    • 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围,例如 :Myapp.exe -r 10 将生成10以内(不包括10)的四则运算题目。该参数可以设置为1或其他自然数。该参数必须给定,否则程序报错并给出帮助信息。
    • 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1− e2的子表达式,那么e1≥ e2。
    • 生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数。
    • 每道题目中出现的运算符个数不超过3个。
    • 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。例如,23 + 45 = 和45 + 23 = 是重复的题目,6 × 8 = 和8 × 6 = 也是重复的题目。3+(2+1)和1+2+3这两个题目是重复的,由于+是左结合的,1+2+3等价于(1+2)+3,也就是3+(1+2),也就是3+(2+1)。但是1+2+3和3+2+1是不重复的两道题,因为1+2+3等价于(1+2)+3,而3+2+1等价于(3+2)+1,它们之间不能通过有限次交换变成同一个题目。
    • 生成的题目存入执行程序的当前目录下的Exercises.txt文件,格式如下:
    1. 四则运算题目1
    2. 四则运算题目2

       ……

     其中真分数在输入输出时采用如下格式,真分数五分之三表示为3/5,真分数二又八分之三表示为2’3/8。

    • 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件,格式如下:

       答案1

         答案2

    • 真分数的运算如下例所示:1/6 + 1/8 = 7/24。
    • 程序应能支持一万道题目的生成。
    • 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,输入参数如下:

      Myapp.exe -e .txt -a .txt 统计结果输出到文件Grade.txt,格式如下:

        Correct: 5 (1, 3, 5, 7, 9)

        Wrong: 5 (2, 4, 6, 8, 10)

      其中“:”后面的数字5表示对/错的题目的数量,括号内的是对/错题目的编号。为简单起见,假设输入的题目都是按照顺序编号的符合规范的题目。

    需求实现
    需求描述 是否实现
    控制生成题目的个数
    控制题目中数值范围
    计算过程不能产生负数
    生成的题目中如果存在形如e1÷ e2的子表达式,那么其结果应是真分数
    每道题目中出现的运算符个数不超过3个
    程序一次运行生成的题目不能重复
    生成的题目存入执行程序的当前目录下的Exercises.txt文件
    计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt文件
    程序应能支持一万道题目的生成
    程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计

    三、psp表

    *PSP2.1* *Personal Software Process Stages* *预估耗时(分钟)* *实际耗时(分钟)*
    Planning 计划 10 10
    ·Estimate · 估计这个任务需要多少时间 10 10
    Development 开发 1440 2670
    · Analysis · 需求分析 (包括学习新技术) 180 500
    · Design Spec · 生成设计文档 60 60
    ·Design Review · 设计复审 (和同事审核设计文档) 60 60
    ·Coding Standard · 代码规范 (为目前的开发制定合适的规范) 60 60
    · Design · 具体设计 180 360
    · Coding · 具体编码 600 1000
    · Code Review · 代码复审 100 200
    · Test · 测试(自我测试,修改代码,提交修改) 200 360
    Reporting 报告 120 120
    · Test Report · 测试报告 60 60
    ·Size Measurement · 计算工作量 30 30
    · Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 30 30
    合计 1570 2730

    四、效能分析

    1 程序效能
    

    2.消耗最多的函数
    

    五、设计实现过程

    六、代码说明

    最主要的是先将表达式定义好,我们自定义的表达式如下

    public class Expression {
    
        /**
         * parameterList 操作数列表
         */
        private List<Integer[]> parameterList;
    
        /**
         * operatorList 操作符列表
         */
        private List<String> operatorList;
    
        /**
         * result 运算结果
         */
        private Integer[] result;
    
        /**
         * pattern 表达式模型
         */
        private String pattern;
    

    1、ExpressionDaoImpl类里面的几个方法

    表达式定义好后,实现起来不难,这里就不贴代码了

    将生成的表达式转化为字符串类型,方便将其存储再入栈或者写入文件

    public String expressionToString(Expression exp) {
            String pattern = exp.getPattern();
        	//要被返回的字符串
            String returnString = "";
    
            List<Integer[]> parameterList = exp.getParameterList();
            Integer[] num;
            List<String> operatorList = exp.getOperatorList();
            String operator;
    
            int parameterIndex = 0;
            int operatorIndex = 0;
    		//每一个表达式都存储着其模型,按照模型将其转化
            for(int i = 0; i < pattern.length(); i++) {
                char temp = pattern.charAt(i);
                switch (temp) {
                    case '(':
                    case ')':
                        returnString += temp + " ";
                        break;
                    case 'n':
                        num = parameterList.get(parameterIndex++);
                        if(num[0] == 0) {
                            returnString += num[1] + " ";
                        }
                        else {
                            //如果带分数前面的整数为0,则只打印分数部分
                            if(num[1] != 0) {
                                returnString += num[1] + "'" + num[2] + "/" + num[3] + " ";
                            }
                            else {
                                returnString += num[2] + "/" + num[3] + " ";
                            }
                        }
                        break;
                    case '#':
                        operator = operatorList.get(operatorIndex++);
                        if(operator.contains("/")) {
                            returnString += "÷ ";
                        }
                        else {
                            returnString += operator + " ";
                        }
                        break;
                    default: 
                }
            }
            returnString += "=";
            return returnString;
        }
    

    将字符串类型的表达式转化为自定义的表达式,用于读取文本文件里的表达式

    public Expression stringToExpression(String expressionString) {
            Expression exp = new Expression();
            List<Integer[]> parameterList = new ArrayList<>();
            List<String> operateList = new ArrayList<>();
            String pattern = "";
            String[] str = expressionString.split("\s");
    
            for (String item : str) {
                if(item.matches("^[()]$")) {
                    pattern += item;
                }
                else if(item.matches("^[\+\-\*÷]$")) {
                    if("÷".equals(item)) {
                        operateList.add("/");
                    }
                    else {
                        operateList.add(item);
                    }
                    pattern += "#";
                }
                //整数
                else if(item.matches("^[0-9]+$")) {
                    pattern += "n";
                    Integer[] num = new Integer[2];
                    num[0] = 0;
                    num[1] = Integer.parseInt(item);
                    parameterList.add(num);
                }
                //分数
                else if(item.matches("^([0-9]+')?[0-9]+\/[0-9]+$")) {
                    pattern += "n";
                    Integer[] num = new Integer[4];
                    num[0] = 1;
                    if(item.contains("'")) {
                        num[1] = Integer.parseInt(item.substring(0, item.indexOf("'")));
                        num[2] = Integer.parseInt(item.substring(item.indexOf("'")+1, item.indexOf("/")));
                    }
                    else {
                        num[1] = 0;
                        num[2] = Integer.parseInt(item.substring(0, item.indexOf("/")));
                    }
                    num[3] = Integer.parseInt(item.substring(item.indexOf("/")+1));
                    parameterList.add(num);
                }
                else if(item.matches("^=$") || !" ".equals(item)) {
    
                }
                else {
                    throw new RuntimeException("将字符串表达式转化为Expression时出现了未知字符" + item);
                }
            }
            exp.setOperatorList(operateList);
            exp.setParameterList(parameterList);
            exp.setPattern(pattern);
            return exp;
        }
    

    2、CalculateUtilsImpl类里面的几个方法

    将表达式转化为后缀表达式

    public Stack<String> getPostfixExpression(Expression expression) {
            Stack<String> S1 = new Stack<>();
            Stack<String> S2 = new Stack<>();
    
            List<Integer[]> parameterList = expression.getParameterList();
            Integer[] num;
    
            List<String> operatorList = expression.getOperatorList();
            String operator;
    
            int parameterIndex = 0;
            int operatorIndex = 0;
    
            //获取表达式的模式
            String pattern = expression.getPattern();
            S1.push("#");
    
            for(int i = 0; i < pattern.length(); i++) {
                char temp = pattern.charAt(i);
                switch (temp) {
                    //若取出的字符是操作数,则分析出完整的运算数,该操作数直接送入S2栈
                    case 'n':
                        num = parameterList.get(parameterIndex++);
                        if(num[0] == 0) {
                            S2.push(num[1].toString());
                        }
                        else {
                            S1.push("(");
                            S2.push(num[1].toString());
                            S1.push("+");
                            S2.push(num[2].toString());
                            S1.push("/");
                            S2.push(num[3].toString());
                            while(S1.peek() != "(") {
                                S2.push(S1.pop());
                            }
                            S1.pop();
                        }
                        break;
                    case '#':
                        operator = operatorList.get(operatorIndex);
                        //1.如果S1为空,或栈顶为"(",则将该运算符进S1栈
                        if(S1.peek() == "#" || S1.peek() == "(") {
                            S1.push(operator);
                            operatorIndex++;
                        }
                        //2.如果该运算符优先级(不包括括号运算符)大于S1栈栈顶运算符优先级,则将该运算符进S1栈
                        else if(comparePriority(operator, S1.peek())) {
                            S1.push(operator);
                            operatorIndex++;
                        }
                        //3.否则,将S1栈的栈顶运算符弹出,送入S2栈中,跳回1
                        else {
                            S2.push(S1.pop());
                            i--;
                        }
                        break;
                    //若取出的字符是“(”,则直接送入S1栈顶。
                    case '(':
                        S1.push("(");
                        break;
                    //若取出的字符是“)”,则将距离S1栈栈顶最近的“(”之间的运算符,逐个出栈,依次送入S2栈,此时抛弃“(”。
                    case ')':
                        while(S1.peek() != "(") {
                            S2.push(S1.pop());
                        }
                        if(S1.peek() == "(") {
                            S1.pop();
                        }
                    default:
                }
            }
            while(S1.peek() != "#") {
                S2.push(S1.pop());
            }
            return S2;
        }
    

    传进一个表达式,计算表达式结果

    public Integer[] getExpressionResult(Expression expression) {
    		//将表达式转化为后缀表达式
            Stack<String> postfixExpression = getPostfixExpression(expression);
            ExpressionDaoImpl exp = new ExpressionDaoImpl();
            Stack<Integer[]> S3 = new Stack<>();
            Integer[] num1, num2, temp;
    
            for (String item : postfixExpression){
    //            如果取出的元素是数字
                if(item.matches("[0-9]+")){
                    S3.push(toInteger(item));
                }
    //            如果取出的元素是操作符
                else if (item.matches("[\+\-\*\/]")){
    //                栈顶元素应该在操作符后面
                    num2 = S3.pop();
                    num1 = S3.pop();
                    temp = calculateTwoNumber(num1,num2,item);
    //                如果两个数字不符合计算规则,除法出现被除数为零,减法出现负数
                    if(temp[0]==4){
                        return temp;
                    }
                    S3.push(temp);
                }
            }
            if (S3.size()!=1){
                throw new RuntimeException("栈内元素剩余不等于1!" + S3.toString());
            }
            //将最终结果换为真分数
            Integer[] result = exp.getProperFraction(S3.peek());
            return result;
        }
    

    3、FileUtilsImpl类里面的几个方法

    读取文本文件时,将题号和表达式使用Map<>映射起来

    public Map<Integer, String> getExpressionFileMap(File expressionFile) throws IOException {
            FileReader fr = new FileReader(expressionFile);
            BufferedReader br = new BufferedReader(fr);
            Map<Integer, String> expressionFileMap = new HashMap<>();
            String line = br.readLine();
    
            Integer number;
            Integer[] result;
            Expression expression;
    
            while(line != null && line != "
    ") {
                number = Integer.parseInt(line.substring(0, line.indexOf(".")));
                expression = exp.stringToExpression(line.substring(line.indexOf(".")+1));
                result = cal.getExpressionResult(expression);
                expressionFileMap.put(number, cal.resultToString(result));
                line = br.readLine();
            }
    
            br.close();
            fr.close();
            return expressionFileMap;
        }
    

    读取文本文件时,将题号和答案使用Map<>映射起来

    public Map<Integer, String> getAnswerFileMap(File answerFile) throws IOException {
            FileReader fr = new FileReader(answerFile);
            BufferedReader br = new BufferedReader(fr);
            Map<Integer, String> answerFileMap = new HashMap<>();
            String line = br.readLine();
    
            Integer number;
            String[] answerString;
    
            while(line != null && line != "
    ") {
                answerString = line.split(" ");
                number = Integer.parseInt(answerString[0].substring(0, answerString[0].indexOf(".")));
                if (answerString.length == 2) {
                    answerFileMap.put(number, answerString[1]);
                }
                line = br.readLine();
            }
    
            br.close();
            fr.close();
            return answerFileMap;
        }
    

    比较表达式文件里面的表达式和答案文件里面的答案是否相同,并将统计结果写入Grade

    public boolean writeGradeInFile(File expressionFile, File answerFile, File gradeFile) throws IOException {
            FileWriter fw = new FileWriter(gradeFile);
            BufferedWriter bw = new BufferedWriter(fw);
            Map<Integer, String> expressionFileMap = getExpressionFileMap(expressionFile);
            Map<Integer, String> answerFileMap = getAnswerFileMap(answerFile);
    
            int correctCount = 0;
            int wrongCount = 0;
    
            String correctString = "Correct:(";
            String wrongString = "Wrong:(";
    
            for (Map.Entry<Integer, String> item : expressionFileMap.entrySet()) {
                Integer key = item.getKey();
                //比较两个答案字符串是否一致
                if(item.getValue().equals(answerFileMap.get(key))) {
                    correctCount++;
                    if(correctCount == 1) {
                        correctString += key;
                    }
                    else {
                        correctString += ", " + key;
                    }
                }
                else {
                    wrongCount++;
                    if(wrongCount == 1) {
                        wrongString += key;
                    }
                    else {
                        wrongString += ", " + key;
                    }
                }
            }
            correctString += ")";
            wrongString += ")";
    
            StringBuilder correctStringBuilder = new StringBuilder(correctString);
            StringBuilder wrongStringBuilder = new StringBuilder(wrongString);
    
            correctStringBuilder.insert(correctStringBuilder.indexOf("("), correctCount);
            wrongStringBuilder.insert(wrongStringBuilder.indexOf("("), wrongCount);
    
            bw.write(correctStringBuilder.toString());
            bw.newLine();
            bw.write(wrongStringBuilder.toString());
    
            bw.close();
            fw.close();
    
            if(wrongCount == 0) {
                return true;
            }
            return false;
        }
    

    七、测试运行

    1、如图所示,可以随机生成一万道题,数据范围为10(可用取值范围为2~10000),人工验证计算出的答案是正确

    2、校验答案

    和Grade文件里面的结果一致

    八、项目小结

    1. ​ 按照结对项目的要求,整个项目的完成一直都是两个人合作实现的。
    2. ​ 第一天的时候并没用直接写代码,而是先分析了该项目需求,讨论了该创建哪种项目模式,和实体类的定义。
    3. ​ 第一次使用Java完成一个项目,一开始的时候很不熟练,所以前面的时候有不少时间在纠结创建哪些类,和哪些包,以及它们之间的关系。
    4. 主要遇到的问题有以下几个:
      1. 如何存储这些随机生成的数字,主要是分数
      2. 如何随机生成括号并且没有错误或者无意义
      3. 计算的时候因为分数要通分,导致的通分后分母可能过大而出现的溢出问题
      4. 判重问题(没有解决)
      5. 在git上遇到不少的问题,包括创建一个团队仓库,直接在IDEA上git,还有遇到的最大的麻烦就是合并分支出现的冲突问题,我们创建了一个develop分支,主要是在这个分支上提交,但我们也修改过master,所以在将其合并到master上的出现了冲突

    5、个人总结:

    ​ 黄济成:收获,这一次项目是先规划了大部分内容,包括先写好了接口,写好注释,然后接下来才是开始写码实现,所以在中间实现的时候很流畅,只要去实现那些功能就可以了。然后就是写好一个功能后就会去写单元测试,虽然这个花了不少时间,但的确也发现了不少问题。实际开发时间远比规划时间多的主要原因是没有料到那些意外(比如git)会花如此多的时间。这种结对合作,让我在写代码的时候更集中注意力,遇到问题的时候更是能集中两个人的智慧解决,这次的项目十分感谢我的合作伙伴,让我学到了非常多的东西。
    胡鹤腾: 整个项目开发过程中我们花了比较多的时间去学习、构思项目结构的构建,git以及合作的方法。一开始要进行不能面对面的合作交流是比较困难的,后来我们适应了用腾讯会议来交流思路、互相监督以及用git共同管理仓库,这还是足够便利的。这些合作技能也是我在这次项目中最大的收获,还有一些收获是编程能力的提高、学习能力的提高。遗憾的是代码写得不是很规范,第一次使用JAVA写完整的项目,我们后来意识到这点但也难以改动,但是下次会做得更高效、规范。

  • 相关阅读:
    汉字获取首字母
    .net 实现对DNS服务器的管理
    css使图片变灰
    javascript实现文本框只能输入数字和字母
    解决Outlook不能打开的问题
    javascript实现弹出式登录界面
    asp.net防盗链技术
    javascript中replace()(转帖)
    chm文件无法显示问题
    使用Lucene.NET进行分词、搜索
  • 原文地址:https://www.cnblogs.com/cheng-/p/12682616.html
Copyright © 2011-2022 走看看