四则运算(Java) 陈志海 邓宇
Github项目地址
PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 40 | 40 |
· Estimate | · 估计这个任务需要多少时间 | 40 | 40 |
Development | 开发 | ||
· Analysis | · 需求分析 (包括学习新技术) | 60 | 120 |
· Design Spec | · 生成设计文档 | 39 | 30 |
· Design Review | · 设计复审 (和同事审核设计文档) | 20 | 50 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 20 | 20 |
· Design | · 具体设计 | 60 | 120 |
· Coding | · 具体编码 | 500 | 720 |
· Code Review | · 代码复审 | 60 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 90 | 120 |
Reporting | 报告 | ||
· Test Report | · 测试报告 | 40 | 40 |
· Size Measurement | · 计算工作量 | 20 | 20 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 60 | 80 |
合计 | 1000 | 1400 |
功能要求
题目
实现一个自动生成小学四则运算题目的命令行程序
功能(已全部实现)
- 使用 -n 参数控制生成题目的个数。
- 使用 -r 参数控制题目中数值(自然数、真分数和真分数分母)的范围。
- 生成的题目中计算过程不能产生负数,也就是说算术表达式中如果存在形如e1 − e2的子表达式,那么e1 ≥ e2。
- 生成的题目中如果存在形如e1 ÷ e2的子表达式,那么其结果应是真分数。
- 每道题目中出现的运算符个数不超过3个。
- 程序一次运行生成的题目不能重复,即任何两道题目不能通过有限次交换+和×左右的算术表达式变换为同一道题目。
- 在生成题目的同时,计算出所有题目的答案,并存入执行程序的当前目录下的Answers.txt。
- 程序应能支持一万道题目的生成。
- 程序支持对给定的题目文件和答案文件,判定答案中的对错并进行数量统计,统计结果输出到文件Grade.txt。
效能分析
参数如下图,n值为100000(即10W道),
观察下图的性能曲线,可以看出大概需要8秒钟时间生成,使用内存峰值大约在150MB左右。
我们再次提高n值上限(1000000 100W道),适当调高r值(1000)。
可以看出大概需要54秒钟生成,使用内存峰值大约在840MB左右。
我们觉得这个运行时间有点过长,试着增大r值,看看题目树生成碰撞导致的效率减慢占比是多少。
我们的r值增大到5000,生成时间就从54秒下降到29秒,有一个很大的改进。
设计实现过程
数值生成
题目提及了三种数字的表示形式,一种是整数(例:3),一种是分数(例:3/5),一种是带分数(例:2’3/5)。这三种数字我们统一使用一个Num类,并提供随机方式生成,然后根据不同部分的值产生不同的导出结果。Num类维护一个int类型的num[3]数组,刚开始被初始化全0。
然后生成逻辑如下:
- 获得整数上限r
- 对于分母部分获得随机[0,r]内的一个数
- 如果分母生成值为0,将分子部分赋给整数部分,此时我们获得了一个全为0的数,结束生成。
- 如果分母生成值不为0,对于分子部分获得随机[0,r*r]内的一个数,此时也保证了最后生成的结果绝对不会大于r。
- 将分子/分母的值赋给整数部分,剩余部分赋给分子,保证带分数中分子不会大于等于分母。
- 如果此时分子生成值为0,将分母也置为0,此时我们获得了一个全为0的数,结束生成。
- 对分子和分母进行辗转相除法,获得最简分式。结束生成。
然后在Num类也内建了一个函数,用来返回字符串类型的数值表达式:
- 如果分母或分子部分为0,直接返回整数部分。
- 如果整数部分为0,因为分子部分也不为0,直接返回一个分式形式。
- 否则返回一个带分数形式。
然后封包了对应的加减乘除法,每次计算返回一个新的Num对象,包含计算完毕的结果。
算式生成
生成算式简单,但是要在生成之后判重需要花费心思去思索。
我们采用了二叉树的数据结构来存储整个算式,在非叶子节点存储运算符,叶子节点存储数值。这样,我们就很容易得到一颗符合波兰表达式的二叉树。我们计算和判重只需要对整个二叉树进行一次前缀遍历即可。
结对约定了二叉树的运算符拓展必然是只拓展二叉树的左子树,这样的约定既方便了代码的生成,使得递归生成的实现十分简洁。这样也同时避免了另外一个问题,就是对于加减法或乘除法来说,有个暗含的优先计算减再算加,先计算除再算乘的细节。例如,1+2的外面套一层括号,对整个式子的结果不会有影响,而1-2的外面套一层括号,则会有影响,故要先算减再算加。
我们使用一个运算符从数字映射到字符串加减乘除的常量数组存储在CTree类中,对于每一次生成,有以下方式初始化:
- 获得父对象的指针,深度,和整数上限r。
- 记录父对象的指针,如果深度为0,对本对象管理下的Num类对象进行初始化构造,本类构造结束。
- 如果深度不为0,对于运算符部分获得随机[0,3]内的一个数,对于加减乘除,对左子树迭代传递构造,右子树进行深度为0的传递构造,参考2。
这样我们就可以获得一颗符合上述节点描述的二叉树了。之后是判重部分的逻辑:
维护了三个方法
- isNumber()用左右子树是否有对象指向来确认该节点是不是数值节点。
- isRoot()用父对象是否有对象指向来确定该节点是不是根节点。
- equals()比较树是否相同,暴露供外部调用。
利用上面三个方法和Num的部分函数来进行判重。
- 如果节点类型不同,直接认为是不同的。
- 如果是数值节点,直接判定Num类中num三个部分是否相同(num已经是最简式)来判定是否重合。
- 如果是运算符节点,判定运算符类型是否相同来判定是否重合。
问题集生成
在CTree上再封装一层,我们利用Calculation类来管理二叉树,同时该类也是判重的接口,以及运算符个数的生成者。
问题集的处理,因为在Ctree部分处理重合的算法选择的好,这部分就不需要太费劲。我们直接建立一个HashSet,Calculation类重写了hashCode()函数,返回答案的字符串hashCode,来第一步去重。若答案一样,也就是hashCode一样,HashSet会调用Calculation类的equals退化成链表保存。
设计实现过程
面向对象编程,将文件按功能抽离成类,每个类暴露对应的函数。
代码说明
数值生成逻辑:
public Num(int r) {
num = new int[3];
num[2] = (int)(Math.random() * r);
if(num[2] == 0) {
num[0] = num[1];
num[1] = 0;
} else {
num[1] = (int)(Math.random() * r) * (int)(Math.random() * r) % num[2] * r;
num[0] = num[1] / num[2];
num[1] = num[1] % num[2];
if(num[1] == 0) {
num[2] = 0;
}
if(num[1] != 0 && num[2] != 0)
{
int gcdNumber = gcd(num[1], num[2]);
num[1] /= gcdNumber;
num[2] /= gcdNumber;
}
}
}
数值返回逻辑:
public String getFromatNumber() {
String str = new String();
if(num[2] == 0) {
str += String.valueOf(num[0]);
} else {
if(num[1] == 0) {
str += String.valueOf(num[0]);
}
else if(num[0] != 0) {
str += String.valueOf(num[0]) + '\''
+ String.valueOf(num[1]) + '/' + String.valueOf(num[2]);
} else {
str += String.valueOf(num[1]) + '/' + String.valueOf(num[2]);
}
}
return str;
}
算式生成逻辑:
public CTree(CTree parent, int depth, int r) {
this.parent = parent;
if(depth == 0) {
lchild = null;
rchild = null;
num = new Num(r);
} else {
operationType = (int)(Math.random() * 4);
lchild = new CTree(this, depth - 1, r);
rchild = new CTree(this, 0, r);
}
}
算式判重逻辑:
public boolean equals(CTree s) { //比较树是否相同 ,不区分左右子树位置不同
if(isNumber() != s.isNumber()) {
return false;
}
if(isNumber() == true) {
return num.equals(s.num);
}
if(operationType != s.operationType) {
return false;
}
return (lchild.equals(s.lchild) && rchild.equals(s.rchild))
|| (lchild.equals(s.rchild) && rchild.equals(s.lchild));
}
测试运行
生成文件
批改
代码覆盖率
项目小结
此次的结对编程,由陈志海同学负责前期架构搭建,项目测试,而我负责前期编码和博文编写。
我们讨论项目基本构思,在每一个点都是先做好大概设计,每一个类需要有什么方法。还讨论了数值生成如何操作,才能保证在范围内;二叉树存放算式的每一部分该怎么设计,才能尽可能准确高效的生成一个算式;整个计算过程中出现负值的处理等等。
然后陈志海同学把架构交给我,两个协作着,我来实现具体细节,他来在每一步编写完毕后提出问题,解决一些明显的逻辑BUG。我来提供一些算法和结构上的优化。
在项目大体编写完毕后,由陈志海同学进行项目测试,解决一些细节和用户体验方面的问题,同时完成了批改的功能。
我从此次项目中收获到了,两个人的力量,不仅仅体现在编码的速度方面,对于提升代码质量、技术精进方面都有很大的助益。同时明白了团队约定代码规范,思路交流方面是需要多多注意的。
四则运算(Java) 陈志海 邓宇的更多相关文章
- 3星|《陈志武金融投资课》:金融改善社会,A股投资策略
从历史上的金融说起,介绍金融的基本知识.理念.大事.重要人物.也有一些A股投资策略和A股政策点评. 引用了不少学术研究成果做证据.讲历史的部分,功力比专业历史学者稍逊,毕竟这不是作者的专业. 我读后认 ...
- 四则运算 Java 杨辉鹏,郑冠华
四则运算 Java 杨辉鹏,郑冠华 GitHub链接:https://github.com/yanghuipeng/arithmetic 项目相关要求 使用 -n 参数控制生成题目的个数,例如 -n ...
- 「一入 Java 深似海 」系列课程
第一期 「一入 Java 深似海 」系列课程 - 第一期 第一节:Java 语言基础
- 陈志生:德国信贷工厂风控模式对P2P的启发
上海合盘金融信息服务股份有限公司董事长陈志生 和讯银行消息 "2014中国金融论坛"于5月14-15日在北京召开,本次论坛主题为“全面深化金融体制改革与实体经济增长”.和讯网作为指 ...
- 四则运算Java语言实验设计过程1
题目要求: 像二柱子那样,花二十分钟写一个能自动生成三十道小学四则运算题目的 “软件”.要求:除了整数以外,还要支持真分数的四则运算(需要验证结果的正确性).题目避免重复.可定制出题的数量. 设计思路 ...
- 四则运算(Java)--温铭淇,付夏阳
GitHub项目地址: https://github.com/fxyJAVA/Calculation 四则运算项目要求: 程序处理用户需求的模式为: Myapp.exe -n num -r size ...
- 四则运算 Java 实现 刘丰璨,王翠鸾
四则运算 GitHub仓库 功能实现 [x] 使用 -n 参数控制生成题目的个数,并且根据解空间限制用户设定的范围(如 range == 2 时,用户却要求生成 10000 道题目,这明显不合理) [ ...
- 四则运算 Java (于泽浩,袁浩越)
GitHub 地址 一. 项目要求 题目 实现一个自动生成小学四则运算题目的命令行程序. 需求(全部完成) 使用 -n 参数控制生成题目的个数 Myapp.exe -n 10 使用 -r 参数控制题目 ...
- 结对编程四则运算--JAVA实现(徐静、林文敏)
Github项目地址 项目相关要求 -n 参数控制生成题目的个数 (√) Myapp.exe -n 10 // 将生成10个题目 -r 参数控制题目中数值(自然数.真分数和真分数分母)的范围 (√) ...
随机推荐
- Spring、Spring MVC、MyBatis整合文件配置详解2
使用SSM框架做了几个小项目了,感觉还不错是时候总结一下了.先总结一下SSM整合的文件配置.其实具体的用法最好还是看官方文档. Spring:http://spring.io/docs MyBatis ...
- $GLOBALS超级全局变量(PHP学习)
1.$GLOBALS是一个数组,里面有所有的全局变量 2.$GLOBALS是超级全局变量,函数内部可以通过它直接操作全局变量.(严重不推荐,因为违反了封装原则) 3.通过$GLOBALS操作全局变量, ...
- UBUNTU readelf的安装
大多数情况下,linux环境上默认可能都装有readelf,但是也有少数情况可能没有装,我自己用的ubuntu的linux虚拟机就没有装readelf.readelf本身是一个分析elf很好用的工具. ...
- Win10的Hosts文件修改后无法保存的问题解决方法,实测可以
1.hosts文件是什么?有什么作用呢? Hosts是一个没有扩展名的系统文件,可以用记事本等工具打开,其作用就是将一些常用的网址域名与其对应的IP地址建立一个关联“数据库”,当用户在浏览器中输入一个 ...
- lua语法基本
lua的下载 http://luabinaries.sourceforge.net/点击所要下载的版本比如我下的是5.3.3https://sourceforge.net/projects/luabi ...
- java多线程练习实例
总结: 循环的使用率蛮高,Thraed.sleep(),try-catch语句 package com.aa; public class West { public static void main( ...
- SqlServer——for xml path
for xml path 就是将 sql 查询出来的内容以XML的格式显示出来.参考网站MSDN:将 PATH 模式与 FOR XML 一起使用. 先创建测试用的表格: create table SZ ...
- git rm简介
本文翻译整理自:http://web.mit.edu/~mkgray/project/silk/root/afs/sipb/project/git/git-doc/git-rm.html 在git中我 ...
- 使用ssh-agent管理密钥
ssh-agent是ssh代理程序,使用ssh-agent可以方面管理私钥. ssh-agent主要使用在如下两个场景: 1.使用不同的密钥连接不同主机,每次连接都要指定私钥; 2.当私钥设置了密码, ...
- 在linux中read、write函数
read函数从打开的设备或文件中读取数据. #include<</span>unistd.h> ssize_t read(int fd, void *buf, size_t ...