zoukankan      html  css  js  c++  java
  • 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

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

  • 相关阅读:
    C++笔记(2018/2/6)
    2017级面向对象程序设计寒假作业1
    谁是你的潜在朋友
    A1095 Cars on Campus (30)(30 分)
    A1083 List Grades (25)(25 分)
    A1075 PAT Judge (25)(25 分)
    A1012 The Best Rank (25)(25 分)
    1009 说反话 (20)(20 分)
    A1055 The World's Richest(25 分)
    A1025 PAT Ranking (25)(25 分)
  • 原文地址:https://www.cnblogs.com/anheqiao/p/8628501.html
Copyright © 2011-2022 走看看