本次做业是实现一个面向小学教师的四则运算题目生成程序,它的功能包括生成题目,以及根据题目计算结果并自动给同窗的做业打分。算法
从表格中能够看出,用于设计和测试的时间较多。express
PSP2.1 | Personal Software Process Stages | Time |
---|---|---|
Planning | 计划 | |
· Estimate | · 估计这个任务须要多少时间 | 20h |
Development | 开发 | |
· Analysis | · 需求分析 (包括学习新技术) | 1h |
· Design Spec | · 生成设计文档 | 1h |
· Design Review | · 设计复审 (和同事审核设计文档) | 1h |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 0.5h |
· Design | · 具体设计 | 2h |
· Coding | · 具体编码 | 3h |
· Code Review | · 代码复审 | 1h |
· Test | · 测试(自我测试,修改代码,提交修改) | 2h |
Reporting | 报告 | |
· Test Report | · 测试报告 | 3h |
· Size Measurement | · 计算工做量 | 0.5h |
· Postmortem & Process Improvement Plan | · 过后总结, 并提出过程改进计划 | 1h |
合计 | 16h |
根据老师的要求,所写程序功能大致分为两部分:编程
其中,第二个功能较为简单,就是一个经典的中缀表达式求值的问题,咱们能够用两个栈来随便维护一下就好。数组
第一个功能中要求较多:数据结构
不一样
的。不一样
定义为:less
任何两道题目不能经过有限次交换+和×左右的算术表达式变换为同一道题目数据结构和算法
首先分析第一个功能。函数
第一个要求很容易知足,经过控制一些参数便可实现。性能
第二个要求则须要对表达式进行运算,若是出现了负数,则交换减号两边的表达式。(或采起其余方式避免)学习
第三个要求至关于对题目的简化。仅用考虑不超过三个运算符的表达式
第四个要求则须要咱们同时处理分数和整数。咱们考虑使用一个统一的Factor类来实现。
第五个要求是这些要求中较难的一个。它要求咱们生成的表达式不能有相同的计算顺序。结合表达式的性质,咱们很容易就想到了树的最小表示法
。
即用二叉树的形式存放表达式,那么只要两棵树的最小表示不一样,则这两个表达式必定是不一样的。
而且因为表达式中的运算符数量较少,将树最小表示的过程能够暴力的来作。
ps:刚开始没有限制表达式的数量,那么若是要对表达式树进行最小表示,则须要树的每个节点均为子表达式,而且须要对表达式类重载小于运算符,实现起来较为繁琐。后来老师加上了对运算符的限制,则大大下降了编程复杂度。
类定义:
class Factor { int fraction; long long numerator, denominator; } class Expression { int opNum; Factor fac[FACTOR_SIZE]; char op[FACTOR_SIZE]; }
上述代码描述了本程序中关键的两个类:factor和Expression。
Factor类实现了分数和整数的统一处理(包括四则运算、输出等),其numerator属性为分子,denomination属性为分母,fraction属性标记了其是否为分数。
Expression类实现了表达式的处理,包括表达式的最小表示,处理出现负数和除数为零的状况等不合法状况以及输出。
有了上述基础,表达式的生成就变得很是简单了。
至此,第一个功能就初步设计完毕了。
第二个功能则没什么好说的,就是读入两个文件,将两个文件中保存的答案记录下来并进行计算和比较,从而得出结果。须要注意的就是一些文件读取的问题和文件内容不合法的判断。
对于Factor和Expression类,我最初在每一个类中都定义了一个print()方法,用于输出该类型的变量。然而以后发现因为须要生成多个表达式,且每一个表达式生成后都要向两个文件中写入数据,那么采用输出重定向的方式就较为不便。因而我将print()方法改成了与Java中相似的toString()方法,该方法会返回要输出的字符串。这样就能够方便的使用文件流的方式来存取数据了。
对于将表达式进行最小表示后的判重问题,我刚开始的思路是开一个不少维的map来存表达式的全部运算符和数值。大概相似这个样子:
map<Factor, map<Factor, map<Factor ,map<char, map <char, int> > > > > mp;
写完以后发现编译器给出了警告:
warning C4503: 'std::_Tree<std::_Tmap_traits<_Kty,_Ty,_Pr,_Alloc,false>>::erase' : decorated name length exceeded, name was truncated
查阅资料后发现,这是因为模板展开后的标识符长度超过了编译器限制。
那如何消除这个警告呢?后来我才想到,既然要求表达式惟一,那么直接将最小表示后的表达式字符串做为key就行了嘛……
因此仅须要一个set<string>
来存放不一样的表达式便可。
对于输入的参数中要求生成较多数量的表达式可是给出的表达式中数值得限制较小,就可能会出现凑不出要求的表达式数量的状况。对于这种状况,目前尚未想到完美的方法来保证全部可能的表达式均被生成。仅限定了一个阈值,若是在循环了这么屡次后仍然没有找到一个之前没有出现的表达式,那么就近似地认为没有更多的表达式了。
不过在实际应用中,应该不会出现这种极限的状况。
在代码写完后,我进行了Code Review和debug。
Code Review中,发现了两处写错的地方,debug中,发现了三个细节问题(判断除0错误时的逻辑问题、生成数值的时候fraction取值的问题以及文件读写的问题),并进行了改正。
在后续的测试中,发现了即便数值限制为100,中间结果也有可能会超过int的范围,所以必须采用long long类型的变量来存储数值。
测试用例以下:
ExpressionGeneration.exe -r 10000 -n 1 pause # generate Exercises.txt including a expression which values are less than 10000 # Answers.txt including the expression's answer
ExpressionGeneration.exe -n 5 pause # input error,display the help message`
ExpressionGeneration.exe -n 5 pause # input error,display the help message`
ExpressionGeneration.exe -n 5.1 -r 4 pause # input invalid, display the help message
ExpressionGeneration.exe -n -r 4 5 pause # input invalid, display the help message
ExpressionGeneration.exe -n 10000 -r 3 pause # generate Exercises.txt including 10000 expressions which values are less than 3 # Answers.txt including the answers of all the expressions
ExpressionGeneration.exe -n 10000 -r 2 pause # It doesn't have so many expressions
测试答案的测试用例因为文件较大,在此再也不展现,围绕文件读取合法性、题目格式的正确性、答案格式的正确性、题目文件和答案文件是否匹配、答案正确的题目数量等方面设计测试用例便可。
不过总的来讲,运行效率能够接受。toString()方法也没有太大的改进空间了。
完成此次我的项目的过程当中,我进一步体会到作工程与解决数学问题的区别。
在实际项目中,需求每每不是可以仅从题目要求中读出来的,更多的仍是要结合实际状况进行分析。同时,作项目的目的是为了服务生活,有时,咱们能够根据生活中的实际状况来对问题进行简化。(好比此次项目中对运算符个数的限制),而不是一味地构想难解的问题。
此外,我也发现了数据结构和算法基础在项目实现中的重要性。在本次项目中,若是可以想到平时常常用到的判断树同构问题的最小表示法的话,那么这就是一个很是熟悉且优美的问题了。(不过个人代码实现仍是不够优美……)