结对项目 Java实现

结对成员:宗义澎、闫浩宇

一、GitHub地址

https://github.com/amazingyp/QustionHomework

二、PSP:

 

1

Personal Software Process Stages

预估耗时(分钟)

实际耗时(分钟)

·Planning

·计划

30

40

· Estimate

· 估计这个任务需要多少时间

30

20

·Development

·开发

300

420

· Analysis

· 需求分析 

60

50

· Design Spec

· 生成设计文档

30

20

· Design Review

· 设计复审 

30

30

· Coding Standard

· 代码规范

100

120

· Design

· 具体设计

60

20

· Coding

· 具体编码

1200

700

· Code Review

· 代码复审

20

20

· Test

· 测试(自我测试,修改代码,提交修改)

200

100

·Reporting

·报告

100

140

· Test Report

· 测试报告

60

50

· Size Measurement

· 计算工作量

20

20

· Postmortem & Process Improvement Plan

· 事后总结, 并提出过程改进计划

40

50

合计

 

2010

1800

 

三、效能分析

结对项目 Java实现_第1张图片

相较于各路大神的各种神仙速度,这个速度实在是慢(似乎是别人的100倍之多?),究其原因,我认为有以下几点:

1.用了过多的随机数:为了实现题目的随机,我在生成题目的许多地方都用了随机数,比如算式中有几个运算符,每个运算符是什么,括号有几个,在第几个位置。这些随机数会降低程序的运行速度

2.数据结构:为了处理方便,我将题目中所有的数都封装成了自己写的分数类(一个分子一个分母),在计算答案时要先封装成分数类在去计算,这也会降低程序的运算速度。

 

四、设计实现过程

首先阅读需求分析,可以大致将问题分为两部分,一部分是生成题目,另一部分是验证题目正确性。为了让程序的数据结构清晰,我将程序中的所有数据封装成了一个类,然后给出分数类的计算方法。生成题目和验证题目的内容放在了Util包里,具体情况如下图

结对项目 Java实现_第2张图片

其中Main是主类,用于处理各种给定的参数;bean包内是分数类;util包中,FileUtil用于处理文件,Number解决如何计算分数类,Question用于生成随机程序

 

五、代码和具体思路

这部分分为三部分来说

1.生成题目

题目要求给定一个数作为数字的上限,然后生成不能重复的题目,题目的运算符不能超过三个,最后的结果不能是负数。由于题目要求不能重复,我开始的想法是不用随机数,用顺序的方法生成题目,但这样根本不可行,因为题目的范围太大,生成的题目非常相似,不能拿给小学生做,于是还是采用了随机数的策略。但是用了随机数可能就会出现重复的题目,于是我干脆就找它的充分条件:只要答案重复,就认为两道题重复,这样做的合理性是题目范围非常大,生成重复题目概率本就很小,就不要为它花费太多的时间了。另外还需要解决的是运算符和括号的问题,这个地方上面也提到过,用的是随机数,生成随机数决定题目的类型。至于括号,用的则是穷举法,列举出每种情况可能出现的情况,然后再用随机数决定题目属于哪种情况。这个部分的代码主要都是随机数的调用,代码从略。

 

2.生成答案

这应该是这个项目最难的地方,给定了一个题目,如何算出它的答案呢?我们知道,题目中有括号,另外还要考虑乘除号比加减号运算等级高,所以不能简单地从左到右解析题目,这个地方的算法用的是之前数据结构学过的一套东西,具体算法为现将给出的中缀表达式题目转化为后缀表达式,然后借助栈来运算后缀表达式。具体到这个项目上,在实现算法的时候,需要把字符串形式的题目先转化为中缀表达式,再转化为后缀表达式,与此同时,题目中的数要封装为分数类,便于进行入栈出栈操作,下面代码显示如何实现这个算法。

