引言:一个 bug 的发现

在 MobaXterm 上看到有内置的 Sudoku 游戏,于是拿 SudokuSolver 求解,随机出题,一上来是个 medium 级别的题:

073 000 060
980 460 000
000 007 304
000 000 578
010 000 090
248 000 000
105 900 000
000 034 081
090 000 230

运行过程信息如下:

Order please:
load-quiz h:\s.txt
Quiz loaded. Order please:
guess-mode 1
In interactive guessing mode:1 (0:no; other:yes) Order please:
run
7) row 1 complete shrunken by group
9) row 3 complete shrunken by group
11) row 3 complete shrunken by group
13) row 3 complete shrunken by group
15) row 4 complete shrunken by group
1GW: 6 shrunken out of [4,3]
1GW: 3 shrunken out of [4,4]
1GW: 6 shrunken out of [4,4]
1GW: 6 shrunken out of [4,6]
17) row 4 incomplete shrunken by group
Take a guess please, by default it will be [3,1]=5: 18) Guess [3,1] level 1 at 1 out of 2
18) shrinking 2 from [8,2] went wrong
19) Forward guess [3,1] level 1 at 2 out of 2
473 215 869
982 463 157
651 897 324 369 142 578
517 386 492
248 759 613 135 928 746
726 534 981
894 671 235 Fine [steps:23, solution sum:1].
Run time: 1958 milliseconds; steps: 23, solution sum: 1.
Biggest level on this run(til): 1 Order please:
step
24) No more solution (solution sum is 1).
... Order please:

可以看到走到 17 步时做了一次交互式猜测。好奇这么一个 medium 级别的题是否真的需要猜测,重新进到 16 步时的上下文做仔细考察:

Order please:
runtil 16
7) row 1 complete shrunken by group
9) row 3 complete shrunken by group
11) row 3 complete shrunken by group
13) row 3 complete shrunken by group
15) row 4 complete shrunken by group
473 000 869
982 463 157
001 897 324 000 040 578
010 000 492
248 000 613 105 900 746
000 034 981
090 000 235 Steps:16
Candidates:
[1,4]: 1 2 5 [1,5]: 1 2 5 [1,6]: 1 2 5
[3,1]: 5 6 [3,2]: 5 6 [4,1]: 3 6
[4,2]: 3 6 [4,3]: 6 9 [4,4]: 1 2 3 6
[4,6]: 1 2 6 9 [5,1]: 3 5 6 7 [5,3]: 6 7
[5,4]: 3 5 6 7 [5,5]: 5 7 8 [5,6]: 5 6 8
[6,4]: 5 7 [6,5]: 5 7 [6,6]: 5 9
[7,2]: 2 3 [7,5]: 2 8 [7,6]: 2 8
[8,1]: 6 7 [8,2]: 2 6 [8,3]: 6 7
[8,4]: 2 5 6 7 [9,1]: 6 7 8 [9,3]: 4 6 7
[9,4]: 1 6 7 [9,5]: 1 7 8 [9,6]: 1 6 8
The foremost cell with 2 candidate(s) at [3,1]
Run time: 49 milliseconds; steps: 16, solution sum: 0.
Biggest level on this run(til): 0

再走一步就要对 [3,1] 位置进行猜测,这时已填数的分布情况为:

473 000 869
982 463 157
001 897 324
000 040 578
010 000 492
248 000 613
105 900 746
000 034 981
090 000 235

考察第 7 行,三个空位的候选值分布情况为:

[7,2]: 2 3    [7,5]: 2 8    [7,6]: 2 8

候选值 3 只出现在 [7,2] 位置,因而必有 [7,2] = 3。而这是早期版本就已经实现了的算法,怎么到这里会做一次猜测?一定是程序有 bug。

调试程序很快发现问题出在 filterCandidates 接口实现如下标黄的语句:

u8 CQuizDealer::filterCandidates()
{
incSteps();
u8 ret = RET_PENDING;
if (m_mode != 2) {
for (u8 row = 0; row < 9; ++row)
if (ret = filterRowGroup(row))
return ret;
for (u8 col = 0; col < 9; ++col)
if (ret = filterColGroup(col))
return ret;
for (u8 blk = 0; blk < 9; ++blk)
if (ret = filterBlkGroup(blk))
return ret;
}
...return ret;
}

从第一行起逐行去调用 filterRowGroup 接口对该行做收缩处理,只要返回值不为 0,即出错或有收缩,就结束当前这一步的收缩处理。出问题时,第 4 行发生了不完全收缩,对应如下输出信息:

