SudokuSolver 1.0 用法与实现效果

SudokuSolver 是一个提供命令交互的命令行程序,提供的命令清单有:

H:\Read\num\Release>sudoku.exe

Order please:

Sudoku Solver 1.0 2021/9/20 by readalps

Order List:
load-quiz <file>: load quiz from file
show: show quiz info
step: step forward
run: run till the end or a new solution met
bye: quit Order please:

以 一道心形数独题的求解 里的那道题为例,先在一个文本文件(如:H:\s.txt)里记录这道题,约定用 0 表示待填数字,内容如下:

000 000 000
023 000 780
100 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000

load-quiz 命令用于从文本文件中把数独题(称为一个 quiz)作为输入加载到程序内存;

show 命令用于显示 quiz 相关信息;

step 和 run 命令用于解题,step 只往前走一步;run 则一直走到求出一个解或走到末尾无解时才停步;

bye 命令结束交互退出程序。

以下是load-quiz、show 以及第一次 step 命令的运行效果:

H:\Read\num\Release>sudoku.exe

Order please:
load-quiz h:\s.txt
Quiz loaded. Order please:
show
000 000 000
023 000 780
100 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000 Order please:
step
000 000 000
023 000 780
100 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000 Candidates:
[1,1]: 5 6 7 8
[1,2]: 4 5 7 8 9
...
[2,1]: 5 6
[2,4]: 1 5 9
[2,5]: 1
[2,6]: 5 9
...
[9,8]: 1 2 3 4 5 6 7
[9,9]: 2 3 4 5 7 Order please:

对比 show 显示的九宫格内容,和 step 之后显示出来的九宫格内容,会发现两者内容完全一致,即这一次 step 还没有开始填数。随后的 candidates 内容显示了各个待填数的格子里所有可能的候选值,其中 [2,5]: 1 表示第 2 行第 5 列的位置只有一个候选值 1,说明下一步这个位置会填上 1。具体看一下:

Order please:
step
000 000 000
023 010 780
100 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000 Candidates:
[1,1]: 5 6 7 8
...
[1,9]: 2 3 4 5
[2,1]: 5 6
[2,4]: 5 9
[2,6]: 5 9
[2,9]: 4 5
[3,2]: 5 7 8
...
[9,9]: 2 3 4 5 7 Order please:

可以看到 [2,5] 位置如期填上了 1,但是从随后的 candidates 信息里看,剩余待填格的候选项数都大于 1,其中位置最靠前的是 :

[2,1]: 5 6

由 一道心形数独题的求解 里的方法,可以直接确定 [2,1] = 6 以及 [2,9] = 4,但这个程序并没有实现这个方法。因此,在下一步里会尝试让 [2,1] = 5。具体看一下:

Order please:
step
Guess [2,1] level 1 at 1 out of 2 000 000 000
523 010 780
100 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000 Candidates:
[1,1]: 6 7 8
...
[1,9]: 2 3 4 5
[2,4]: 9
[2,6]: 9
[2,9]: 4
[3,2]: 7 8
[3,3]: 7 8
...
[9,9]: 2 3 4 5 7 At guess level 1 [2,1] 1 Order please:

[2,1] 位置上如期填上了 5。在 step 命令之后有如下一句信息:

Guess [2,1] level 1 at 1 out of 2

这是说,[2,1] 位置上有两个候选值,这里先尝试第一个候选值(at 1 out of 2),当前处在的猜测级数为1(level 1)。

随后的 candidates 信息里有:

[2,4]: 9
[2,6]: 9

即 [2,4] 和 [2,6] 位置的唯一候选值都为 9,因为同处第 2 行,这是不合理的。因此,下一步要做出调整:

Order please:
step
9 is in row 2 before!
Forward guess [2,1] level 1 at 2 out of 2 000 000 000
623 010 780
100 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000 Candidates:
[1,1]: 5 7 8
...
[2,4]: 5 9
[2,6]: 5 9
[2,9]: 4 5
...
[9,9]: 2 3 4 5 7 At guess level 1 [2,1] 2 Order please:

[2,1] 位置如期调整为第二个候选值 6。

Forward guess [2,1] level 1 at 2 out of 2

