1、GitHub地址:https://github.com/caiyouling/Myapp

队友:钟小敏 GitHub地址:https://github.com/zhongxiao136/Myapp

2.PSP表格

PSP Personal Software Process Stages 预计耗时(分钟) 实际耗时(分钟)
Planning 计划 40 30
.Estimate .估计这个任务需要多少时间 40 30
Development 开发 1320 1330
.Analysis .需求分析 100 120
.Design Spec .生成设计文档 40 30
.Design Review .设计复审 15 20
.Coding Standard .代码规范 15 20
.Design .具体设计 90 100
.Coding .具体编码 850 950
.Code Review .代码复审 90 90
.Test .测试(自我测试,修改代码,提交修改) 120 130
Reporting 报告 180 160
.Test Report .测试报告 90 80
.Size Measurement .计算工作量 30 40
.Postmortem&Process Improvement Plan .事后总结,并提出过程改进计划 60 40
合计 1540 1520

效能分析

设计实现过程

  • 解题思路:由于题目为自动生成小学四则运算题目,所以需要系统随机生成运算式。运算式包含操作数和运算符,所以我们就用了一个CreateNumAndOpe类随机生成,然后利用createExpression类组合成运算式和答案(利用Calculate类进行计算,因为结果要为真分数或整数,所以我们利用Fraction类对分数进行操作化为最终形式),然后用createFile类生成题目,答案和做题文件,最后,利用Grade类进行验证答案,并生成成绩文件。
  • createNumAndOpe类:定义运算符数组;包含createNumber()和createOperator(),其中createNumber()用于生成操作数(利用Random类随机生成整数或分数);createOperator()用于生成运算符,随机从运算符数组中选出运算符。
  • createExpression类:用于生成随机带括号的运算式,其中exerciseAndAnswer()根据操作数数组和运算符数组生成运算式,其中需要用Random类判断是否随机生成括号,最后用Calculate()计算出结果。getExerciseAndAnswer()根据用户输入的题目个数和数字范围生成相应的题目和答案。
  • Calculate类:根据运算式转为字符数组,然后逐个遍历,建立一个运算符栈和操作数栈,利用中缀表达式转为后缀表达式进行计算。
  1. 因为括号的优先级最高,所以我们先判断是否为'(',若是则入运算符栈;
  2. 判断是否为')',是则从操作数取出两个进行运算,将'('之后的运算符都拿出来算,结果再存入操作数栈中,然后左括号出栈;
  3. 判断是否为运算符,当运算符优先级小于运算符栈顶元素,则取两个操作数进行运算,直至栈顶优先级不高于该运算符,结果存入操作数栈中,然后该运算符入栈;
  4. 若为操作数,因为生成的操作数有分数,但是我们转为了字符数组,就需要根据'/'作为标志获取分子和分母,若为整数,我们将分母设为1,把操作数都转为分数进行传到calculate()进行运算,返回结果
  • Fraction类:对分数进行操作,其中getFinalFraction()用于生成最终分数形式,gcd()利用辗转相除法生成最大公约数,getRealFraction()将假分数转为真分数。
  • createFile类:利用System.getProperTy()获取当前路径生成printFile文件夹,利用BufferedWriter写入题目文件,答案文件和答题文件(用户进行答题的文件),存放到printFile文件夹下
  • Grade类:用于验证用户答题是否正确,利用BufferedReader读取Writer文件和answer文件进行比较,算出对错的题数及题号写入Grade文件,也存在printFile文件夹下。
  • 各类之间的调用如下:
  • Frame类:图形化界面,用户点击‘生成文件’,即会生成练习文件,在左面板也会出现题目,中间面板进行答题,点击查看答案,则会有正确答案在右侧面板,查看成绩后会弹出成绩框。

关键代码说明

  • 用于生成操作数和运算符的方法:1.利用随机生成的flag=[0,1],当flag为0时,生成整数 2.当flag为1时,生成分子和分母组成分数
