Sudoku 小项目

Sudoku 小项目 - 软工第二次作业

Part 1 · 项目相关

此处输入图片的描述

  • 部分动态

此处输入图片的描述

  • 撰写该文时 SourceTree 结构如下:

此处输入图片的描述

Part 2 · PSP 表格

Statu Stages 预估耗时 实际耗时
Accept 【计划】Planning 60 100
Accept —— 估计时间 Estimate 60 100
Accept 【开发】Development 345 1550
Accept —— 需求分析 Analysis 20 15
Accept —— 设计文档 Design Spec 60 130
Accept —— 设计复审 Design Review 15 20
Accept —— 代码规范 Coding Standard 20 25
Accept —— 具体设计 Design 30 300
Accept —— 具体编码 Coding 180 1000
Accept —— 代码复审 Code Review 20 30
Accept —— 测试 Test 60 30
Accept 【记录用时】Record Time Spent 10 10
Accept 【测试报告】Test Report 40 20
Accept 【算工作量】 Size Measurement 10 10
Accept 【总结改进】 Postmortem 60 45
Accept 【合计】Summary 585 1735

Part 3 · 解题思路

  • 先按做acm题的心态思考了1~2小时,没想到什么比较优秀的做法;
  • 然后疯狂搜索,因为一开始打算做附加题所以搜了不少附加题相关的内容。
  • 先是搜到了:矩阵交换法的做法,觉得很机智很快,但是因为所有生成矩阵是等价矩阵所以不想采用这个做法
  • 然后搜到了搜索法,这个比较直观,除了拉斯维加斯比较有心意,但是普遍效率都不是很好,基本都存在简单回溯的话确实本来随机性的意义。
  • 然后搜到了一个不断随机每一行的做法,觉得很棒,博主也测试说很高效,结果实现了一下发现根本做不了,冷静下来分析了一下才发现是个假算法。。浪费了好多时间。
  • 最后还是采取了矩阵交换法,虽然也在项目中实现了所有终盘随机质量很好的回溯法,但是没有在指令<-c>中实装,因为测试发现除非只有几k的数据要求量,不然时间要好几十秒乃至若干分钟。
  • 当时想做附加题所以继续思考怎么做终盘,搜到的资料多是在挖空的位置上做文章。

Part 4 · 设计实现

(一)类之间的关系:

此处输入图片的描述

(二)代码组织

  • 类:SudokuSudokuGeneratorSudokuChecker 在项目 Sudoku
  • 类:SudokuTestSudokuGeneratorTestSudokuCheckerTest 在单元测试项目 SudokuTest
  • 类:ParamCheckerCommandWorker 在项目 Command
  • 类:ParamCheckerTest 在单元测试项目 CommandTest
  • 主程序 main.cpp 在项目 Main
  • 项目 SudokuCommand 均设为静态

Part 5 · 代码说明

(一)内部函数:快速生成

  • 主代码
  • 主要思路:通过重编码数字、行、列从已有的终盘快速生成其它终盘。
  • 注释:逻辑比较简单,就是找下一个行编码,列编码,数字编码,代码中有简单的英文短语注释。

此处输入图片的描述

(二)内部函数:高质量生成

  • 主代码
  • 主要思路:通过深搜,每次随机能当前格子合法的数字集合中随机一个数字放到当前行、列。
  • 注释:传统深搜,短语注释了几个逻辑块。

此处输入图片的描述

(三)外部框架:指令系统

  • 指令系统分为两个类,一个类是封装所有指令的参数校验,另一个类是实现所有指令的功能
  • 从而做到能 高效的拓展指令,笔者搭建好框架后在添加指令<-check>以及<-help>途中感觉非常畅通,其它的代码都被隔开,那种仿佛在一张白纸上工作的感觉相当的棒。
  • 整个框架:
  • 主程序 根据 argv[1] 判断指令,调用 主程序中相关指令函数段,并负责整个过程中抛出的 exception