Forward guess 是指在同一个猜测级向下一个候选值调整,即两个候选值中的第二个(at 2 out of 2)。

在随后的 candidates 信息里,剩余待填格的候选项数都大于 1,说明又要做更进一级的猜测,即猜测级数为 2。其中位置最靠前的是 :

[2,4]: 5 9

下一步会猜测 [2,4] = 5:

Order please:
step
Guess [2,4] level 2 at 1 out of 2 000 000 000
623 510 780
100 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000 Candidates:
[1,1]: 5 7 8
...
[2,6]: 9
[2,9]: 4
[3,2]: 5 7 8
...
[9,9]: 2 3 4 5 7 At guess level 2 [2,4] 1 Order please:

从如下信息可以看出猜测级别进到了 level 2:

Guess [2,4] level 2 at 1 out of 2

新的候选值信息里有唯一候选值的情形,下一步会进一步填值:

Order please:
step
000 000 000
623 519 784
100 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000 Candidates:
[1,1]: 5 7 8
...
[3,3]: 7 8
...
[9,9]: 2 3 5 7 At guess level 2 [2,4] 1 Order please:

新的候选值信息里没有唯一候选值的情形,说明下一步里猜测会加深一级:

Order please:
step
Guess [3,3] level 3 at 1 out of 2 000 000 000
623 519 784
107 406 009 400 050 001
900 000 006
060 000 090 005 000 800
000 301 000
000 090 000 Candidates:
[1,1]: 5 8
...
[3,2]: 5 8
...
[9,9]: 2 3 5 7 At guess level 3 [3,3] 1 Order please:

后面不再一步步走了,直接试一次 run 命令:

Order please:
run
Guess [1,1] level 4 at 1 out of 2
Guess [1,2] level 5 at 1 out of 2
Guess [1,9] level 6 at 1 out of 2
Guess [1,4] level 7 at 1 out of 2
Guess [1,5] level 8 at 1 out of 2
Guess [1,7] level 9 at 1 out of 2
Guess [3,7] level 10 at 1 out of 2
Guess [4,2] level 11 at 1 out of 2
7 is in row 4 before!
Forward guess [4,2] level 11 at 2 out of 2

3 is in row 4 before!
Upward guess [3,7] level 10 at 2 out of 2

Guess [4,2] level 11 at 1 out of 2
7 is in row 4 before!
Forward guess [4,2] level 11 at 2 out of 2
...
2 is in row 4 before!
Upward guess [3,7] level 9 at 2 out of 2

8 is in row 4 before!
Upward guess [2,4] level 2 at 2 out of 2

Guess [3,3] level 3 at 1 out of 2
Guess [1,1] level 4 at 1 out of 2
Guess [1,2] level 5 at 1 out of 2
Guess [1,9] level 6 at 1 out of 2
Guess [1,4] level 7 at 1 out of 2
Guess [1,5] level 8 at 1 out of 2
Guess [1,7] level 9 at 1 out of 2
Guess [3,7] level 10 at 1 out of 2
Guess [4,2] level 11 at 1 out of 2
Guess [5,3] level 12 at 1 out of 2 549 738 162
623 915 784
187 426 359 438 659 271
951 872 436
762 143 598 395 264 817
276 381 945
814 597 623 Done [steps:1058, solution sum:1].
Run time: 763 milliseconds; steps: 1058, solution sum: 1. Order please:

这次的 run 命令求得了一个解,为求得这个解,一共走了 1058 步,耗时大约为 763 毫秒。

从这次 run 的前期输出信息看,进到 guess level 11 时,因出现冲突,而做了一次平级(forward guess)调整:

Forward guess [4,2] level 11 at 2 out of 2

随后这次平级调整同样出现冲突,且无法再做平级调整,于是做了一次降级(upward guess)调整:

Upward guess [3,7] level 10 at 2 out of 2

从这次 run 的后期输出信息看,最后一次调整是从 level 9 直降到 level 2:

Upward guess [3,7] level 9 at 2 out of 2
8 is in row 4 before!
Upward guess [2,4] level 2 at 2 out of 2

即前面 level 2 时由于 [2,4]: 5 9 猜测的 [2,4] = 5 是错误的,直到此时才调整为 [2,4] = 9,随后又依次逐级进到 level 12 才求出第一个解。