public static String[] createNumber(int number, int round){

        Random random = new Random();
        String[] num = new String[number];

        for (int i = 0; i < number; i++) {
            //用于判断生成整数还是分数
            int flag = (int)(Math.random()*10) % 2;
            if(flag == 0){          //生成整数
                int n = random.nextInt(round);
                if(n == 0){
                    num[i] = 1 + "";
                }else{
                    num[i] = n +"";
                }
            }else{          //生成分数
                //随机生成分子和分母
                int numerator = random.nextInt(round);
                int denominator = random.nextInt(round);;
                while(numerator>=denominator || numerator==0 || denominator==0){    //判断是否为真分数,且不能生成带0的分数
                    numerator = random.nextInt(round);
                    denominator = random.nextInt(round);
                }
                //拼装成分数形式
                num[i] = numerator + "/" + denominator;
            }
        }
        return num;
    }

    /**
     * 随机生成运算符
     * 将四则运算符放入一个静态不可变的operatorTypes[]字符数组中
     * 随机产生index到数组中取操作符
     */
    private static final Character[] operatorTypes = {'+' , '-' , '×' , '÷'};

    public static Character[] createOperators(int number) {
        Character[] operators = new Character[number];
        for (int i = 0; i < number; i++) {
            //随机获取运算符的类型(0~3 代表4个运算符(+、-、×、÷)的类型)
            int index = (int)(Math.random()*4);
            Character operatorType = operatorTypes[index];
            operators[i] = operatorType;
        }
        return operators;
    }
     public static int priority(Character character) {
        switch(character) {

            case '×':
            case '÷':return 1;
            case '+':
            case '-':return 0;
        }
        return -1;
    }
    
     private static String[] exerciseAndAnswer(String[] numbers, Character[] operators){

        Random random = new Random();
        //获得操作数的数量
        int num = numbers.length;
        //随机生成带括号的算式
        int isAddBracket = (int)(Math.random()*10) %2;
        if(isAddBracket ==  1){    //当isAddBracket==1时,生成带括号的表达式
            //当标记为1时代表该操作数已经添加了左括号
            int[] leftBracket = new int[num];
            //当标记为1时代表该操作数已经添加了右括号
            int[] rightBracket = new int[num];
            //遍历操作数数组,随机添加括号
            for (int index = 0 ; index<num-1 ; index++) {
                int n = (int)(Math.random()*10)%2;
                if(n == 0 && rightBracket[index] != 1) {//判断当前操作数是否标记了左括号
                    leftBracket[index] = 1;     //标记已生成左括号
                    numbers[index] = "(" + numbers[index];  //操作数之前加上左括号
                    int k = num - 1;
                    //生成右括号的位置(左括号的位置~最后)
                    int rightBracketIndex = random.nextInt(k)%(k-index) + (index+1);
                    //如果当前操作数有左括号,则重新生成括号位置
                    while (leftBracket[rightBracketIndex] == 1){
                        rightBracketIndex = random.nextInt(k)%(k-index) + (index+1);
                    }
                    rightBracket[rightBracketIndex] = 1;        //标记已生成右括号
                    numbers[rightBracketIndex] = numbers[rightBracketIndex] +")";
                }
            }
        }
        //将运算符数组和操作数数组交替拼成一个算式字符串
        StringBuilder str = new StringBuilder(numbers[0]);
        for (int k = 0; k < operators.length; k++) {     //组成算式
            str.append(operators[k]).append(numbers[k + 1]);
        }
        //将算式转换为String
        String exercise = str.toString();
        //获取运算式结果
        String answer = Calculate.getAnswer(exercise);
        if(answer.equals("-")){      //运算过程出现负数则返回null
            return null;
        }
        return new String[]{exercise,answer};
    }