/**
      * 将字符串形式的题目转化为Queue形式的中缀表达式
     *
     */
    public static Queue ReadString(String question) {
        Queue mid = new LinkedList();

        int son = -1;
        String temp = "";
        for(int x = 0; x) {
            char c = question.charAt(x);
            if(c=='(') {//左括号前面要么没有,要么就有一个运算符,所以直接放进队列
                mid.add(c);
            }
            else if(c==')' || c=='+' || c=='-' || c=='×' || c=='÷') {
                if(son!=-1) {//如果有分子,则说明现在的temp是分母
                    mid.add(new Num(son,Integer.valueOf(temp)));
                    temp = "";
                    son = -1;
                }
                else if(!temp.equals("")) {//若队列内有字符,则temp是分子,分母为1
                    mid.add(new Num(Integer.valueOf(temp),1));
                    temp = "";
                }
                mid.add(c);
            }
            else {//读到数字或者分母号
                if(c!='/') {//不是分数号,放到temp队列里
                    temp = temp+c;
                }
                else {//遇到分数号,队列的东西提出来做分子
                    son = Integer.valueOf(temp);
                    temp = "";
                }
            }
        }
        
        //把最后一个数搞出来
        if(son!=-1) {//如果有分子,则说明现在的temp是分母
            mid.add(new Num(son,Integer.valueOf(temp)));
            temp = "";
        }
        else if(!temp.equals("")) {//若队列内有字符,则temp是分子,分母为1
            mid.add(new Num(Integer.valueOf(temp),1));
            temp = "";
        }
        
        return mid;
    }
    
    /**
      * 将Queue计算为最后结果
      * 应输入中缀表达式
     */
    public static Num CountQueue(Queue mid) {
        Queue after = MidToAfter(mid);
        Stack tempStack = new Stack();
        
        while(after.peek()!=null) {
            if(after.peek() instanceof Num) {//操作数直接入栈
                tempStack.push(after.poll());
            }
            else {
                char op = (char)after.poll();
                Num n,first,second;
                switch (op) {
                case '+':
                    n = NumberUtil.add((Num)tempStack.pop(),(Num)tempStack.pop());
                    tempStack.push(n);
                    break;
                case '-':
                    first = (Num)tempStack.pop();
                    second = (Num)tempStack.pop();
                    n = NumberUtil.sub(second,first);
                    tempStack.push(n);
                    break;
                case '×':
                    n = NumberUtil.mul((Num)tempStack.pop(),(Num)tempStack.pop());
                    tempStack.push(n);
                    break;
                case '÷':
                    first = (Num)tempStack.pop();
                    second = (Num)tempStack.pop();
                    n = NumberUtil.div(second,first);
                    tempStack.push(n);
                    break;
                }
            }
            
        }
        Num answer = (Num)tempStack.pop();
        return NumberUtil.normal(answer);
    }
    
    /**
      * 将中缀表达式转换为后缀表达式
     *
     */
    public static Queue MidToAfter(Queue mid) {
        Queue after = new LinkedList();
        Stack tempStack = new Stack();
        
        while(mid.peek()!=null) {
            if(mid.peek() instanceof Num) {//操作数直接入队列
                after.add(mid.poll());
            }
            else {//符号的判定
                char c = (char) mid.poll();
                if(c=='(') {//左括号直接入栈
                    tempStack.add(c);
                }
                else if(c==')') {//右括号把栈里的东西全弄到队列里,直到遇到左括号
                    while (true) {
                        if (tempStack.empty()) {
                            System.out.println("缺少左括号! ");
                            return null;
                        } else if ((char)tempStack.peek()=='(') {
                            tempStack.pop();
                            break;
                        } else {
                            after.add(tempStack.pop());
                        }
                    }
                }
                
                //非括号类运算符
                else if (!tempStack.empty()) {
                    char peek = (char)tempStack.peek();
                    //当前运算符优先级大于栈顶运算符优先级,或者栈顶为左括号时,当前运算符直接入栈
                    if(((c=='×' ||c=='÷')&&((peek=='+') || (peek=='-'))) || peek=='(') {
                        tempStack.push(c);
                    }
                    //否则,将栈顶的运算符取出并存入队列,然后将自己入栈
                    else {
                        after.add(tempStack.pop());
                        tempStack.push(c);
                    }                        
                } 
                else {
                    tempStack.push(c);
                }
            }
        }
        
        while(!tempStack.empty()) {
            after.add(tempStack.pop());
        }
        return after;
    } 
  
 

以上的三个方法可以实现上述算法。

 

3.检查用户输入

这个部分是由队友完成,我直接把他的实现方法贴上来:

1)打开习题册,创建一个文件选择器,由用户自己选择要练习的习题册。将选择的文件逐行读取,并显示在控制台上。前提是已经有生成过的题目文件在储存盘中。

2)输入答案。由用户自行根据习题册内容输入自己的答案,并以换行为题目分割。同样用文件选择器保存到目标路径下。

3)检查。分别将答案文件与用户用io流读取。逐行比较,并记录错题数与题号。将保存好的错题记录输出。

 

六、测试运行

输入生成题目的指令:

 

 

 

 

 

在项目的questionbank中生成了对应文件

结对项目 Java实现_第3张图片

 

 

题目文件的具体内容:

结对项目 Java实现_第4张图片

 

输入检查的指令

 

输入指令后成功验证答案

结对项目 Java实现_第5张图片

 

七、项目小结

通过这个项目,体会到了结对编程的好处,结对编程可以动用两个人的智慧,想到更多的好方法,除此之外,通过这个项目,对之前的Java知识也有了更深的认识。这个项目完成的不好的地方是没有过多的考虑程序运行时间的问题,导致了程序效能极低,以后需要在这方面多下功夫。

你可能感兴趣的:(结对项目 Java实现)