救救孩子

本次作业的相关要求的链接:https://edu.cnblogs.com/campus/fzzcxy/2016SE/homework/2104
211611302 洪康 杨慧德 211605242

一、预估与实际

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划
• Estimate • 估计这个任务需要多少时间 10 10
Development 开发
• Analysis • 需求分析 (包括学习新技术) 100 200
• Design Spec • 生成设计文档 20 20
• Design Review • 设计复审 20 20
• Coding Standard • 代码规范 (为目前的开发制定合适的规范) 20 20
• Design • 具体设计 60 60
• Coding • 具体编码 500 800
• Code Review • 代码复审 30 30
• Test • 测试(自我测试,修改代码,提交修改) 30 60
Reporting 报告
• Test Repor • 测试报告 60 60
• Size Measurement • 计算工作量 60 30
• Postmortem & Process Improvement Plan • 事后总结, 并提出过程改进计划 60 30
合计 1340

二、需求分析

这次的作业,需要在一二年级的基础功能上,新增小学三四年级的混合运算:

要求如下:

  • 运算符在2~4个【进一步细化要求:至少两个不同的运算符】
  • 可以加括号
  • 减法运算的结果不能有负数
  • 除法运算除数不能为0,不能有余数

三、设计

1. 设计思路

说明你如何设计这个程序

比如:

  • 这个程序有16个类,类分的比较多的原因是因为用了“简单工厂”对“加减乘除”进行了细分。
  • 这次主要新增的是PolishNotation类(将中缀表达式转为后缀表达式,并对后缀表达式进行运算并求值),以及TopicGraderThree类(负责生成三年级的混合运算题)。
    img

算法的关键的关键是什么?

img

这次的作业,代码基本可以分成如上三部分。
  1. 随机生成带括号的混合运算题,并且生成的题目的计算结果不能有负数、分数、余数。这个部分对我来说是最难的,卡了我很久,最后很无奈,只能先对括号的生成位置进行固定,当括号固定后,再对括号中的随机数进行一定的限制,就可以符合题目要求了。

    只是这样的代码,写起来又臭又长,为了让出的题目看起来随机,得自己写多种不同的情况,麻烦得很,在下周的“测试与优化”的作业中会将这部分代码优化。

  2. 第二部分是中缀表达式转后缀表达式,也就是调度场算法,主要是用到栈,把我们标准的四则运算表达式的转化成便于计算机计算的后缀表达式。中缀转后缀的规则:从左到右遍历中缀表达式的每个数字和符号,若是数字就输出,即成为后缀表达式的一部分,若是符号,则判断其与栈顶符号的优先级,是右括号或优先级不高于栈顶符号(乘除优先加减),则栈顶元素依次出栈并输出并将当前符号进栈,一直到最终输出后缀表达式为止。

  3. 第三部分是后缀表达式的计算,这部分是相对来说比较简单的,规则:从左到右遍历表达式的每个数字和符号,遇到是数字就进栈,遇到是符号,就将处于栈顶的两个数字出栈,进行运算,运算结果再次进栈,一直到最终获得结果

四、编码

1.关键代码:将中缀表达式转后缀表达式
	// 将中缀表达式转为后缀表达式
	public void getPostfix() {
	//用正则表达式匹配“纯数字”、“加减乘除”和“左括号”“右括号”
		Matcher m = Pattern.compile("(-?\\d+)|(\\+)|(\\-)|(\\*)|(\\/)|(\\()|(\\))").matcher(expression);
		while (m.find()) {
			// 纯数字,直接添加到后缀表达式中
			if (m.group().matches("-?\\d+")) {
				Postfix.append(" " + m.group());
			}
			// 栈为空,当前符号直接进栈
			else if (stack.isEmpty()) {
				stack.push(m.group());
			}
			// 当前符号为 “左括号”,直接进栈
			else if (m.group().equals("(")) {
				stack.push("(");
			}
			// 当前符号为“右括号”,不进栈,将栈顶元素依次添加到后缀表达式中
			// 直到“左括号”出栈为止
			else if (m.group().matches("\\)")) {
				while (true) {
					String pop = stack.pop();
					if (pop.equals("(")) {
						break;
					} else {
						Postfix.append(" " + pop);
					}
				}
			}
			// 运行到这里时,当前符号只可能是“加减乘除”中的某一个运算符了
			// 如果栈顶元素是“左括号”,m.group()直接进栈
			else if (stack.peek().equals("(")) {
				stack.push(m.group());
			} else {
				// 比较运算符的优先级
				comparePriority(m.group(), stack.peek());
			}
		}
		while (!stack.isEmpty()) {
			Postfix.append(" " + stack.pop());
		}
		Postfix.delete(0, 1);// 删掉处于字符串首位的空格
	}

	// 比较运算符的优先级
	private void comparePriority(String group, String peek) {
		// 如果当前符号为“+”或者“-”,则直接将栈顶符号出栈
		if (group.equals("+") || group.equals("-")) {
			do {
				Postfix.append(" " + stack.pop());
				if (stack.isEmpty()) {
					break;
				}
			} while (!(stack.peek().equals("(")));
			stack.push(group);
		} else {
			//如果栈顶符号是"+"或者"-",则当前符号直接进栈
			if (peek.equals("+") || peek.equals("-")) {
				stack.push(group);
			} else {
			//栈顶符号是"*"或者"/",则先把栈顶符号出栈,再比较
				do {
					Postfix.append(" " + stack.pop());
					if (stack.isEmpty()) {
						break;
					}
				} while (!(stack.peek().matches("(\\+)|(\\-)|(\\()")));
				stack.push(group);
			}
		}
	}

