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

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

原文地址:https://www.cnblogs.com/anheqiao/p/8628501.html