再来一次 run 命令:

...
2 is in col 3 before!
Forward guess [5,2] level 10 at 2 out of 2 Guess [5,3] level 11 at 1 out of 2 5 is in col 7 before!
Forward guess [5,3] level 11 at 2 out of 2 Candidates for [8,7] went wrong
No more solution (solution sum is 1).
794 832 615
623 915 784
158 476 329 487 659 231
932 000 506
561 000 490 005 000 800
009 301 000
006 090 000 Invalid quiz [steps:4496] - no more solution (solution sum is 1)
Run time: 1618 milliseconds; steps: 4496, solution sum: 1. Order please:

上一次 run 已经找到一个解,新的 run 会从上次得到解的位置试图做平级调整、降级调整等手段,尝试找一个新的解。但本例中,第二次 run 走到了末尾都没有找到新解,说明本例只有一个解。

Candidates for [8,7] went wrong

这句输出说明,最后得到的九宫格里 [8,7] 位置不能填 1 到 9 的任何数字(从第 8 行看,不能为 1、3、9,从第 7 列看,不能为 2、3、4、5、6、7、8)。

No more solution (solution sum is 1).

这句说明,当时所处的各级 guess level 都已是该级的最后一个选项值,出现冲突后无法做平级调整或降级调整,即所有可能有解的情形都已经走完了。

这次 run 命令耗时约 1618 毫秒。而从第一个 step 算起,包括两次 run 里面的 step 数,合计为 4496 步(steps)。

SudokuSolver 1.0 实现程序与说明

一个代码文件 sudoku.cpp 总计约 540 行代码。

Cell 结构

 1 #include <stdio.h>
2 #include <stdint.h>
3 #include <string>
4 #include <vector>
5 #include <set>
6 #include <stack>
7 #include <iostream>
8 #include <fstream>
9 #include <ctime>
10
11 typedef unsigned char u8;
12 typedef unsigned long ulong;
13
14 struct Cell {
15 u8 val;
16 u8 candidates[10];
17 Cell() {memset(this, 0, sizeof(Cell));}
18 };

一个 Cell 对应九宫格中的一个数字格。分量 val 为该数字格的值,初始值为 0 表示该格的数字待填;分量 candidates 记录待填格的候选值数组,下标从 1 开始(下标为 0 的单元记录候选值个数)。

Snapshot 结构

 1 struct Snapshot
2 {
3 Cell seqCell[81];
4 std::set<u8> setBlank;
5 std::vector<std::set<u8> > rowTaken;
6 std::vector<std::set<u8> > colTaken;
7 std::vector<std::set<u8> > blkTaken;
8 u8 state;
9 u8 guessLevel;
10 u8 guessPos;
11 u8 guessValPos;
12 };

一个 Snapshot 记录某一次猜测的上下文,包括:

seqCell,每个数字格的值与候选值信息;

setBlank,所有待填格(值为 0)的下标集合;

rowTaken、colTaken、blkTaken,记录每一行、每一列、每一宫已填数字的集合;

state,当时 quiz 所处状态;

guessLevel、guessPos、guessValPos,猜测级别、猜测数字格对应的下标、猜测数字格的取值在候选数列中的下标。

CQuizDealer 类

 1 class CQuizDealer
2 {
3 public:
4 void loadQuiz(std::string& strAbsFile);
5 void showQuiz();
6 void step();
7 void run();
8 static CQuizDealer* instance() {
9 return (NULL == sm_pInst ? (sm_pInst = new CQuizDealer) : sm_pInst);
10 }
11
12 private:
13 enum {STA_UNLOADED = 0, STA_LOADED, STA_INVALID, STA_VALID, STA_DONE};
14 CQuizDealer() : m_state(STA_UNLOADED), m_guessLevel(0), m_guessPos(0),
m_guessValPos(0), m_soluSum(0), m_steps(0) {};
15 void parse();
16 void adjust();
17 void initTakens();
18 void initTaken(std::vector<std::set<u8> >& vec);
19 bool fillTaken(u8 key, u8 val, std::vector<std::set<u8> >& vec);
20 bool adjustTakens(u8 idx, u8 val);
21 bool calcCandidates(u8 idx);
22 bool reCalcCandidates();
23 void guess(u8 idx);
24 void pushIn(u8 idx, u8 valIdx);
25 bool shift(u8 idx, u8 valIdx);
26 void nextGuess();
27 void backGuess();
28 void useSnapshot(Snapshot* pSnap);
29
30 Cell m_seqCell[81];
31 std::set<u8> m_setBlank;
32 std::vector<std::set<u8> > m_rowTaken;
33 std::vector<std::set<u8> > m_colTaken;
34 std::vector<std::set<u8> > m_blockTaken;
35 u8 m_state;
36 u8 m_guessLevel;
37 u8 m_guessPos;
38 u8 m_guessValPos;
39 ulong m_soluSum;
40 ulong m_steps;
41
42 std::stack<Snapshot*> m_stkSnap;
43 static CQuizDealer* sm_pInst;
44 };

