Sudoku 小项目
Sudoku 小项目 - 软工第二次作业
Part 1 · 项目相关
-
项目的更多信息以及所有开发文档见 README.md
-
整体过程,Todolist工具: Teambition
- 部分动态
- 撰写该文时 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 · 设计实现
(一)类之间的关系:
(二)代码组织
- 类:Sudoku、SudokuGenerator、SudokuChecker 在项目 Sudoku 里
- 类:SudokuTest、SudokuGeneratorTest、SudokuCheckerTest 在单元测试项目 SudokuTest 里
- 类:ParamChecker、CommandWorker 在项目 Command 里
- 类:ParamCheckerTest 在单元测试项目 CommandTest 里
- 主程序 main.cpp 在项目 Main 里
- 项目 Sudoku,Command 均设为静态
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。
标签:
《软件工程实践》
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!