单人项目
Github工程链接
PSP表
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 20 | 30 |
Estimate | 估计这个任务需要多少时间 | 10 | 10 |
Analysis | 需求分析 | 30 | 20 |
Design | 具体设计 | 60 | 50 |
Design Spec | 生成设计文档 | 60 | 60 |
Coding Standard | 代码规范 | 30 | 20 |
Development | 开发 | 300 | 400 |
Code Review | 代码复审 | 40 | 45 |
Test | 测试 | 200 | 250 |
Reporting | 报告 | 60 | 50 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 840 | 965 |
熟悉Visual Studio 2017
单元测试
助教@ChildishChange推荐的示例跳跃性太强,很多关键步骤没有截图或截图不完整,且是中文版的Visual Studio。使我这样的初学者很迷惑。
所以我选择了参考官方的测试文档。不得不说,巨硬家的文档写的就是好,
有图有真相。
单元测试还是及其好用的。上次大规模使用单元测试应该是大二下的“面向对象建模方法(OO)”的实验课了,当时的单元测试的工具是Eclipse + JAVA,并没有深度使用。单元测试的好处有很多。我现在有感受的主要有:改动代码后仍然能很方便地保证功能的正确性;测试甚至调试某一方法或函数很方便(我之前都是新建一个小工程进行测试和调试,有了单元测试这些都不需要了)。
生成数独
设计详述
我定义了一个数独格子的类,维护格子中的数和填格子的操作。首先,将数独的左上角填成5;之后,递归地从左到右填充格子;对于每一个格子,我们先生成一个包含1到9的随机列表,所填的数就是依次选取这个列表中的数。之所以这么做,是为了增加生成数独的随机性。每填一个数,判断其是否符合与行列小宫格中数不同的 要求。如果满足,判断是否数独已经填完。如果数独生成完毕,打印这个数独,并在最后一个格子上尝试下一个数;如果没填完,则继续填下一个格子。如果不满足,尝试列表中的下一个数。直到列表尝试完毕,返回上一个格子,并在上一个格子选取列表中的下一个数。
当生成了足够多的数独后,直接抛出一个异常,中断生成数独的递归程序,简单暴力。
我生成数独的算法使用的是最简单暴力的搜索法,时间复杂度很高,因为有回溯过程中有大量碰撞的情况。不过实现起来比较简单。
其实生成1000,000个数独有更取巧的方法。题目要求生成数独不重复,但并没有要求通过旋转、对称、置换、行交换、列交换等操作后仍不重复;而且通过阅读乾神的测试程序,我发现判断两个数独是否相等只是判断了两个数独的HASH值是否相等,并没有防范对称、旋转、置换后的数据不重复。
那么,一个数独通过旋转、对称、置换后可以产生多少数独呢?由于我们将左上角的数字定死了,旋转就不可以了,对称只能是从左上角到右下角的对角线对称,置换也只能是8个数字之间的置换。所以,大概会产生2! * 3! * 3! * 2! * 3! * 3! * 2 * !8 = 474656
个数独。为了保证数独生成的不重复性,我们可以使用HashSet来维护。
性能分析
由效能分析结果可知,花费CPU时间最多的两个内部调用是 数独生成器的FillNextGrid和PrintResult。其中,FillNextGrid是递归搜索数独结果的函数,由于搜索空间大,所需CPU时间多;PrintResult是当搜索到一个正确的数独时,打印结果到文件中去。针对这效能低下两个函数,可以由以下改进:
如何缩小搜索空间?我们可以采用生成等价数独的方法。即搜索到一个数独后,通过交换行列、数字置换、对称等方式生成与之等价却不相等的数独。由于一个数独可以生成等价数独的数目是巨大的,这样做可以有效地缩小搜索空间。当然,这种方法也有弊端:丧失了很大的随机性,产生的数独大量相关。
如何减少输出所需的时间?输出作为一种IO操作,相比CPU来说确实是很慢的。在CPU的效能分析中,输出结果函数同样占用了很多CPU时间,可能是调度等待的时间比较长。为了提升IO操作,我想到两种方法:一是多线程,计算和IO分离;二是先将结果存在一个数据结构(内存)中,累积到一定量时再一同输出。两者同时作用效果当然更好(其实是我不会用C#的多线程编程)。
求解数独
我求解数独的方法和生成数独很类似,都是使用回溯法。区别在于,生成数独时判断数字不重复只需要和左边还有上边的格子比较,求解时就还要比较了。同样存在时间复杂度较大的问题。
附加题 GUI
附加题没有算法上的难度,更多是是工程和实践上;包括错误处理,交互设计等。生成数独可以调用与CLI相同的库,增加按规定随机挖空的功能;用户填完数独提交后,还需要判断所填数独的正确性,给出相应的反馈。
我实现GUI用的是WPF(Windows Presentation Foundation),Visual Studio对其的支持非常好,开发效率也极高。我之前接触过一点UWP的开发,并没有WPF开发的经验。但是UWP开发出的程序无法生成可执行程序,不符合需求,只能生成安装包,助教测试的时候还要安装,很不方便。WPF的界面设计语言与UWP很像都是xaml的,所以之前的经验还是很有用的。
感想体会
在群里看到有些大佬使用QT完成GUI,而我用的WPF;而且大多数人使用C++完成这次作业,而我用的C#。看来我使用的工具和语言都比较小众。很多同学生成时间都比我快很多,1M的数独只需要几秒。看来我程序和算法还有很大的提高空间。