zoukankan      html  css  js  c++  java
  • 2016012032小学四则运算练习软件项目报告

    Coding.net源码仓库地址:https://git.coding.net/wanghz499/2016012032week2-2.git

    测试步骤:

    1.进入src文件夹

    2.在命令行输入javac -encoding utf-8 Main.java

    3.回车再输入java Main 20

    4.回车,将会在根目录下(与src同级)产生result.txt

    一、需求分析

        通过对题目要求的分析,我共提取出以下7个需求(实现带括号和真分数的附加功能):

        1.程序可从命令行接收一个输入参数n,然后随机产生n道加减乘除练习题。

        2.每个数字在 0 和 100 之间,运算符在3个到5个之间。

        3.每个练习题至少要包含2种运算符。

        4.所出的练习题在运算过程中不得出现负数与非整数。

        5.将学号与生成的n道练习题及其对应的正确答案输出到文件“result.txt”中。

        6.支持有括号的运算式,包括出题与求解正确答案。算式中存在的括号必须大于2个,且不得超过运算符的个数。

        7.支持真分数的加减法,并且每个分数都化到最简

    二、功能设计

        能够根据用户输入的参数n随机产生n道符合要求的练习题,自动算出答案,并将式子与答案以文档的形式呈现。并实现附加功能:支持有括号的运算、支持真分数的加减运算。

    三、设计实现

        我共设计了5个类,如图:

                       

        Creat类:负责随机产生一条带括号的至少2种运算符四则运算的式子,且有3-5个运算符

        Calculator类:负责筛选运算过程中不产生负数和小数的式子,并计算答案

        MakeFile类:负责产生result.txt文件,并将学号和产生的练习题写入文件

        properFraction类:负责产生真分数式子并计算答案

        Main类:主类,负责接收命令行的参数并启动程序

       5个类的相互调用关系为: 

                 

        比较重要的函数:

            Creat类:creatProblem():随机产生一条带括号的含3-5个运算符四则运算式子,

                                    若式子不符合条件,会递归直到产生符合条件的式子。

                     index(int n):产生运算符下标数组,并保证至少有2个不同的运算符。

            Calculator类:

                     algorithm(String s):结合了调度场算法和逆波兰表达式的求值,计算出式子的答案。

                     calculate(int a,int b,String stmp):计算式子每一部分的运算,排除运算过程中出现小数和负数的式子。

            ProperFraction类:

                     createProblem():随机产生一条含3-5个运算符的真分数加减运算式子,并计算出结果。

                     greatFactor(int x,int y):求最大公因数,用于化简

        函数间的逻辑关系:creatProblem()调用index(int n)algorithm(String s)algorithm(String s)调用calculate(int a,int b,String stmp),还有生成文件相关的方法就不列举了。

    四、算法详解

        本项目的关键在于Calculator类的计算,结合了调度场算法和逆波兰表达式(即后缀表达式)的求值,一步实现计算四则运算式子。

        关于调度场算法和逆波兰表达式求值,我花费了大量时间浏览博客理解它的实现过程,现总结如下:

        调度场算法的作用是将中缀表达式变为后缀表达式,它需要一个队列来装后缀表达式和一个栈来装符号。先从左到右遍历中缀表达式的每个符号和数字,若是数字就入队;若是符号,则判断其与栈顶符号的优先级,若该符号是右括号或其优先级低于或等于栈顶符号,则栈顶元素依次出栈并输出进入队列,并将当前符号进栈,一直到最终输出后缀表达式。

        逆波兰表达式求值步骤(只需要一个栈):

        1.先初始化一个空栈,开始遍历后缀表达式。

        2.如果字符是一个操作数,则令其入栈。

        3.如果字符是个运算符,则弹出栈里的两个操作数(一定会有两个数在栈里,因为是后缀表达式),进行运算,再把结果入栈。

        4.到后缀表达式末尾,从栈中弹出结果。

       

        理解了这两个算法后,就可将两个算法结合,只需两个栈就可一次性求出答案:

        1.初始化两个栈,分别是数字栈和符号栈。

        2.遍历中缀表达式,如果是数字,则入数字栈。

        3.如果是符号,则判断其与符号栈的栈顶符号的优先级。若当前符号是右括号或其优先级低于或等于栈顶符号,则栈顶元素依次出符号栈,并在数字栈弹出两个数进行相应运算,再使结果入数字栈,当前符号也入符号栈。

        4.当遇到等号,则将符号栈里的符号依次出栈,从数字栈弹出两个数进行相应运算,再把结果入数字栈,直到最后一个符号出栈。把数字栈的数字弹出,即为结果。

       

        至于符号的优先级,则使用Hashmap<String,int>建立多组键值对,使每个符号对应一个数值,数值越高说明优先级越高。

        优先级从小到大:)小于 + - 小于 ×÷ 小于 (

    五、测试运行

        进入src文件夹,在命令行输入javac -encoding utf-8 Main.java 将类编译成class文件,再输入java Main 20 运行class文件,这里先做一个非法输入和越界测试,如输入java Main e或java Main 1200

      

        再正常输入如java Main 20,将会在根目录下(与src同级)产生result.txt文件:

       

        

              

        测试完成!

    六、代码展示

        产生整数式子的方法:

    public static String createProblem(){
            Random random = new Random();
            String[] operator = {"+","-","×","÷"};
    
            int operatorCount = 3+random.nextInt(3); //操作符的个数3-5
            int[] num = new int[operatorCount+1]; //操作数的个数比操作符多1
            int[] index = index(operatorCount); //操作符的下标
            String s = new String();
    
            for(int j=0;j<operatorCount+1;j++){
                num[j] = random.nextInt(101); //产生0-100范围的操作数,random.nextInt(n)的取值范围是[0,n)
            }
    
    
            int choose = random.nextInt(2); //选择式子括号形态
    
            switch (operatorCount){
                case 3:{
                    if(choose==0){
                        s=num[0]+operator[index[0]]+"("+"("+num[1]+operator[index[1]]+num[2]+")"+operator[index[2]]+num[3]+")";//1+((2×3)-4)型
                    }else s="("+num[0]+operator[index[0]]+num[1]+")"+operator[index[1]]+"("+num[2]+operator[index[2]]+num[3]+")";//(1+2)×(3+4)型
                    break;
                }
    
                case 4:{
                    if(choose==0){
                        s="("+num[0]+operator[index[0]]+num[1]+")"+operator[index[1]]+num[4]+operator[index[3]]+"("+num[2]+operator[index[2]]+num[3]+")";//(1+2)×3÷(4-1)型
                    }else s=num[4]+operator[index[3]]+"("+num[0]+operator[index[0]]+num[1]+")"+operator[index[1]]+"("+num[2]+operator[index[2]]+num[3]+")";//3×(1+2)+(4÷2)型
                    break;
                }
    
                case 5:{
                    if(choose==0){
                        s="("+num[0]+operator[index[0]]+num[1]+operator[index[4]]+num[5]+")"+operator[index[1]]+"("+num[4]+operator[index[3]]+num[2]+")"+operator[index[2]]+num[3];//(6+2×3)-(1+2)×3型
                    }else s="("+num[0]+operator[index[0]]+"("+num[1]+operator[index[1]]+num[2]+operator[index[2]]+num[3]+")"+")"+operator[index[3]]+"("+num[4]+operator[index[4]]+num[5]+")";//(1+(2×3+4))-(6÷3)型
                    break;
                }
            }
    
    
            s+="="; //给式子加上等号
            int answer = Calculator.calculate(s);
    
            if(answer>=0){ //判断式子是否符合要求,凡是返回负数的就是不合格的
                s+=answer;
            }else {
                return createProblem(); //递归,直到产生合格的式子
            }
    
            return s;
    
        }

     

        保证式子里至少有2个不同操作符的方法:

        private static int[] index(int n,int m){ //产生操作符的下标数组
            Random random = new Random();
            int similar=0;
            int[] a = new int[n];
            for(int j=0;j<n;j++){
                a[j] = random.nextInt(m);
            }
            for(int j=1;j<n;j++){
                if(a[0]==a[j]) similar++;
            }
            if(similar==n-1) return index(n); //保证一个式子里至少有2个不同的操作符,若所有操作符下标都一样,则重新产生操作符下标
            else {
                return a;
            }
    
        }

       

        产生真分数式子并计算的方法:

    public String createProblem(){
            Random random = new Random();
            String[] operator = {"+","-"};
            int operatorCount = 3+random.nextInt(3); //操作符的个数3-5
    
            Create create = new Create();
            int[] index = create.index(operatorCount,2); //操作符的下标
    
            int sumx = 1+random.nextInt(10); //第一个数的分子1-10
            int sumy = 1+random.nextInt(10);//第一个数的分母1-10
            int greatFactor = greatFactor(sumx,sumy);
            sumx/=greatFactor; //化简
            sumy/=greatFactor;
    
            while (sumx>=sumy){
                sumx = 1+random.nextInt(10);
                sumy = 1+random.nextInt(10);
                greatFactor = greatFactor(sumx,sumy);
                sumx/=greatFactor;
                sumy/=greatFactor;
            }
    
            String s=sumx+"/"+sumy; //第一个数
    
            for(int i=0;i<operatorCount;i++){
                int numx = random.nextInt(25); //分子分母不宜过大
                int numy = 1+random.nextInt(25); //否则通分可能会产生很大的数导致溢出
                String currentOpreator = operator[index[i]];
                while (numx>=numy){ //当分子大于分母,即假分数,则重新生成
                    numx = random.nextInt(25);
                    numy = 1+random.nextInt(25);
                    greatFactor = greatFactor(numx,numy);
                    numx/=greatFactor;
                    numy/=greatFactor;
                }
                if(currentOpreator.equals("+")){  //加法
                    while(sumx*numy+sumy*numx>sumy*numy) //和为假分数
                    {
                        numx=random.nextInt(25);
                        numy=1+random.nextInt(25);
                        greatFactor=greatFactor(numx,numy);
                        numx/=greatFactor;
                        numy/=greatFactor;
                    }
                    sumx=sumx*numy+sumy*numx;
                    sumy=sumy*numy;
                }
                else {   //减法
                    while(sumx*numy-sumy*numx<0) //差为负数
                    {
                        numx=random.nextInt(25);
                        numy=1+random.nextInt(25);
                        greatFactor=greatFactor(numx,numy);
                        numx/=greatFactor;
                        numy/=greatFactor;
                    }
                    sumx=sumx*numy-sumy*numx;
                    sumy=sumy*numy;
                }
                s+=currentOpreator+numx+"/"+numy;
            }
    
            greatFactor = greatFactor(sumx,sumy);
            sumx/=greatFactor; //最终结果化简
            sumy/=greatFactor;
    
            if(sumx==0) s+="="+sumx;
            else if(sumx==1&&sumy==1) s+="="+sumx;
            else s+="="+sumx+"/"+sumy;
    
            return s;
    
        }

        判断非法输入和越界输入的方法(主方法):

    public static void main(String[] args) {
            int n = 0;
            try {
                 n = Integer.parseInt(args[0]);
                if(n>1000||n<1){
                    System.out.println("对不起,只允许输入1-1000的数字!");
                    return; //结束运行
                }
            }catch (NumberFormatException e){ //输入非数字字符等
                System.out.println("对不起,只允许输入1-1000的数字!");
                return; //结束运行
            }
    
            MakeFile.creatFile(n);
    
        }

        其他代码请见coding.net,就不一一展示了。

    六、PSP

    SP2.1

    任务内容

    计划共完成需要的时间(h)

    实际完成需要的时间(h)

    Planning

    计划

    26

    47

    ·        Estimate

    ·   估计这个任务需要多少时间,并规划大致工作步骤

    26

    47

    Development

    开发

    20

    40

    ·        Analysis

    ·         需求分析 (包括学习新技术)

    3

    5

    ·        Design Spec

    ·         生成设计文档

    0

    0

    ·        Design Review

    ·         设计复审 (和同事审核设计文档)

    0

    0

    ·        Coding Standard

    ·         代码规范 (为目前的开发制定合适的规范)

    0

    0

    ·        Design

    ·         具体设计

    3

    5

    ·        Coding

    ·         具体编码

    10

    15

    ·        Code Review

    ·         代码复审

    2

    5

    ·        Test

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

    2

    10

    Reporting

    报告

    6

    7

    ·         Test Report

    ·         测试报告

    5

    6

    ·         Size Measurement

    ·         计算工作量

    0.5

    0.5

    ·         Postmortem & Process Improvement Plan

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

    0.5

    0.5

    七、总结

        这次项目比我想象中的要难,原以为一两天就可以搞定,实际上花了整整4天时间在图书馆钻研。其实做这个项目我并没有完整地按照软件开发的步骤,也没有提前设计代码,想到哪就写到哪,导致敲代码的过程中遇到很多小问题,一遇到问题就得停下来去找相应的解决方法,经常是代码写了又删,删了又写,效率比较低,下次一定要事先设计,想好整个流程再写代码。此外,通过这次作业,我感受到了算法在项目中的重要性,算法是一个项目的灵魂。就如调度场算法和逆波兰表达式求值算法是这次作业的核心,我再也不敢说类似于不知算法有什么用之类的话了。

        原本我是没有实现分数加减的附加功能的,因为我潜意识里觉得它很难,所以压根没想过也不敢做这个附加功能。但是看了其他同学的博客后,发现很多同学都实现了,我分析他们的代码,突然觉得也不是很难了,几经思考后我最终也实现了分数的功能,开心不已。我想写博客的意义就在此吧,互相分享自己的学习成果,共同进步。很感谢那些愿意写博客分享技术的人,从他们的博客中真的可以学到很多东西!

        这4天,看了一篇又一篇博客,改了一段又一段代码,很疲惫却也很充实,让我感受到了全身心投入一件事情的快乐,专注与钻研,我喜欢这样的感觉。同时,也让我意识到自己的水平远比想象中的低,做一个四则运算就让我费了这么大的劲,说明我的水平真的还不够,我要好好努力。最后,还是忍不住分享独自完成一个小项目的喜悦,真的很开心,也给了我很大的鼓励!专注的感觉真好!

  • 相关阅读:
    【CH 5501】环路运输【DP】【单调队列】
    【CH 5501】环路运输【DP】【单调队列】
    【POJ 1456】Supermarket【并查集】
    【POJ 1456】Supermarket【并查集】
    【POJ 1456】Supermarket【并查集】
    【POJ 2411】Mondriaan's Dream【DP】
    数据结构实验之二叉树二:遍历二叉树
    数据结构实验之二叉树二:遍历二叉树
    36 静态数据成员与静态成员函数
    36 静态数据成员与静态成员函数
  • 原文地址:https://www.cnblogs.com/hiwhz/p/8620687.html
Copyright © 2011-2022 走看看