CQuizDealer 的成员变量大多和 Snapshot 的成员相对应,m_soluSum 记录总解数,m_steps 记录求解过程中所用的总步数,m_stkSnap 是个堆栈,记录各级猜测的上下文;sm_pInst 是 CQuizDealer 类的单例指针,使用 CQuizDealer::instance() 访问。

main 函数

 1 int main()
2 {
3 std::string strOrder;
4 while (!isQuitSet()) {
5 printf("\nOrder please:\n");
6 getline(std::cin, strOrder);
7 dealOrder(strOrder);
8 }
9 return 0;
10 }

其中用到的 isQuitSet() 为:

1 static bool s_bQuit = false;
2 bool isQuitSet() {return s_bQuit;}
3 void setQuit() {s_bQuit = true;}

dealOrder 和 showOrderList 函数

 1 void showOrderList()
2 {
3 printf("Sudoku Solver 1.0 2021/9/20 by readalps\n\n");
4 printf("Order List:\n");
5 printf("load-quiz <file>: load quiz from file\n");
6 printf("show: show quiz info\n");
7 printf("step: step forward\n");
8 printf("run: run till the end or a new solution met\n");
9 printf("bye: quit\n");
10 }
11
12 void dealOrder(std::string& strOrder)
13 {
14 std::string strEx;
15 if ("bye" == strOrder)
16 setQuit();
17 else if (matchPrefixEx(strOrder, "load-quiz", strEx))
18 CQuizDealer::instance()->loadQuiz(strEx);
19 else if ("show" == strOrder)
20 CQuizDealer::instance()->showQuiz();
21 else if ("step" == strOrder)
22 CQuizDealer::instance()->step();
23 else if ("run" == strOrder)
24 CQuizDealer::instance()->run();
25 else
26 showOrderList();
27 }

其中用到的 matchPrefixEx 函数实现为:

 1 bool matchPrefixEx(const std::string& src, const char* prefix, std::string& strEx)
2 {
3 if (NULL == prefix || 0 == strlen(prefix))
4 return false;
5 size_t len = strlen(prefix);
6 if (0 == strncmp(src.c_str(), prefix, len)) {
7 strEx = src.substr(len);
8 return true;
9 }
10 return false;
11 }

CQuizDealer 类内部实现放到下一篇。

