一.项目时间规划与实际用时
PSP2.1 |
Personal Software Process Stages |
预计时间/h |
实际时间/h |
Planning |
计划 |
||
· Estimate |
· 估计这个任务需要多少时间 |
8 |
10 |
Development |
开发 |
||
· Analysis |
· 需求分析 (包括学习新技术) |
2 |
3 |
· Design Spec |
· 生成设计文档 |
0.5 |
0.5 |
· Design Review |
· 设计复审 (和同事审核设计文档) |
0.5 |
0.5 |
· Coding Standard |
· 代码规范 (为目前的开发制定合适的规范) |
0.2 |
0.5 |
· Design |
· 具体设计 |
1 |
3 |
· Coding |
· 具体编码 |
6 |
10 |
· Code Review |
· 代码复审 |
5 |
8 |
· Test |
· 测试(自我测试,修改代码,提交修改) |
2 |
5 |
Reporting |
报告 |
||
· Test Report |
· 测试报告 |
1 |
1.5 |
· Size Measurement |
· 计算工作量 |
0.2 |
0.2 |
· Postmortem & Process Improvement Plan |
· 事后总结, 并提出过程改进计划 |
1 |
1 |
合计 |
16.9 |
43.2 |
这次编程自己做的非常失败。预估完全失误,最终完成整个项目需要的时间是预估的两倍多。可能是因为暑假长时间没有从事过程序方面的设计了,很多项目的细节与坑点都没有考虑到,导致在开发阶段(尤其是在具体编码、代码复审阶段以及测试阶段)花费了十分长的时间,导致整个项目的工作时间大幅上升,最终也影响了最终的提交。
二.改进程序性能
个人在总体算法上花费的时间并不是很长,大约在3小时左右。在我看来,算法的核心便是如何生成计算式,同时保证能够不重复且不产生负数,同时分母要小于range。
1. 对于随机算法的看法:一开始自己跟一些同学计划选择随机生成操作数,之后在计算中进行条件限制,不符合则淘汰重新生成。但经过考虑后,觉得这样生成往往会影响效率。随机数种子的选取直接影响到最终生成的操作数,具有极大的不确定性,重复性以及是否会生成负数上都无法保证。而在运算符的选取上,随机算法更是会让重复的几率加大。
2. 最终生成算法:因为最多只有三个运算符,所以可以轻易计算出运算符的排列个数。将它们编号,按序输出可以极大增加程序效率。
之后,便是问题:重复性与操作数要求。因为操作数有限制范围,于是可以根据给定的range与要求的题目来进行计算,得出一个大致的”间隔gap“,即循环时操作数的递减值,该值保证小于range。首先以range-gap为初始值,对应运算符全排列生成一遍,继而以gap为递减值继续循环,直到生成题目数量达到要求。这期间除了range-n*gap外,会让其他的操作数尽可能小,并将range-n*gap放在最开始,尽可能减少产生负数的情况。此外再加以函数判断,若遇到产生负数的式子,则重新生成。
ps: 算法存在弊端,或者直接说是BUG(时间原因,现在仍没能完善,之后会尽力),操作数选取上的过于宽松会导致当range较小,而题目数量极多时,无法生成足够数量的题目。例如: n=10000,r=3,会无可避免的生成失败. 这是操作数选取策略上的失败。
核心代码:
for (num_title=1;num_title<=titlenum&&first_num.d_val.mol>0;num_title++)
{
temp=num_title;
for (k=1;k<=100&&temp<=titlenum;k++){
for (i=1;i<=(level.d_val.mol/level.d_val.deo);i++,temp++)
{
struct dat l={0,one};
struct title t=iterator(k,first_num,gap,l);
if (t.result.d_val.mol<0)
{
temp--;
continue;
}
my_print(t,temp);
my_title[temp]=t;
}
}
if (temp!=num_title)
num_title=temp-1;
first_num=operation(&first_num,&gap,2);
}
3. 最终匹配算法:因为自己生成算法的特殊性,匹配式子这一方面就要重新规划。就选取了最常规的后缀变前缀,再计算前缀表达式值的算法。继而换算成分数结构体进行匹配。期间关于栈的弹栈操作出现了错误(虽然数据结构课是练习过的),代码实现以及后期调试测试又花费了很长时间......
4. 操作数的有效性检测:因为从生成时尽可能的避免了重复操作数以及分母越界,所以检测时只需在进行一次运算过后检查结果是否小于0即可。
性能分析
(很多同学都无法使用工具。去找同学帮忙分析,但因为时间紧,采样时间不够,采集的例子过少。之后会完善)
三.后期遇到的主要困难
1.生成计算式的细节:这是一开始设计时没有在意的。例如当range较小(但相对于题目数量而言不至于根本无法生成足够计算式)时,可能得到的最小gap会大于range。考虑后决定再设置一个参数用来对操作数进行控制——会让同一个运算符序列对应的式子的操作数种类增多从而达到数量要求。具体实现为,当上述情况发生时,参数便累加1,gap缩小为原来1/2,直到满足gap<range。
2.栈溢出:一开始对局部变量数组的限制做的不到位,导致很多函数直接发生栈溢出。
3.中缀与后缀:在一个弹栈操作上出了错,后期花费大量时间才找到。。。
四.测试用例
1.Operation.exe -n 10 -r 1
2.Operation.exe -n 10 -r 2
3.Operation.exe -n 20 -r 6
4.Operation.exe -n 5000 -r 30
5.Operation.exe -n 10000 -r 40
6.Operation.exe -n 10 (我的程序里认为是不合法 -n -r 必须同时出现)
7.Operation.exe -r 2
8.Operation.exe -r 10 -n 10
9.Operation.exe -e A.txt
10.Operation.exe -a A.txt
11.Operation.exe -a A.txt -e B.txt
12.Operation.exe -e A.txt -a B.txt (因为要求提到默认提供的文件都是规范的,所以没有考虑A B 本身存在不合法的问题)
程序正确性:1.生成方面 首先保证了gap小于range;第二,因为保证了当运算符顺序相同时,式子中至少有一个运算数是不同的,从而可以杜绝重复式子的出现;而且在一次运算 出现负数时可及时发现并处理。
2.运算方面 采用中缀变后缀,再计算后缀表达式的值
五.个人总结
1.了解了设计与实现的距离。再简单的设计,往往实现起来也是充满困难。不重视、低估项目终究会导致整个项目的失败
2.设计是整个项目的关键:软件工程中的设计不仅仅代表着把核心算法设计好,它要考虑到方方面面,各种细节以及程序的性能与代价。这次便是初期设计时考虑的太过于粗劣,很多细节未涉及,导致开发阶段困难重重。详尽全面的设计是项目工作必不可少的环节。
3.代码实现时的规划:写代码时有一个很深的感触就是当代码量较大时,往往不知该从哪块下手,该如何合理规划。经常是这一部分还未写完整,另一部分突然有了更好的设计就回去修改;或者是写到一半,发现有的部分需要另外的函数来协助。这样感觉很容易打乱自己的思路,会浪费大量时间。
吃一堑长一智。这次作业自己完成的是非常失败的,但也能警醒激励自己,去追求更好。