2.调试日志

记录编码调试的日志,请记录下开发过程中的 debug 历程

​ 写“调度场算法”的过程中,最常遇到的异常就是NullPointerException,经研究后发现,问题是出在了“出栈”的操作上,因为对空栈执行了pop操作,就会抛空指针异常。最后又发现,是因为当前栈顶符号出栈后,我没有考虑到栈可能会变成空栈的情况。

解决方法:加一个stack.isEmpty的判断。

3. 代码规范

请给出本次实验使用的代码规范:

  • 第一条:不使用“Magic Number”,都用有意义的英文名进行变量的命名
  • 第二条:不使用拼音进行命名
  • 第三条:采用驼峰式命名规则,类名以大写字母开头,变量名和方法名以小写字母开头
  • 第四条:代码中的命名均不能以下划线或美元符号开始,也不能一下划线或美元符号结束。
  • 第五条:左小括号和字符之间不出现空格;同样的,有小括号和字符之间也不出现空格。

并人工检查代码是否符合规范

五、测试

测试 预期结果 实际结果
不输入参数 输入有误!程序结束。 输入有误!程序结束。
只输入一个参数:100 输入有误!程序结束。 输入有误!程序结束。
-n 10 -grade 1 题目已经生成,详情请见out.txt 题目已经生成,详情请见out.txt
-n 10.5 -grade 1 输入有误!程序结束。 输入有误!程序结束。
-n ascc -grade 2 输入有误!程序结束。 输入有误!程序结束。
-n 10 -grade vsdv 输入有误!程序结束。 输入有误!程序结束。
-n 00001 -grade 3 输入有误!程序结束。 输入有误!程序结束。
-n 1000 -grade 2.3 输入有误!程序结束。 输入有误!程序结束。
-n 10 -grade 002 输入有误!程序结束。 输入有误!程序结束。
-n 10000 -grade 3 题目已经生成,详情请见out.txt 题目已经生成,详情请见out.txt
-n -1 -grade 3 输入有误!程序结束。 输入有误!程序结束。
1000 -n -grade 2 输入有误!程序结束。 输入有误!程序结束。!
-n 10 -grade -3 输入有误!程序结束。 输入有误!程序结束。
-n 1000 2 -grade 输入有误!程序结束。 输入有误!程序结束。
-grade 2 -n 1000 题目已经生成,详情请见out.txt 题目已经生成,详情请见out.txt
-grade 0.1 -n 800 输入有误!程序结束。 输入有误!程序结束。
-grade a1 -n 10 输入有误!程序结束。 输入有误!程序结束。
-grade 001 -n 20 输入有误!程序结束。 输入有误!程序结束。
-grade 1 -n 0000000002 输入有误!程序结束。 输入有误!程序结束。
-grade 0.1 -n 0.1 输入有误!程序结束。 输入有误!程序结束。
-n a1 -grade a1 输入有误!程序结束。 输入有误!程序结束。

六、总结

  • 关于结对编程:结对编程的一大好处就是能够集思广益,因为编程就是通过写代码来解决问题,而解决问题的方法肯定不是唯一的。通过结对编程,我们能够在编程的过程中,向对方阐述自己的想法,然后尝试去理解对方解题的思路,或许在这个过程中,我们就能够获取对方编程的思想和技巧,从而找到最优解。

  • 这次作业还有个让我觉得难以下手的一点是:如何在原一二年级的代码上扩展三年级的功能,我想要做到的是让原先的代码尽量少的修改,并且三年级的代码中能够复用一二年级的代码中的部分功能模块),从而减少代码的耦合性。最后虽然代码是敲出来了,但总觉得代码还不够“好看”。这点我会在新一周的作业中继续完善。