public static String getAnswer(String expression) {

        Stack<Character> operators = new Stack<Character>();  //运算符栈,用于存放运算符
        Stack<Fraction> fractions = new Stack<Fraction>();  //操作数栈,用于存放操作数
        if(expression == null){
            return "null";
        }
        char[] chars = expression.toCharArray();   //将表达式字符串转成字符数组
        //遍历获取处理
        for (int i = 0 ; i<chars.length ; i++) {
            char c = chars[i];
            if(c == '('){             //先处理有括号的情况,如果是左括号,入栈
                operators.push(c);
            }   else if(c == ')'){           //当前字符为右括号
                while(operators.peek() != '('){   //当运算符栈顶的元素不为‘(’,则继续

                    Fraction fraction1 = fractions.pop();   //拿取操作栈中的两个分数
                    Fraction  fraction2 = fractions.pop();
                    //获取计算后的值
                    Fraction result = calculate(operators.pop(), fraction1.getNumerator(), fraction1.getDenominator(),
                            fraction2.getNumerator(), fraction2.getDenominator());
                    if(result.getNumerator()<0){            //判断是否为负数
                        return  "-";
                    }
                    //将结果压入栈中
                    fractions.push(result);
                }
                //将左括号出栈
                operators.pop();
            }else if(isOperator(c)){//是运算符
                //当运算符栈不为空,且当前运算符优先级小于栈顶运算符优先级
                while(!operators.empty() && !(priority(c)>= priority(operators.peek()))){
                    //拿取操作栈中的两个分数
                    Fraction fraction1 = fractions.pop();
                    Fraction  fraction2 = fractions.pop();
                    //获取计算后的值
                    Fraction result = calculate(operators.pop(), fraction1.getNumerator(), fraction1.getDenominator(),
                            fraction2.getNumerator(), fraction2.getDenominator());
                    if(result.getNumerator()<0){
                        return  "-";
                    }
                    //将结果压入栈中
                    fractions.push(result);
                }
                //将运算符入栈
                operators.push(c);
            }else{          //是操作数
                if(c >= '0' && c <= '9'){
                    StringBuilder fra = new StringBuilder();
                    //对分式进行处理
                    while(i<chars.length && (chars[i]=='/' || ((chars[i]>='0') && chars[i]<='9'))){
                        fra.append(chars[i]);
                        i++;
                    }
                    i--;
                    //到此 fra里面是一个操作数
                    String val = fra.toString();
                    //标记‘/’的位置
                    int flag = val.length();
                    for(int j = 0 ; j<val.length() ; j++){
                        if(val.charAt(j)=='/'){//当获取的数值存在/则标记/的位置,便于接下来划分分子和分母生成分数对象
                            flag = j;
                        }
                    }
                    //把操作数拆成分式计算
                    //分子
                    StringBuilder numeratorBuf = new StringBuilder();
                    //分母
                    StringBuilder denominatorBuf = new StringBuilder();
                    for(int k = 0 ; k<flag; k++){
                        numeratorBuf.append(val.charAt(k));
                    }
                    //判断是否为分数
                    if(flag != val.length()){
                        for(int m = flag+1 ; m<val.length() ; m++){
                            denominatorBuf.append(val.charAt(m));
                        }
                    }else{//如果不是分数则分母计为1
                        denominatorBuf.append('1');
                    }
                    //将分子分母分别入栈
                    fractions.push(new Fraction(Integer.parseInt(numeratorBuf.toString()),
                            Integer.parseInt(denominatorBuf.toString())));
                }
            }
        }
        while(!operators.empty() && fractions.size()>1){

            Fraction fraction1 = fractions.pop();
            Fraction  fraction2 = fractions.pop();
            //获取计算后的值
            Fraction result = calculate(operators.pop(), fraction1.getNumerator(), fraction1.getDenominator(),
                    fraction2.getNumerator(), fraction2.getDenominator());
            if(result.getNumerator()<0){
                return "-";
            }
            //将结果压入栈中
            fractions.push(result);
        }
        //计算结果
        Fraction result = fractions.pop();
        //获取最终的结果(将分数进行约分)
        return Fraction.getFinalFraction(result);
    }
    //用于验证答案是否正确,correct的题数用字符串拼接,再分割字符串
     public static void isAnswer(String writeFilePath, String answerFilePath) {
        //获取用户答题和练习答案的文件
        File writeFile = new File(writeFilePath);
        File answerFile = new File(answerFilePath);
        //找到存放Writer.txt和Answer.txt文件的文件夹
        String fileDirectory = System.getProperty("user.dir") + File.separator + "PrintFile";
        File gradeFile = new File(fileDirectory, "Grade.txt");//在当前文件夹下生成Grade.txt文件
        if(writeFile.isFile() && answerFile.isFile()) {
            try {
                BufferedReader writeReader = new BufferedReader(new InputStreamReader(new FileInputStream(writeFile)));
                BufferedReader answerReader = new BufferedReader(new InputStreamReader(new FileInputStream(answerFile)));
                //储存错题和对题
                String correct = "";
                String wrong = "";
                int correctNum = 0;
                int wrongNum = 0;
                //记录错题对题的题号
                int index = 1;
                String write = null;
                String answer = null;
                System.out.println("开始验证答案···");
                //通过一行行读取文件比较答题情况
                while((( write= writeReader.readLine()) != null) && ((answer = answerReader.readLine()) != null)){
                    if(write.equals(answer)){
                        //将答对的题用字符串拼接
                        correct += "," + index;
                        index ++;
                        correctNum ++;
                    }else{
                        wrong += "," + index;
                        index ++;
                        wrongNum ++;
                    }
                }
                //用于生成Grade.txt文件内容
                if(correctNum > 0){
                    correct = "Correct: " + correctNum + "(" + correct.substring(1) +")" + "\r\n" ;
                }else{
                    correct = "Correct: 0" +"\r\n";
                }
                if(wrongNum > 0){
                    wrong = "Wrong: " + wrongNum +  "(" + wrong.substring(1) +")";
                }else{
                    wrong = "Wrong: 0";
                }
                //写入文件
                BufferedWriter gradeFileBuf = new BufferedWriter(new FileWriter(gradeFile));
                gradeFileBuf.write(correct);//将correct 和wrong写入文件
                gradeFileBuf.write(wrong);
                System.out.print(correct);//控制台也打印答题情况
                System.out.println(wrong);
                if(writeReader != null){
                    writeReader.close();
                }
                if(answerReader != null){
                    answerReader.close();
                }
                if(gradeFileBuf != null){
                    gradeFileBuf.close();
                }
                 public static String getFinalFraction(Fraction fraction) {
        int denominator = fraction.getDenominator();        //获得分子分母
        int numerator = fraction.getNumerator();
        if(numerator == 0){ //若分子为0,则输出0
            return "0";
        }
        if(denominator == 0){
            return "";
        }
        int a = gcd(numerator, denominator);        //c是分子分母的最大公因数,将分子分母化简
        if (denominator == 1 ) {   //分母为1
            return numerator + "";
        }
        if(numerator == denominator){
            return 1+"";
        }else {
            if(numerator > denominator){           //分子大于分母化为真分数
                fraction = getRealFraction(fraction);	//假分数分割为商和余数,余数作为新的分子
                if(fraction.getNumerator() == 0){//若余数为0,则代表整除
                    return fraction.getInter()+"";
                }else {
                    return fraction.getInter() + "'" + fraction.getNumerator()/a + "/" + fraction.getDenominator()/a;
                }
            }else{              //其他情况化简分数
                return numerator/a + "/" + denominator/a;
            }
        }
    }
    private static int gcd(int numerator, int denominator){
        if(numerator%denominator == 0){
            return denominator;         //获取最大公因数
        }else{
            return gcd(denominator,numerator%denominator);    //辗转相除法递归获取最大公因数
        }
    }

测试

1.启动main,运行控制台打印

2.用户输入后生成的文件列表
.
3.生成题目文件的题目

4.生成答案放入文件

5.在答题文件进行答题后

6.重新判别答案

7.传入参数不正确的情况

8.输入不正确的文件名

9.生成一万道题目

10.生成一万道题目的答案文件

11.初始界面

12.点击生成文件

13.输入生成个数

14.生成题目

15.进行作答

16.输出成绩

17.生成十道题

18.生成一万道

小结

  • 本次结对项目中,我们都受益匪浅,结对项目需要多次讨论得出一致结论,两个人一起分析问题解决bug是比一个人好一些,虽然也有发生歧义的地方,但是不同角度看问题,才能实现1+1>2,这次编程作业让我们对java有了更深了了解,但也还有很多不足的