• 2016012040+小学四则运算练习软件项目报告


     Coding仓库地址:https://git.coding.net/liuc144/2016012040homework.git

     一、需求分析

      1、脚本需要通过JAVA命令行运行。

      2、参数大小在1-1000之间,并且不允许非数字参数。

      3、每道练习题里至少有两道运算符,最多不能大于五道。

      4、练习题在运算过程中不能出现负数或者小数。

      5、将学号与生成的n道题练习题及其正确的答案输出到文件“result.txt”里,不能生成额外信息。

      6、由于算术题是出给小学生的,算术题的运算结果最好不要大于1000。

      7、算术题最好不要重复。

      8、程序可以重复利用,便于测试。

      9、小学生的算术题也要有难度梯度,要分为简单题和困难题。

    二、功能设计

     

     

      如图所示,四则运算项目包含三大模块

      1、生成算术题:通过public String[] getCaculate(int n,String pattern){....};方法来获得随机算术题,算术题是依靠随机运算符数组和随机数字通过String类型的+来生成的。它分两种模式,一是只有加减运算符,的EASY模式和四则运算符都存在的HARD模式.此番设计是为了照顾水平不高的学生。

      2、中缀表达式转换后缀表达式为什么要进行转换呢?我并没有看多少老师给的调度场算法,因为我觉得我的方法也很简单。算表达式的值仅仅只用转换式子,再对后缀表达式求值就行

      原表达式即中缀表达式是人最习以为常、是我们最容易接受的形式。如中缀表达式:

              A+B(CD)E/FA+B∗(C−D)−E/F

      我们很容易就能理解表达式的数学含义,但是要把表达式丢给计算机去处理,它并不能像人一样有逻辑的去判断先处理哪一步,后处理哪一步,它只会严格的按照从左只有执行,因此为了符合计算机运行方式,必须把原表达式转换为对应的后缀表达式才行。

      3、后缀表达式求值:
      运用后缀表达式进行计算的具体做法:
      建立一个栈S 。从左到右读表达式,如果读到操作数就将它压入栈S中,如果读到n元运算符(即需要参数个数为n的运算符)则取出由栈顶向下的n项按操作数运算,再将运算的结果代替原栈顶的n项,压入栈S中 。如果后缀表达式未读完,则重复上面过程,最后输出栈顶的数值则为结束。
    三、设计实现
      

    我的设计思路很简单,解决这个项目只需要三个模块这三个模块是逐级调用的关系,Main模块调用Caculate模块,Caculate模块调用ToSuffix模块。

    他们的逻辑关系:

       1、Main模块,当老师运行脚本输入命令时,用来判断输入是否非法,并根据不同的非法信息给予提示。

      其精华是

      /**
      * 利用正则表达式判断输入是否合法
      */
      result = args[0].matches("[0-9]+");

      /**  

      *用来判断是否存在pattern的命令(hard模式或者easy模式,默认状态下是hard模式)

      */  

      if(args.length==1)
        arr = test.getCaculate(n,"hard");
      else
        arr=test.getCaculate(n, args[1]);

      最后输出到result.txt文件

      

      2、Caculate模块主要功能就是生成随机表达式,然后调用ToSuffix模块求值判断是否非法。

      这里简单的说一下我是如何设计这个模块的。

      方法参数是n,n是算术题的题数。

      用了两个字符数组,一个是easy模式会包含的操作符,一个是hard模式会包含的操作符。

       for循环,每一次循环都会生成一个符合题意的表达式,它包括两个小模块。

                      

       3、ToSuffix模块

      它分为两个小模块,一个是toSuffix(String index)用来将中缀表达式转化为后缀表达式,另一个是后缀表达式求值。两个算法解决问题,详细的算法在后面解释

    四、算法详解

      1、  生成表达式

      首先定义字符数组

      private final char[] operator = { '+', '-', '*', '/' };

        for循环,每一次循环都会得到一个合法的算术题

          一个子for循环,rOperator变量用来操作3~5个运算符的随机性

          一个while循环,用int random = (int) (Math.random() * 100);生成100以内的随机数,int r = (int) (Math.random() * 4);在字符数组中求得随机操作符(hard模式)

          调用ToSuffix模块的函数计算算术题的值。double result = Double.parseDouble(tx.suffixToArithmetic(tx.toSuffix(str)));

          if (result == (int) result && result >= 0 && result <= 1000)如果结果满足无负数,无小数,结果小于1000就判定合法。

        将合法的式子放入arr[]数组内。

    public String[] getCaculate(int n,String pattern) {
    		String[] arr = new String[1010];
    		String str = null;
    		int count = 0;
    		for (int i = 0; i < n; i++) {									// for循环,每一次循环都会得到一个合法的算术题
    			int rOperator;
    			for (;;) {													//一个子for循环,rOperator变量用来操作3~5个运算符的随机性
    				rOperator = (int) (Math.random() * 5 + 6);
    				if (rOperator % 2 == 0)
    					break;
    			}
    			while (true) {												//一个while循环,用int random = (int) (Math.random() * 100);生成100以内的随机数,
    				for (int j = 1; j <= rOperator; j++) {					//int r = (int) (Math.random() * 4);在字符数组中求得随机操作符(hard模式)
    					int random = (int) (Math.random() * 100);
    					if (j == 1)
    						str = random + "";
    					if (j % 2 == 0)
    						str += random;
    					else {
    						if(pattern=="hard"){
    							int r = (int) (Math.random() * 4);
    							str += operator[r];
    						}else{
    							int r = (int) (Math.random() * 2);
    							str += Eoperator[r];
    						}
    						
    					}
    				}
    				/**
    				 * 验证算术题是否非法
    				 */
    
    				ToSuffix tx = new ToSuffix();														//调用ToSuffix模块的函数计算算术题的值。double result = Double.parseDouble(tx.suffixToArithmetic(tx.toSuffix(str)));
    				double result = Double.parseDouble(tx.suffixToArithmetic(tx.toSuffix(str)));		//if (result == (int) result && result >= 0 && result <= 1000)如果结果满足无负数,无小数,结果小于1000就判定合法。
    				if (result == (int) result && result >= 0 && result <= 1000) {
    						arr[count] = str + " = " + (int) result;	//将合法的式子放入arr[]数组内。
    						count++;
    						break;
    				}
    				
    			}
    		}
    		return arr;
    
    	}
    
    }
    

      

      2、  中缀表达式转后缀表达式

      算法思想

      将中缀表达式转换为后缀表达式的算法思想
      ·开始扫描;
      ·数字时,加入后缀表达式;
      ·运算符:
      a. 若为 '(',入栈;
      b. 若为 ')',则依次把栈中的的运算符加入后缀表达式中,直到出现'(',从栈中删除'(' ;
      c. 若为 除括号外的其他运算符, 当其优先级高于除'('以外的栈顶运算符时,直接入栈。否则从栈顶开始,依次弹出比当前处理的运算符优先级高和优先级相等的运算符,直到一个比它优先级低的或者遇到了一个左括号为止。
      ·当扫描的中缀表达式结束时,栈中的的所有运算符出栈;
    优先级的设置

    private static final Map<Character, Integer> basic = new HashMap<Character, Integer>();

    static {
      basic.put('-', 1);
      basic.put('+', 1);
      basic.put('*', 2);
      basic.put('/', 2);
      basic.put('(', 0);// 在运算中 ()的优先级最高,但是此处因程序中需要 故设置为0
    }

    static静态代码块的作用是对映射进行初始化,用static可以节省空间和时间,提升程序效率。

    也可以定义函数通过识别不同的操作符返回相应的值,'(',')'的值>'*','/'的值>'-','+'

      3、  后缀表达式求值
      
    设置一个栈,开始时,栈为空,然后从左到右扫描后缀表达式。
    若遇操作数,则进栈;若遇运算符,则从栈中退出两个元素,先退出的放到运算符的右边,后退出的 放到运算符左边,运算后的结果再进栈,直到后缀表达式扫描完毕。此时,栈中仅有一个元素,即为运算的结果。
      public double suffixToArithmetic(String exp) {
    	 		Stack<Double> stack=new Stack<Double>();	 //使用正则表达式匹配数字 
    	 		pattern=Pattern.compile("\d+||(\d+\.\d+)"); //将字符串分割成字符串数组 String
    	 		strs[]=exp.split(""); 
    	 		for (int i = 0; i < strs.length; i++) {
    	 			if(strs[i].equals("")) continue; //如果是数字,则入栈
    	 			if((pattern.matcher(strs[i])).matches()) {
    	 				stack.push(Double.parseDouble(strs[i])); }else { //如果是运算符,则弹出运算符,计算结果
    	  				double b=stack.pop(); double a=stack.pop(); //将运算结果重新压入栈中
    	 	 			stack.push(calcute(a, b, strs[i])); } } return stack.pop();
    	 			 }
    	 
    	 		根据符号计算最终结果 
    	 public static double calcute(double a,double b,Stringsimble) {
    	  		 if(simble.trim().equals("+")) return a+b;
    	 		 if(simble.trim().equals("-")) return a-b; 
    	 		 if(simble.trim().equals("*")) return a*b;
    	 		 if(simble.trim().equals("/")) return a/b; 
    	 			return 0;
    	 }
    

      五、测试运行

    一、正常运行

     

    二、Easy模式下生成的算术题

     

    三、输入错误的几种情况

         1、输入负数

       2、输入大于1000的数

      3、输入非法命令

      4、输入多个命令

      5、没有输入命令

    六、总结

       花了将近30~40个小时来完成这个作业,终于要有了尾声。面对这个项目我并没有犯怵,因为总体代码都服从我刚开始构思出的框架内。这作业难吗?不难!但为什么花了这么长时间?编码基础差,项目编写流程不熟悉。说实话,一开始写这份项目是没有头绪的。所以我从代码中寻找思路,越写代码越了解这个项目。我写了三个模块,方便我设计项目。

     有了这三个模块,我就可以从大局出发,一点一点从上而下慢慢写。光是设计着三个模块和完成基本部分代码就花了编码时间的3分之一,也就7个小时,两个小时构思出模块,五个小时完成基本部分代码。后来的14个小时解决的是生成题目的算法,和表达式转换和后缀表达式求值的算法。

    七、PSP

    PSP

    任务内容

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

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

    Planning

    计划

    1

    0.5

    ·        Estimate

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

    0.5

    1

    Development

    开发

    15

    33

    ·        Analysis

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

    1

    4

    ·        Design Spec

    ·         生成设计文档

    1

    2

    ·        Design Review

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

    0

    0

    ·        Coding Standard

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

    1

    1

    ·        Design

    ·         具体设计

    1

    3

    ·        Coding

    ·         具体编码

    9

    20

    ·        Code Review

    ·         代码复审

    1

    1

    ·        Test

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

    1

    2

    Reporting

    报告

    6

    8

    ·         Test Report

    ·         测试报告

    2

    4

    ·         Size Measurement

    ·         计算工作量

    1

    2

    ·         Postmortem & Process Improvement Plan

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

    3

    2

    很明显,预期和现实有着过大的差距,原因为何?敲代码不熟练,项目流程不熟悉。而且,我做这个项目也没有按照这个流程走。我也不太喜欢这个流程,我喜欢的流程是,自己设计框架,然后写基础代码,在写代码的时候就会注意到很多不敲打时注意不到的细节。基础铺垫好了,再去写设计文档,这样写出来的文档会高一个档次。因为你敲代码的时候会不停的对项目加深理解,理解越深,写出来的需求就越详细,功能设计就越完善。

  • 相关阅读:
    Web SSH 客户端Ajaxterm安装
    Ubuntu Manpage: ajaxterm
    Web工程师的工具箱 | 酷壳
    EF架构~二级域名中共享Session
    VS~通过IIS网站启用"域名"调试
    EF架构~豁出去了,为了IOC,为了扩展,改变以前的IRepository接口
    MVVM架构~knockoutjs系列之文本框数符长度动态统计功能
    JS~jwPlayer为js预留的回调方法大总结
    晒网站:应用诺贝尔奖得主罗斯匹配算法的交友网站,具有更符合用户大网撒鱼心理的新颖用户使用模式
    ZOJ 2334(Monkey King-左偏树第一题)
  • 原文地址:https://www.cnblogs.com/anheqiao/p/8628501.html
Copyright © 2020-2023  润新知