此处输入图片的描述

  • 相关指令函数段:调用 参数校验,接着调用 指令执行

此处输入图片的描述

  • 参数校验 框架:

此处输入图片的描述

  • 指令执行 框架:

此处输入图片的描述

(四)程序运行

  • 基本上所有的不合法输入都被 单元测试指令系统框架的参数校验代码 ban掉了,所以只展示成功的界面。
  • <-help> 指令

此处输入图片的描述

  • <-c> 指令:测试100w的数据量,笔者本地运行时间大概 1s,CPU为 I7-4720HQ。

此处输入图片的描述

  • <-check> 指令:检验100w的数据量是否有重复,笔者本地运行时间大概6s,CPU为 I7-4720HQ。

此处输入图片的描述

(五)改进性能过程

生成改进一

  • 最原本的判法是暴力判合法性,但是效率太低了。
  • 编写的复杂的 Sudoku 类是主要为质量最好的 回溯法(SudokuGenerator::BestGenerate) 准备的,内部采用了维护每行、每列、每宫的状态来维护整个棋盘;
  • 可以做到动态维护数独棋盘是否合法,每次修改格子都只要花几个常数的时间,O(1)查询当前终盘是否有冲突、是否合法等等。

生成改进二

  • 一开始 矩阵转换法 习惯性的用了sudoku 存生成的方案,生成100w数据大概要60秒。
  • 性能分析工具分析出Sudoku维护函数调用太多。
  • 但是矩阵转换法由于是直接重编码,所以不需要维护合法性,换了一下生成100w大概要10秒,提速6倍左右。

生成改进三

  • 输出一开始采取一个个输出,效率太低。
  • 后面把每个棋盘弄成一个字符串然后puts输出,改进完后生成100w数据大概在 1 秒左右。

校验改进一

  • 把每个棋盘压成一个string,然后扔进set来判重复。
  • 改进后判定100w个棋盘速度大概在 6s左右。

(六)性能分析图

  • 由于设计时就不打算提供非命令行运行,所以性能分析时采用输入信息嵌入主代码的方式。
  • 性能分析图

此处输入图片的描述

  • 分析数据量 1w,测试结果较理想。
  • 按调用数排序,核心函数 std::next_permutation 调用次数 1w 多次,理论上生成1w的数据调用次数无法低于 1w

此处输入图片的描述

  • 次数最高的函数为系统函数,其次是输出函数,函数如下:

此处输入图片的描述


(六)总结

  • 偏工程方面的总结在 README.md 的文档里。
  • 感觉几天内学到了好多东西吧,搜了不少东西,从一开始的把自己弄得手忙脚乱,commit的时候一堆东西揉在一起,代码习惯性的会往打acm题目的风格靠,git里还有100m+的输出文件就commit最后push崩溃才察觉到这个问题,设计了改改了再设计,时常写不出单元测试偷偷先去写主代码最后砸了自己的脚。
  • 到后来慢慢熟悉了整个过程,回档fix bug,先写单元测试再写代码,检验文件无异常再commit和merge,每个功能新开一条git 分支编写然后merge回dev分支,VS又报错了也淡定了,exe崩溃了也知道自己可能有野指针或者内存泄漏,慢慢感觉到了一丝丝的秩序,虽然这一切还是按照自己临时搜的一堆资料构建出来的自以为的软件开发应有的流程Orz..但还是觉得学到了很多吧,也研究了一下怎么处理异常好一点,最后学了下try throw的那一套逻辑,但是可能姿势不太对还是搜的信息还不够所以也是按照自己的理解构建了目前项目的整个异常系统,函数声明也会留意要不要const 和throw等等。
  • 然后突然发现不会打题了。

End。

posted @ 2017-09-10 11:11  TheSkyFucker  阅读(763)  评论(19编辑  收藏  举报

本 当 で す か ?

は い 。