一、项目地址
代码托管在了GitHub上,地址:https://github.com/InspAlgo/Personal_Suduku
各位看官大大们记得给我项目点star,我请你们撸代码!
还有个人项目纪实忠实记录个人项目开发过程,有很多细节!
二、PSP
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | ||
·Estimate | ·估计这个任务需要多长时间 | 10 | 20 |
Development | 开发 | ||
·Analysis | ·需求分析(包括学习新技术) | 60 | 120 |
·Design Spec | ·生成设计文档 | 300 | 120 |
·Design Review | ·设计复审(和同事审核设计文档) | / | / |
·Coding Standard | ·代码规范(为目前的开发制定合适的规范) | 120 | 180 |
·Design | ·具体设计 | 120 | 180 |
·Coding | ·具体编码 | 1080 | 1200 |
·Code Review | ·代码复审 | 300 | 60 |
·Test | ·测试(自我测试,修改代码,提交修改) | 300 | 960 |
Reporting | 报告 | ||
·Test Report | ·测试报告 | 60 | 60 |
·Size Measurement | ·计算工作量 | 30 | 30 |
·Postmortem & Process Improvement Plan | ·事后总结,并提出过程改进计划 | 180 | 120 |
合计 | 2560 | 3050 |
三、解题思路
看老师的要求文档,数独?用计算机解数独问题~还要啥代码分析、性能测试?还要托管在github上,还要用git进行版本控制,还有各种规范,还尼玛各种数量巨多的要求。。。问题真是超级多啊!毕竟是个小工程项目。
不过总的来说有三个大块:一是要搞定数独求解算法,这个是核心;二是代码测试、性能分析,与我们之前写类似OJ的算法习题不同,这个是一个正式的软件编程的必要部分;三是进行版本管理以及项目规范,这是从项目工程角度指导设计及编写代码进行软件开发的,也是软件工程学科的意义所在。在我看来这次个人项目考量的基本上是这三点了,接下来是具体步骤。
1. 首先有问题找搜索引擎,搜索数独 计算机 算法
等关键词。这一步是要初步了解问题的样子,先看看前人可有相关经验可以借鉴,好让心里有个谱,关键是了解是否有相关算法的存在。
在知乎上,我看这几篇写的很不错:
数独求解算法
暴力算法之美:如何在1毫秒内解决数独问题?| 暴力枚举法+深度优先搜索 POJ 2982
数独求解算法Kotlin版
成为数独高手有哪些好的训练方法
推荐的:
Solving Every Sudoku Puzzle
Sudoku solving algorithms
在谷歌、必应上找的:
数独高效完全解生成算法的研究和实现
算法实践——舞蹈链(Dancing Links)算法求解数独
跳跃的舞者,舞蹈链(Dancing Links)算法——求解精确覆盖问题
[buaa-SE-2017]个人项目
算法:
2. 这里过程跳一下,在写代码前,我要先看看关于github和git的使用,因为要求文档里说要看我们的commit情况,emmmm,这样我就要先建仓库啊,可这我也不会啊,于是又要找教程,这个也简单,随便搜索一下就有入门教程,我也按教程先创了仓库,具体可以看我的个人项目纪实。
3. 应该讲一讲关于代码测试与性能分析方面的了,不过考虑到这是项目后期问题,为了节省时间,我们先完成核心算法,先快速搞出项目原型,然后再来看这个问题,再一边学一边弄。
然后是关于具体算法的解题思路,看了那么多介绍数独求解方法,感觉好多都看起来复杂度好高,还有随机化方法的,不过个人感觉随机化方法应该是不可取的,因为你无法判断是否有重复的情况出现,难不成又弄个哈希再检测一下?那样挺占用内存的,毕竟压力测试时可是有100万的数据量;然后最直接的方法就是暴力回溯了,因为我们知道1~9这就九个数必定要出现,那么生成终局问题就变成了给数字排位置问题,首先给九个1排,再给2排,再给3排……依次类推,最后排到9。每给一个数排好位置我们就要进行递归,因为递归操作很方便我们回溯,我们在排数字时,肯定会遇到排不了的情况,这时候我们就要返回上一步重新排数字,再不行就再返回。求解数独的算法也基本相同,只不过只需要生成一个终局即可,我们可以把返回条件改为是否有解,因为不是所有的数独题都是有解的,也可能出现无解的情况,这个我现在无法确定所以不能忽略无解的情况。具体算法的伪代码可参见我的个人项目纪实或者后面的代码说明。
四、设计实现过程
4.1 代码风格规范
由于本部分内容较多,故重新写了篇博文,详见软件工程基础课-个人项目-代码风格规范。
里面的内容主要摘自Google C++风格指南,同时在个人项目中尽力以此为风格标准,可能个别地方不一致。
4.2 函数关系图
程序基本流程图为:
函数关系图由VS自动生成
五、程序性能分析及改进
通过性能分析图可以看出来我的检查函数占比较多,因为是暴力回溯所以每一步都要判断是否有重复、不符合规则的数字出现,而每次判断都要用循环遍历,这个就很耗时了,我是已经作了优化的,将横竖分别循环判断改成了同时循环判断,emm,没想到还是比较耗时间。还有一种方法就是加个位置记录,具体方法是这样,比如num_row[num][i]
代表数字num
在第i
行是否有出现,同理num_col[num][j]
代表数字num
在第j
列是否有出现,num_box[[num][row][col]
代表数字num
是否在第[row][col]
小九宫格上有出现。这样就不需要用循环判断了,填数字了就赋值为false
,刚开始没数字时是赋值为true
的。
然后试验了一下
如下图可以看出C策略函数的检查降到800的量级,之前还是4000的量级
这次改进还是有效果的,连检查函数我都删掉了,因为这种写法的话判断语句就太短了。不过需要注意的就是在合适的位置还原为true
。
在基本完成程序后又重新进行了一下性能分析,结果见下。
其中消耗最大的函数为strategyC
,其具体情况为:
与之前性能分析比较有一定的时间延长,在本机测试时发现会出现一定的时间波动。
六、代码说明
我主要使用的是暴力回溯法生成数独的,求解数独同理。
我们看一下这个规则,会发现一个规律,就是第一行有数x后不能再有x,它一定会依次出现在后面的行,同理列也一样,然后其他数也是这样。那么我们如果按数字顺序在第一个小方格内填入1,然后再在第二行某个格填入1,依次类推直到填完9种数,到这里有人会说这样迟早会产生冲突,出现不符合规则的局面,没错,的确会这样,那么我们就从试不了的地方开始回溯,换个数继续试,直到满足位置,这里有时候会回溯好几步才能成功,不过计算机比较快,尤其是用递归实现起来还是相当快的了。
用伪代码描述:
function Backtracking_Search(i, j) //i,j表示的是大九宫格里的(i, j)格
{
if 已生成的终局数 >= 要求生成的终局数:
return;
if 生成一个新终局: //检查生成新终局的便捷办法就是判断边界条件
{ //当格子的位置移出9×9的大九宫格外说明就有新终局了
已生成的终局数++;
输出新生成的终局; //或者先存起来一起输出
return;
}
if 到了一行的最末: //这个看你是怎么搜索了,我是按行的,也可以按列
{
换行到下一行开头; //我的就是i++;j=1;换列就是i=1;j++;
}
for num from 1 to 9:
{
if 已生成的终局数 >= 要求生成的终局数:
return;
if 准备填入(i,j)格的数num满足数独规则:
{
将num赋值给(i,j)格;
Backtracking_Search(i, j); //满足条件就继续递归下去
}
}
(i, j)格重置为0; //说明填什么数字都不满足数独规则,要开始回溯,这个位置要重置为0
}
这个算法的出口就是你要生成多少终局。
七、单元测试与代码覆盖率分析
总共设计了13个测试,覆盖率为88.33%,接近90%,已达个人预期的80%。
单元测试主要针对输入的判断作了较多的测试,因为程序首先要能分析命令行指令的正确性,设计了2组正确输入也设计了3组错误输入,对参数分析作了比较完备的测试,对类的构造函数也进行了测试以判断初始化情况,对储存数组、生成数独、求解数独、输出数独等核心模块也都分别设计了测试。具体内容可以参见测试代码,不过需要注意单元测试时要将sudoku.h
文件中Sudoku类
的private
注释掉,因为我的类数据成员原本全私有,且不少方法封装的可能有点紧凑不好切入断言,故为了单元测试方便,需要改成公有。注意、注意、注意
使用我的单元测试时一定要将私有改公有!!!
八、项目总结
本次个人项目历经十余天,遇到了许多奇奇怪怪的bug,感觉不再是对代码能力的考验了而是对意志力的磨练。
8.1 个人的提升
面向对象思想
第一次正式使用VS构建C++工程,使用多文件进行对项目分模块化,而不再像之前是单文件模式,模块化构建使自己的思路与逻辑更加清晰,并且配合C++类的封装等面向对象的编程思想使得代码结构也更加清晰。这也让我感受到了面向对象编程的优秀之处,我们的世界本身就是物物的作用关系,而对象则是物物的代码抽象化,而作用关系则是方法,我们的世界中有显性的作用关系,也有隐性的,而宏观物的内部也有更加具体的微观物,这就是公有与私有、类与成员的关系。工程能力
什么是工程?个人认为工程是综合、是规范。综合说明了实施的过程必然是繁琐的,有一定量的,需要的知识也是多方面的,软件工程亦是如此,在本项目中,要学习C++的一些特性,还要熟悉VS的一些使用技巧,尤其是编写代码时要思考该怎么使用算法?要思考应该如何具体实现。规范则说明了代码不是随便编写的,例如首要的就是代码风格规范,有一个良好的风格规范一方面是便于阅读,另一方面则是使代码逻辑更加严谨,减少了不必要的错误。规范还体现在单元测试,之前的程序练习要求能跑就好,没有多少真正的测试,而软件开发的单元测试也对应了一般意义上的工程开发,像建楼房,有各种验收审查,各个部分的检验,而这就是代码的各个模块,单元测试是软件的一质量测试,虽实现不同但意义是一样的。模仿与学习
在本项目中,很多东西都是自己以前没有遇到过的,没有任何经验,只能模仿别人的方法,并通过模仿来学习自己未知的知识与经验。在这里,非常感谢一位北航学长,他的项目给我很多启发与灵感,在模仿他的一些方法的过程中学习到了很多,减少了很多弯路。模仿是学习的一种基础,先模仿获得初步的经验,有了经验我们才能更好的反思与改进,没有天生就会的,而有了足够的经验我们才能够创新与创造。
这次的个人项目对模仿与学习能力有极大的考验,在此之前都没有过比较深入的看官方文档的经历,学会如何看文档、技术博客,从中获取自己所需要的知识与经验是一种属于成长式的能力,这种能力将会伴随我们终身并成为我们终身学习的关键一环。
8.2 不足
个人
英文水平有待提高,在看文档的时候习惯性将网址的en-us
改成zh-cn
,要是没有就会启动网页翻译,博客也是中文博客,没有养成在Stack Overflow上寻找解析的习惯。对待问题总是没有彻彻底底解决的感觉,不过这个涉及的能力实在不知道该怎么说了。注意力目前还是放在了工具的使用上了,作为一名软件开发人员,我们不光要会使用轮子,更要明白轮子的构建,以及软件开发方法。项目本身
例如程序本身的稳定性方面没有做过较多的研究,像程序生成100万数独终局的时间就有一定的波动,没有将其压到一个较低的稳定值,也没有做多平台测试,只是在个人的Win10 x64上进行了测试。也没有去完成项目的附加题——界面程序,这个是感觉很耽误时间,这段时间要有许多科目的学习,不能将时间全砸在软工的项目上,如果是小学期课程就会好很多,没有其他课程的干扰,可以一心一意的扑在项目上。课程设计
这个个人项目应该有讨论群的,应该有多位助教帮忙答疑解惑。毕竟我们这是本科二年级的课程,纵然我们有一定的自学能力,但是有人指导和没人的捉瞎的感觉是不一样的。老师在课上也应该及时评阅同学的个人项目,例如中期点评啥的,这样可以避免很多不必要的错误,或者举一些实际的例子给我们演示一下,而不是照着课本讲一些目前对我们来说的很宏观的方法,工厂都有师傅带徒弟进行实操练习,这个课也应如此,讲结构设计就应该给个完整的实际例子,而不是像书上那样的言简意赅,非常抽象,讲白盒测试、黑盒测试就应该给个具体例子、具体测试工具演示一下到底是怎么弄的,只讲宏观的东西是很难弄明白的。
致谢
[buaa-SE-2017]个人项目
感谢北航辛学长的项目给了我很多灵感与经验,让我少走了许多不必要的弯路。通过学长的代码学习到了C++面向对象编程的基本方法,同时其简洁优美的代码风格与技巧让人留恋与难忘!Visual Studio 文档
感谢微软的官方文档,让我在面对各种奇奇怪怪的错误与警告提示时有了一些基本概念与策略,同时通过文档也初步学习与掌握了单元测试方法等。其言简意赅的描述让人刻骨铭心!【算法研究】数独高效完全解生成算法的研究和实现、带你玩转Visual Studio——性能分析与优化、Git 中忽略某些文件或者文件夹······
这里无法一一列举参考过的博客,感谢大佬们的经验分享,让我在痛苦与绝望时看见了希望的光火!其开源分享的精神让人感动!感谢老师与同学,这次的个人项目对个人确实有所历练,大概能够理解老师的用意!