标签:开发 阶段 gen person font des false sse ++
https://github.com/crvz6182/sudoku_partner
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
Planning | 计划 | 10 | |
· Estimate | · 估计这个任务需要多少时间 | 10 | |
Development | 开发 | 1270 | |
· Analysis | · 需求分析 (包括学习新技术) | 30 | |
· Design Spec | · 生成设计文档 | 10 | |
· Design Review | · 设计复审 (和同事审核设计文档) | 30 | |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | |
· Design | · 具体设计 | 30 | |
· Coding | · 具体编码 | 300 | |
· Code Review | · 代码复审 | 60 | |
· Test | · 测试(自我测试,修改代码,提交修改) | 500 | |
Reporting | 报告 | 120 | |
· Test Report | · 测试报告 | 85 | |
· Size Measurement | · 计算工作量 | 5 | |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | |
合计 | 1400 |
设计时主要重视接口是否分工明确,功能是否单一,没有重复。
分工明确:单独设计了用来挖空的接口blank。DLX求解中有很多重复执行的部分,因此拆分成了多个接口(resume,remove,toMartix,dance,delete,init)
功能单一,没有重复:挖空的时候要确定是否有唯一解,没有通过规定参数使求解接口同时实现这个功能,使用专门的接口isunique来判断。
涉及到Core模块中的以下函数:
_declspec(dllexport) bool __stdcall solve(int puzzle[81], int solution[81]);
_declspec(dllexport) bool __stdcall blank(int puzzle[81], int mode);
_declspec(dllexport) bool __stdcall blank(int puzzle[81], int lower, int upper, bool unique);
Node* toMatrix(int puzzle[81]);
void remove(Node* c);
void resume(Node* c);
int dance(Node* head, int solution[81]);
int dance(Node* head, int &tag);
bool DLX(int puzzle[81], int solution[81]);
bool isunique(int puzzle[81]);
void Delete(Node* n);
void init(Node* n);
blank用于对生成好的数独挖空得到题目,挖空时根据传入的参数在范围内随机挖空,会调用isunique判断挖完的题目是否有唯一解,传入参数不合法的话会抛出异常并返回false
由于在实际上玩数独的时候比起解的个数,挖空数对难度的影响要大得多,因此本项目难度的划分不考虑解的个数,具体为:
难度 | 挖空个数 |
1 | 20~35 |
2 | 36~45 |
3 | 46~55 |
求解使用了DLX算法,使用链表实现
DLX用于求解数独,会调用dance(Node* head, int solution[81])和toMatrix,返回值表示是否有解
isunique用于判断一个数独题目是否有唯一解,会对数独进行回溯求解,直到回溯完成或找到两个解。是对DLX进行少量重写得到的,返回值表示是否有唯一解
isunique中调用dance(Node* head, int &tag),用tag标记解的个数,tag=2表示有两个以上的解,=1表示唯一解,=0表示无解
init用于初始化链表的节点,Delete用于释放链表的空间
设计函数时将DLX算法分为2个部分:生成链表和遍历。其中遍历会多次执行删除元素和恢复元素的操作。因此将DLX的整个过程分为4个函数
toMatrix用于生成链表,返回head节点
dance(Node* head, int solution[81])用于得出一个解并保存到solution数组里
resume和remove分别用于恢复和删除链表中元素
基本只对求解进行了改进,但是花费了大量时间(7-8h)
第一版完成时在x86下进行了简单的测试,当时还没有发现问题
在第一版完成以后我们组进行了第四阶段的交换测试,发现在x64环境下DLX求解变得十分慢而且内存消耗极大,在长时间调试后找到了原因
一开始通过中断调试找到了死循环,发现多次挖空时没有还原挖过的数独,修正了挖空的逻辑,但是问题没有得到解决
然后通过内存分析发现链表的Node节点占用了大量的内存,可能是释放链表空间出了问题
经过长时间的分析后发现在求解完成时链表是不完整的,一部分节点被删除,但是变成了孤立节点,没有被释放
然后在链表中加入了一个额外的指针,使整个链表形成一个一维链表,以此为索引进行释放就不会漏掉节点,最终解决了问题
性能分析
两张分别对应生成数独题和求解,生成参数为 -n 100 -u,求解为多次求解一个较难的17个数的数独
由于generate调用了blank,blank调用了isunique,isunique调用了toMatrix和dance,时间主要消耗在转换成矩阵上,求解也花了一定量的时间。
求解的时候选用了难度较大的数独,这时dance函数消耗的时间明显上升
在使用-u参数的时候,由于需要多次求解和重新挖空,效率很慢,生成100个就要5s,目前还没有找到什么比较好的解决办法
契约式编程保证了调用者和被调用和双方的质量,避免调用方的代码质量明显较差,但缺点是需要一种机制,对程序设计语言有要求
在结对编程中,两方的地位平等,分工明确,这对于使用契约式编程来说是个很好的环境。
在项目中我主要负责编写被调用者(Core),另一位同学负责调用者(GUI)
双方分别明确接口规格,对自己的部分负责,并进行完善的测试,这可以有效避免组合在一起时出现bug,节省时间。
测试生成:
TEST_METHOD(TestGenerate)
{
// TODO: 在此输入测试代码
int sudo[9][9];
Core test;
int result[3][81];
test.generate(3, result);
for (int i = 0; i < 3; i++)
{
for (int j = 0; j < 81; j++)
{
sudo[j / 9][j % 9] = result[i][j];
}
for (int j = 0; j < 9; j++)
{
for (int k = 0; k < 9; k++)
{
for (int n = 0; n < 9; n++)
{
if (n != j)
{
Assert::AreEqual(false, sudo[i][j] == sudo[i][n]);
}
if (n != i)
{
Assert::AreEqual(false, sudo[i][j] == sudo[n][j]);
}
}
}
}
}
int blankNum = 0;
int puzzle[81] = { 8,1,2,7,5,3,6,4,9,9,4,3,6,8
,2,1,7,5,6,7,5,4,9,1,2,8,3,1,5,4,2,3,7,8,9
,6,3,6,9,8,4,5,7,2,1,2,8,7,1,6,9,5,3,4,5,2
,1,9,7,4,3,6,8,4,3,8,5,2,6,9,1,7,7,9,6,3,1
,8,4,5,2 };
int backup[81] = { 8,1,2,7,5,3,6,4,9,9,4,3,6,8
,2,1,7,5,6,7,5,4,9,1,2,8,3,1,5,4,2,3,7,8,9
,6,3,6,9,8,4,5,7,2,1,2,8,7,1,6,9,5,3,4,5,2
,1,9,7,4,3,6,8,4,3,8,5,2,6,9,1,7,7,9,6,3,1
,8,4,5,2 };
test.generate(1, 1, result);
for (int i = 0; i < 81; i++)
{
if (result[0][i] == 0)
{
blankNum++;
result[0][i] = backup[i];
}
}
Assert::AreEqual((20 <= blankNum&&blankNum <= 35), true);
blankNum = 0;
test.generate(1, 2, result);
for (int i = 0; i < 81; i++)
{
if (result[0][i] == 0)
{
blankNum++;
result[0][i] = backup[i];
}
}
Assert::AreEqual((36 <= blankNum&&blankNum <= 45), true);
blankNum = 0;
test.generate(1, 3, result);
for (int i = 0; i < 81; i++)
{
if (result[0][i] == 0)
{
blankNum++;
result[0][i] = backup[i];
}
}
Assert::AreEqual((46 <= blankNum&&blankNum <= 55), true);
blankNum = 0;
test.generate(1, 20, 55, true, result);
for (int i = 0; i < 81; i++)
{
if (result[0][i] == 0)
{
blankNum++;
result[0][i] = backup[i];
}
}
Assert::AreEqual((20 <= blankNum&&blankNum <= 55), true);
blankNum = 0;
test.generate(1, 40, 40, true, result);
for (int i = 0; i < 81; i++)
{
if (result[0][i] == 0)
{
blankNum++;
result[0][i] = backup[i];
}
}
Assert::AreEqual((40 == blankNum), true);
}
分别针对生成的三种接口进行测试,检测生成的数独终局是否合法/题目是否符合要求
测试求解:
TEST_METHOD(TestSolve) { // TODO: 在此输入测试代码 int puzzle[81] = { 8,0,0,0,0,0,0,0,0 ,0,0,3,6,0,0,0,0,0 ,0,7,0,0,9,0,2,0,0 ,0,5,0,0,0,7,0,0,0 ,0,0,0,0,4,5,7,0,0 ,0,0,0,1,0,0,0,3,0 ,0,0,1,0,0,0,0,6,8 ,0,0,8,5,0,0,0,1,0 ,0,9,0,0,0,0,4,0,0,}; int result[81] = { 0 }; int answer[81] = { 8,1,2,7,5,3,6,4,9,9,4,3,6,8 ,2,1,7,5,6,7,5,4,9,1,2,8,3,1,5,4,2,3,7,8,9 ,6,3,6,9,8,4,5,7,2,1,2,8,7,1,6,9,5,3,4,5,2 ,1,9,7,4,3,6,8,4,3,8,5,2,6,9,1,7,7,9,6,3,1 ,8,4,5,2 }; int wrong[81] = { 8,1,2,7,5,3,6,4,9,9,4,3,6,8 ,2,1,7,5,6,7,5,4,9,1,8,2,3,1,5,4,2,3,7,8,9 ,6,3,6,9,8,4,5,7,2,1,2,8,7,1,6,9,5,3,4,5,2 ,1,9,7,4,3,6,8,4,3,8,5,2,6,9,1,7,7,9,6,3,1 ,8,4,5,2 }; Core test; bool isvalid = true; test.solve(puzzle, result); for (int i = 0; i < 81; i++) {
puzzle[i] = answer[i]; Assert::AreEqual(result[i], answer[i]); } test.blank(puzzle, 20, 55, true); test.solve(puzzle, result); for (int i = 0; i < 81; i++) { Assert::AreEqual(result[i], answer[i]); } isvalid = test.solve(wrong, result); Assert::AreEqual(isvalid, false); isvalid = test.solve(answer, result); Assert::AreEqual(isvalid, true); }
依次求解一个较难的数独,随机挖空的唯一解数独,不合法的数独和完整的合法数独,检测求解的结果和返回值正不正确
同时检测了判断唯一解算法的正确性,若返回的数独不是唯一解的话求解出的result和answer可能不相同,不能通过测试
单元测试覆盖率:
项目的异常分为三种:
numberException | 输入的数独生成数量异常 |
boundException | 输入的挖空边界异常 |
modeException | 输入的难度异常 |
TEST_METHOD(TestException) { Core test; int result[3][81]; try { test.generate(2000000, result); } catch (numberException &e) { Assert::AreEqual(0, 0); } try { test.generate(20000, 2, result); } catch (numberException &e) { Assert::AreEqual(0, 0); } try { test.generate(20000, 20, 40, false, result); } catch (numberException &e) { Assert::AreEqual(0, 0); } try { test.generate(3, 5, result); } catch (modeException &e) { Assert::AreEqual(0, 0); } try { test.generate(3, 41, 40, false, result); } catch (boundException &e) { Assert::AreEqual(0, 0); } try { test.generate(3, 12, 40, false, result); } catch (boundException &e) { Assert::AreEqual(0, 0); } try { test.generate(3, 32, 70, false, result); } catch (boundException &e) { Assert::AreEqual(0, 0); } }
单元测试分别针对使用-c和-n时的参数,难度不为1-3,边界超出范围和lower>upper等情况进行了测试
test.generate(2000000, result);
|
使用参数-c,超出了最大范围1000000 |
test.generate(20000, 2, result)
|
使用参数-n,超出了最大范围10000 |
test.generate(20000, 20, 40, false, result)
|
使用参数-n,超出了最大范围10000 |
test.generate(3, 5, result)
|
使用参数-m,输入的参数不在1~3范围内 |
test.generate(3, 41, 40, false, result)
|
使用参数-r,lower>upper |
test.generate(3, 12, 40, false, result)
|
使用参数-r,lower超出范围 |
test.generate(3, 32, 70, false, result)
|
使用参数-r,upper超出范围 |
主要的讨论都在网上进行,在确定结对后线上讨论得出分工,之后制订计划并开始编码
讨论的结果是我负责除GUI和生成完整数独算法以外的任务
在下课的时候会当面讨论一些线上无法解决的问题
结对编程的优点:在开发一个不需要大量研究的项目时,可以有效提高工作效率和编码质量,减少不经意间产生的bug,两个人也可以互相学习
缺点:分工和规则的制定有些麻烦,需要两个人的配合和互相理解
她的优点:
写的代码在交给我的时候几乎没有bug,我没有做到这一点
擅长交流,我们可以很有效地沟通
可以即时接受反馈并改进
缺点:
没有主动报告自己的进度
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
Planning | 计划 | 10 | 10 |
· Estimate | · 估计这个任务需要多少时间 | 10 | 10 |
Development | 开发 | 1270 | 1160 |
· Analysis | · 需求分析 (包括学习新技术) | 20 | 60 |
· Design Spec | · 生成设计文档 | 10 | 20 |
· Design Review | · 设计复审 (和同事审核设计文档) | 10 | 30 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 10 | 10 |
· Design | · 具体设计 | 60 | 60 |
· Coding | · 具体编码 | 300 | 300 |
· Code Review | · 代码复审 | 60 | 60 |
· Test | · 测试(自我测试,修改代码,提交修改) | 500 | 450 |
Reporting | 报告 | 120 | 150 |
· Test Report | · 测试报告 | 85 | 120 |
· Size Measurement | · 计算工作量 | 5 | 5 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 25 |
合计 | 1400 | 1410 |
合作小组:15061122 15061144
问题主要出现在两方的测试模块和Core对接,由于我们在测试的时候都用了非标准接口的函数,在测试时出现了问题。
我对测试进行了针对性修改后可以使用他们的Core。
他们在测试时发现generate(100,40,55,true,result)会导致程序异常,这是导致我发现第六部分提到的问题的原因(在此感谢他们)
由于我测试的时候是在x86上,他们是在x64上,因此出现了完全不一样的结果,对问题的处理和优化在第六部分有详细说明。
标签:开发 阶段 gen person font des false sse ++
原文地址:http://www.cnblogs.com/crvz6182/p/7668120.html