1GW: 6 shrunken out of [4,3]
1GW: 3 shrunken out of [4,4]
1GW: 6 shrunken out of [4,4]
1GW: 6 shrunken out of [4,6]
17) row 4 incomplete shrunken by group

这时就结束整个 filterCandidates 的处理,就会导致上面所示的发生在第 7 行的完全收缩 [7,2] = 3 直接被跳过了,随后就出现了不必要的猜测。

SudokuSolver 2.7 版代码修改

filterCandidates 接口实现修改:

 1 u8 CQuizDealer::filterCandidates()
2 {
3 incSteps();
4 u8 ret = RET_PENDING;
5 bool partlyShrunken = false;
6 if (m_mode != 2) {
7 for (u8 row = 0; row < 9; ++row) {
8 ret = filterRowGroup(row);
9 if (IsDone(ret))
10 return ret;
11 if (!partlyShrunken && ret == RET_SHRUNKEN)
12 partlyShrunken = true;
13 }
14 for (u8 col = 0; col < 9; ++col) {
15 ret = filterColGroup(col);
16 if (IsDone(ret))
17 return ret;
18 if (!partlyShrunken && ret == RET_SHRUNKEN)
19 partlyShrunken = true;
20 }
21 for (u8 blk = 0; blk < 9; ++blk) {
22 ret = filterBlkGroup(blk);
23 if (IsDone(ret))
24 return ret;
25 if (!partlyShrunken && ret == RET_SHRUNKEN)
26 partlyShrunken = true;
27 }
28 }
29 if (m_mode != 1) {
30 for (u8 row = 0; row < 9; ++row) {
31 ret = filterRowCandidatesEx(row);
32 if (IsDone(ret))
33 return ret;
34 if (!partlyShrunken && ret == RET_SHRUNKEN)
35 partlyShrunken = true;
36 }
37 for (u8 col = 0; col < 9; ++col) {
38 ret = filterColCandidatesEx(col);
39 if (IsDone(ret))
40 return ret;
41 if (!partlyShrunken && ret == RET_SHRUNKEN)
42 partlyShrunken = true;
43 }
44 }
45 if (partlyShrunken) {
46 printf("%d) incomplete shrink met, filter again\n", m_steps);
47 return filterCandidates();
48 }
49 if (ret == RET_PENDING)
50 printf("%d) no shrink happened\n", m_steps);
51 return ret;
52 }

修改了上述的 bug,末尾部分也增加了未能收缩时输出 no shrink happened 提示信息的处理。

版本信息修改:

// 2.6 2021/10/30
#define STR_VER "Sudoku Solver 2.7 2021/11/1 by readalps\n\n"

用 2.7 版求解“最难”数独题

800 000 000
003 600 000
070 090 200
050 007 000
000 045 700
000 100 030
001 000 068
008 500 010
090 000 400
Order please:
load-quiz h:\s.txt
Quiz loaded. Order please:
run
2) no shrink happened
3) Guess [8,7] level 1 at 1 out of 2
4) no shrink happened
5) Guess [7,7] level 2 at 1 out of 2
...
380) Guess [1,8] level 9 at 1 out of 2
382) no shrink happened
383) Guess [2,5] level 10 at 1 out of 2
386) shrinking 4 from [6,3] went wrong
387) Forward guess [2,5] level 10 at 2 out of 2
812 753 649
943 682 175
675 491 283 154 237 896
369 845 721
287 169 534 521 974 368
438 526 917
796 318 452 Fine [steps:392, solution sum:1].
Run time: 733 milliseconds; steps: 392, solution sum: 1.
Biggest level on this run(til): 10 Order please:

第二次 run 的输出:

787) No more solution (solution sum is 1).
800 000 306
003 600 800
670 893 201 352 967 184
186 345 700
040 128 635 001 479 568
068 530 910
090 286 473 Invalid quiz [steps:787] - no more solution (solution sum is 1)
Run time: 573 milliseconds; steps: 787, solution sum: 1.
Biggest level on this run(til): 8

2.4 版的两次 run 用了两千多步,这里只有 787 步;最大猜测级别也由 12 降到了 10。

用 2.7 版求解另一道难题

005 300 000
800 000 020
070 010 500
400 005 300
010 070 006
003 200 080
060 500 009
004 000 030
000 009 700

因为上述的 bug,此前 2.4 版求解这道数独题时,最大猜测级别达到了 11。来看 2.7 版求解的效果:

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