SudokuSolver 1.0:用C++实现的数独解题程序 【一】的更多相关文章

  1. SudokuSolver 1.0:用C++实现的数独解题程序 【二】

    本篇是 SudokuSolver 1.0:用C++实现的数独解题程序 [一] 的续篇. CQuizDealer::loadQuiz 接口实现 1 CQuizDealer* CQuizDealer::s ...

  2. SudokuSolver 2.0:用C++实现的数独解题程序 【一】

    SudokuSolver 2.0 实现效果 H:\Read\num\Release>sudoku.exe Order please: Sudoku Solver 2.0 2021/10/2 by ...

  3. 用C++实现的数独解题程序 SudokuSolver 2.3 及实例分析

    SudokuSolver 2.3 程序实现 用C++实现的数独解题程序 SudokuSolver 2.2 及实例分析 里新发现了一处可以改进 grp 算法的地方,本次版本实现了对应的改进 grp 算法 ...

  4. 用C++实现的数独解题程序 SudokuSolver 2.2 及实例分析

    SudokuSolver 2.2 程序实现 根据 用C++实现的数独解题程序 SudokuSolver 2.1 及实例分析 里分析,对 2.1 版做了一些改进和尝试. CQuizDealer 类声明部 ...

  5. 用C++实现的数独解题程序 SudokuSolver 2.1 及实例分析

    SudokuSolver 2.1 程序实现 在 2.0 版的基础上,2.1 版在输出信息上做了一些改进,并增加了 runtil <steps> 命令,方便做实例分析. CQuizDeale ...

  6. 用C++实现的数独解题程序 SudokuSolver 2.4 及实例分析

    SudokuSolver 2.4 程序实现 本次版本实现了 用C++实现的数独解题程序 SudokuSolver 2.3 及实例分析 里发现的第三个不完全收缩 grp 算法 thirdGreenWor ...

  7. 用C++实现的数独解题程序 SudokuSolver 2.6 的新功能及相关分析

    SudokuSolver 2.6 的新功能及相关分析 SudokuSolver 2.6 的命令清单如下: H:\Read\num\Release>sudoku.exe Order please: ...

  8. 用C++实现的数独解题程序 SudokuSolver 2.7 及实例分析

    引言:一个 bug 的发现 在 MobaXterm 上看到有内置的 Sudoku 游戏,于是拿 SudokuSolver 求解,随机出题,一上来是个 medium 级别的题: 073 000 060 ...

  9. 数独GUI程序项目实现

    数独GUI程序项目实现 导语:最近玩上了数独这个游戏,但是找到的几个PC端数独游戏都有点老了...我就想自己做一个数独小游戏,也是一个不错的选择. 前期我在网上简单地查看了一些数独游戏的界面,代码.好 ...

随机推荐

  1. tensorflow 单机多卡 官方cifar10例程

    测试了官方历程,看没有问题,加上时间紧任务重,就不深究了. 官方tutorials:https://www.tensorflow.org/tutorials/images/deep_cnn githu ...

  2. DNS重新绑定攻击

    来自微信外挂的安全风险 DNS重新绑定攻击 DDNS 动态域名设置

  3. netfilter框架之hook点

    1. Netfilter中hook的所在位置 当网络上有数据包到来时,由驱动程序将数据包从网卡内存区通过DMA转移到设备主存区(内存区), 之后触发中断通知CPU进行异步响应,之后ip_rcv函数会被 ...

  4. 远程线程注入DLL突破session 0 隔离

    远程线程注入DLL突破session 0 隔离 0x00 前言 补充上篇的远程线程注入,突破系统SESSION 0 隔离,向系统服务进程中注入DLL. 0x01 介绍 通过CreateRemoteTh ...

  5. 测试平台系列(55) 引入AceEditor(代码编辑器)

    大家好,我是米洛,求三连!求关注测试开发坑货! 回顾 我们上一节已经写好了左侧数据表目录,今天继续完成sql编辑器的部分. 调研组件 monaco 因为我们的项目用的是React,市面上很多编辑器都是 ...

  6. [第六篇]——云服务器之Spring Cloud直播商城 b2b2c电子商务技术总结

    云服务器 云服务器(Elastic Compute Service, ECS)是一种简单高效.安全可靠.处理能力可弹性伸缩的计算服务. 云服务器管理方式比物理服务器更简单高效,我们无需提前购买昂贵的硬 ...

  7. 完美数java

    完全数(Perfect number),又称完美数或完备数,是一些特殊的自然数.它所有的真因子(即除了自身以外的约数)的和(即因子函数),恰好等于它本身.如果一个数恰好等于它的因子之和,则称该数为&q ...

  8. 多选Combobox的实现(适合MVVM模式)

    MVVM没有.cs后台逻辑,一般依靠command驱动逻辑及通过binding(vm层的属性)来显示前端 我的数据类Student有三个属性int StuId ,string StuName ,boo ...

  9. undefined和null

    undefined和null undefined的情景: 声明变量为赋值 var name; console.og(name); //undefined 访问对象上不存在的属性 var obj={} ...

  10. idea鼠标双击.log日志文件无法打开

    发现只要再mybatis-config.xml的起别名中加<package name="xxx"/>,就会导致Reader entry: ����   1 n乱码,而R ...