Order please:
load-quiz h:\s.txt
Quiz loaded. Order please:
run
2) row 2 complete shrunken by group
4) row 5 complete shrunken by group
2GW: 2 shrunken out of [4,3]
2GW: 8 shrunken out of [4,3]
2GW: 9 shrunken out of [4,3]
2GW: 5 shrunken out of [6,1]
2GW: 9 shrunken out of [6,1]
CelSet: [4,3] [6,1] ValSet: 6 7
6) blk 4 incomplete shrunken by group
6) incomplete shrink met, filter again
7) no shrink happened
8) Guess [6,2] level 1 at 1 out of 2
...
137) col 8 complete shrunken by group
145 327 698
839 654 127
672 918 543 496 185 372
218 473 956
753 296 481 367 542 819
984 761 235
521 839 764 Fine [steps:141, solution sum:1].
Run time: 168 milliseconds; steps: 141, solution sum: 1.
Biggest level on this run(til): 10 Order please:
run
142) Forward guess [1,7] level 5 at 2 out of 2
144) shrinking 8 from [9,4] went wrong
...
287) col 7 complete shrunken by group
288) shrinking 6 from [2,4] went wrong
289) No more solution (solution sum is 1).
145 300 900
839 050 127
672 018 543 426 005 371
518 473 296
793 261 485 067 500 819
954 000 632
081 609 750 Invalid quiz [steps:289] - no more solution (solution sum is 1)
Run time: 188 milliseconds; steps: 289, solution sum: 1.
Biggest level on this run(til): 6 Order please:

最大猜测级别也达到了 10。

用C++实现的数独解题程序 SudokuSolver 2.7 及实例分析的更多相关文章

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    SudokuSolver 1.0 用法与实现效果 SudokuSolver 是一个提供命令交互的命令行程序,提供的命令清单有: H:\Read\num\Release>sudoku.exe Or ...

  9. 数独GUI程序项目实现

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

随机推荐

  1. 开源的物联网技术平台(Thingsboard)

    1   总体说明 1.1   产品概述 1.1.1 Thingsboard作用 1.置备并控制设备. 2.采集设备数据并进行数据可视化. 3.分析设备数据,触发告警. 4.将数据传输到另一个系统. 5 ...

  2. mysql将语句写入表中

    使用create table语句即可 CREATE TABLE membertmp (select a.* from member as a where a.phone <> '' and ...

  3. Linux系列(29) - rpm包命名规则(1)

    RPM包命名规则 例如包名:httpd-2.2.15-15.el6.centsos.1.i686.rpm 软件包名-httpd 软件版本-2.2.15 发布的次数-15 el6.centos适合的Li ...

  4. MySQL数据库连接重试功能和连接超时功能的DB连接Python实现

    def reConndb(self): # 数据库连接重试功能和连接超时功能的DB连接 _conn_status = True _max_retries_count = 10 # 设置最大重试次数 _ ...

  5. Windows 10、Windows Server 定时任务(定时关机)

    前言 在测试过程中,有些测试机每天都需要关机,一台台很麻烦,于是想起了Windows的任务计划程序,想着试一试,就将具体过程记录一下. 过程 Windows 搜索任务计划程序 创建任务(不要选错了) ...

  6. 使用Jacoco统计服务端代码覆盖情况实践

    一.背景 随着需求的迭代,需求增加的同时,有可能会伴随着一些功能的下线.如果不对系统已经不用的代码进行梳理并删除不需要的代码,那么就会增加系统维护成本以及理解成本.但经历比较长的迭代以及系统交接,可能 ...

  7. java 从零开始手写 RPC (01) 基于 websocket 实现

    RPC 解决的问题 RPC 主要是为了解决的两个问题: 解决分布式系统中,服务之间的调用问题. 远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑. 这一节我们来学习下如何基于 we ...

  8. C++核心编程 4 类和对象-封装

    C++面向对象的三大特性:封装.继承.多态 C++认为万事万物皆为对象,对象上有其属性和行为 封装 意义:1.将属性和行为作为一个整体,表现生活中的事物 语法: class 类名{   访问权限:属性 ...

  9. 熊猫分布密度制图(ArcPy实现)

    一.背景 大熊猫是我国国家级珍惜保护动物,熊猫的生存必须满足一定槽域(独占的猎食与活动范围)条件.因此,科学准确的分析熊猫的分布情况,对合理制定保护措施和评价保护成效具有重要意义. 二.目的 通过练习 ...

  10. xml文件报Element 'beans' cannot have character [children],because the type's content type is element

    写springMvc.xml文件时,偶然遇到 Element 'beans' cannot have character [children],because the type's content t ...