SudokuSolver 2.2 程序实现

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

CQuizDealer 类声明部分的修改

class CQuizDealer
{
public:
...
void run(ulong tilsteps = 0);
void setOnlyGrpMode() {m_onlyGrp = true;}
...
private:
...
CQuizDealer() : m_state(STA_UNLOADED), ..., m_onlyGrp(false) {};
inline void incSteps() {++m_steps;}
inline bool IsDone(u8 ret) {return (ret == RET_OK || ret == RET_WRONG);}...
u8 filterOneGroup(u8* pGrp);
bool completeShrinkByGrp(u8* pGrp, u8* pTimes);
u8 incompleteShrinkByGrp(u8 valSum, u8* pCelSumExs, u8* pGrp);
bool sameCandidates(u8 cel1, u8 cel2);
...
ulong m_steps;
bool m_onlyGrp;
...
};

新增的 setOnlyGrpMode 接口用于控制只采用第一类收缩算法求解。

filterCandidates 接口实现修改

u8 CQuizDealer::filterCandidates()
{
incSteps();
u8 ret = RET_PENDING;
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;
if (!m_onlyGrp) {
for (u8 row = 0; row < 9; ++row) {
ret = filterRowCandidatesEx(row);
if (IsDone(ret))
return ret;
}
for (u8 col = 0; col < 9; ++col) {
ret = filterColCandidatesEx(col);
if (IsDone(ret))
return ret;
}
}
if (ret == RET_SHRUNKEN) {
printf("incomplete shrink met, filter again\n");
return filterCandidates();
}
return ret;
}

增加了 onlyGrp 控制,即上面所说的是否只采用第一类收缩算法求解。末尾增加的条件递归调用,源自上一篇的实例分析。

filterRowGroup 接口实现修改

u8 CQuizDealer::filterRowGroup(u8 row)
{
u8 celIdxs[10] = {0}; // first item denotes sum of zeros
u8 base = row * 9;
for (u8 col = 0; col < 9; ++col) {
if (m_seqCell[base + col].val == 0) {
celIdxs[0] += 1;
celIdxs[celIdxs[0]] = base + col;
}
}
if (celIdxs[0] == 0)
return RET_PENDING;
u8 ret = filterOneGroup(celIdxs);
if (ret == RET_OK)
printf("%u) row %d complete shrunken by group\n", m_steps, (int)row + 1);
else if (ret == RET_WRONG)
printf("%u) row %d shrink by group went WRONG\n", m_steps, (int)row + 1);
else if (ret == RET_SHRUNKEN)
printf("%u) row %d incomplete shrunken by group\n", m_steps, (int)row + 1);
return ret;
}

filterColGroup 和 filterBlkGroup 接口修改类似。

filterOneGroup 接口新实现

 1 u8 CQuizDealer::filterOneGroup(u8* pGrp)
2 {
3 u8 times[20] = {0};
4 if (completeShrinkByGrp(pGrp, times))
5 return RET_OK;
6
7 u8 size = pGrp[0];
8 u8 celSumExs[100] = {0};
9 for (u8 idx = 1; idx <= size; ++idx) {
10 u8 valSum = m_seqCell[pGrp[idx]].candidates[0];
11 u8 base = valSum * 10;
12 celSumExs[base] += 1;
13 u8 pos = base + celSumExs[base];
14 celSumExs[pos] = pGrp[idx];
15 }
16
17 for (u8 idx = 2; idx <= 6; ++idx) {
18 u8 ret = incompleteShrinkByGrp(idx, celSumExs, pGrp);
19 if (ret != RET_PENDING)
20 return ret;
21 }
22 return RET_PENDING;
23 }

filterOneGroup 接口的原有实现只支持完全收缩,现在放到了新增 completeShrinkByGrp 接口里:

bool CQuizDealer::completeShrinkByGrp(u8* pGrp, u8* pTimes)
{
u8 size = pGrp[0];
for (u8 idx = 1; idx <= size; ++idx) {
u8 sum = m_seqCell[pGrp[idx]].candidates[0];
for (u8 inn = 1; inn <= sum; ++inn) {
u8 val = m_seqCell[pGrp[idx]].candidates[inn];
pTimes[val] += 1;
pTimes[val + 9] = pGrp[idx];
}
}
bool ret = false;
for (u8 val = 1; val <= 9; ++val) {
if (pTimes[val] == 1) {
ret = true;
u8 celIdx = pTimes[val + 9];
m_seqCell[celIdx].candidates[0] = 1;
m_seqCell[celIdx].candidates[1] = val;
}
}
return ret;
}

新增 incompleteShrinkByGrp 接口实现

 1 u8 CQuizDealer::incompleteShrinkByGrp(u8 valSum, u8* pCelSumExs, u8* pGrp)
2 {
3 u8 base = valSum * 10;
4 if (pCelSumExs[base] < valSum)
5 return RET_PENDING;
6 u8 itemSum = 0;
7 Item items[9];
8 for (u8 pos = 1; pos <= pCelSumExs[base]; ++pos) {
9 u8 idx = 0;
10 for (; idx < itemSum; ++idx)
11 if (sameCandidates(pCelSumExs[base + pos], items[idx].celIdxs[0])) {
12 items[idx].celIdxs[items[idx].sameSum] = pCelSumExs[base + pos];
13 items[idx].sameSum++;
14 break;
15 }
16 if (idx == itemSum) {
17 items[itemSum].sameSum = 1;
18 items[itemSum].celIdxs[0] = pCelSumExs[base + pos];
19 ++itemSum;
20 }
21 }
22 for (u8 idx = 0; idx < itemSum; ++idx) {
23 if (items[idx].sameSum > valSum)
24 return RET_WRONG;
25 }
26 bool shrunken = false;
27 for (u8 idx = 0; idx < itemSum; ++idx) {
28 if (items[idx].sameSum < valSum)
29 continue;
30 for (u8 pos = 1; pos <= pGrp[0]; ++pos) {
31 if (inSet(pGrp[pos], (u8*)&(items[idx])))
32 continue;
33 u8 isVals[10] = {0};
34 u8 cel1 = items[idx].celIdxs[0];
35 u8 cel2 = pGrp[pos];
36 intersection(m_seqCell[cel1].candidates, m_seqCell[cel2].candidates, isVals);
37 if (isVals[0] == 0)
38 continue;
39 shrunken = true;
40 for (u8 valIdx = 1; valIdx <= isVals[0]; ++valIdx)
41 if (!removeVal(m_seqCell[cel2].candidates, isVals[valIdx]))
42 return RET_WRONG;
43 }
44 }
45 return (shrunken ? RET_SHRUNKEN : RET_PENDING);
46 }

里面用到的 Item 结构以及 sameCandidates 接口为:

struct Item {
u8 sameSum;
u8 celIdxs[9];
Item() {sameSum = 0;}
};

bool CQuizDealer::sameCandidates(u8 cel1, u8 cel2)
  {
      u8 sum = m_seqCell[cel1].candidates[0];
      if (sum != m_seqCell[cel2].candidates[0])
          return false;
      for (u8 idx = 1; idx <= sum; ++idx)
          if (m_seqCell[cel1].candidates[idx] != m_seqCell[cel2].candidates[idx])
              return false;
      return true;
  }

filterRowCandidatesEx 接口实现小修改

u8 CQuizDealer::filterRowCandidatesEx(u8 row)
{
...
for (u8 idx = 0; idx < 3; ++idx) {
ret = filterRowByPolicy1(row, idx, vals, blkTakens);
if (IsDone(ret))
return ret;
}
for (u8 idx = 0; idx < 3; ++idx) {
ret = filterRowByPolicy2(row, idx, vals, blkTakens);
if (IsDone(ret))
return ret;
}
return ret;
}

filterColCandidatesEx 接口的修改类似。

shrinkRowCandidatesP1 接口实现小修改

u8 CQuizDealer::shrinkRowCandidatesP1(u8* pBlk, u8* pRow, u8 zeroSum, u8 row, u8 colBase)
{
...
u8 shrunken = 0;
for (u8 col = colBase; col < colBase + 3; ++col) {
...
if (ret != RET_PENDING) {
printf(" worked by row-ply1.\n");
if (ret == RET_OK)
shrunken = 1; // complete
else if (ret == RET_SHRUNKEN && shrunken == 0)
shrunken = 2; // incomplete
}
}
if (shrunken == 1) {
ret = RET_OK;
printf("%u) row %d shrunken ply-1 by blk %d\n", m_steps, (u8)row + 1, (u8)colBase / 3 + 1);
}
else if (shrunken == 2)
ret = RET_SHRUNKEN;
return ret;
}

shrinkRowCandidatesP2 接口以及 shrinkColCandidatesP1 和 shrinkColCandidatesP2 接口的修改类似。

其他小修改

// 1.0 2021/9/20
// 2.0 2021/10/2
// 2.1 2021/10/4
#define STR_VER "Sudoku Solver 2.2 2021/10/10 by readalps\n\n" void showOrderList()
{
...
printf("onlygrp: only using group shrink\n");
printf("step: step forward\n");
...
} void dealOrder(std::string& strOrder)
{
...
else if ("onlygrp" == strOrder)
CQuizDealer::instance()->setOnlyGrpMode();
else if ("step" == strOrder)
...

实例分析

继续以 SudokuSolver 1.0:用C++实现的数独解题程序 【二】 里试验过的“最难”数独题为例做分析。在第一次 run 命令的输出中有如下信息:

506) Forward guess [1,4] level 11 at 2 out of 2
[2,1]: 1 4 5 9 shrunken to 5 9 worked by row-ply2.
[2,8]: 4 5 9 shrunken to 5 9 worked by row-ply2.
509) Guess [1,3] level 12 at 1 out of 2

第 2 行有两个空位发生了不完全收缩,走的不是第一类收缩算法(by grp),而是第二类收缩算法(by ply)。重新用 runtil 506 命令进到当时的上下文做深入分析:

...
506) Forward guess [1,4] level 11 at 2 out of 2
820 703 601
003 600 807
070 090 203 050 007 104
000 045 706
007 100 935 001 009 568
008 500 319
090 000 472 Steps:506
Candidates:
[1,3]: 4 5 9 [1,5]: 5 [1,8]: 4 5 9
[2,1]: 1 4 5 9 [2,2]: 1 4 [2,5]: 1 2 5
[2,6]: 1 2 4 [2,8]: 4 5 9 [3,1]: 1 4 5 6
[3,3]: 4 5 6 [3,4]: 4 8 [3,6]: 1 4 8
[3,8]: 4 5 [4,1]: 2 3 6 9 [4,3]: 2 6 9
[4,4]: 2 3 8 9 [4,5]: 2 3 6 8 [4,8]: 2 8
[5,1]: 1 2 3 9 [5,2]: 1 3 8 [5,3]: 2 9
[5,4]: 2 3 8 9 [5,8]: 2 8 [6,1]: 2 4 6
[6,2]: 4 6 8 [6,5]: 2 6 8 [6,6]: 2 6 8
[7,1]: 2 3 4 7 [7,2]: 3 4 [7,4]: 2 3 4
[7,5]: 2 3 7 [8,1]: 2 4 6 7 [8,2]: 4 6
[8,5]: 2 6 7 [8,6]: 2 4 6 [9,1]: 3 5 6
[9,3]: 5 6 [9,4]: 3 8 [9,5]: 1 3 6 8
[9,6]: 1 6 8
The foremost cell with 1 candidate(s) at [1,5] At guess level 11 [1,4] 2
Run time: 326 milliseconds; steps: 506, solution sum: 0. Order please:

由 The foremost cell with 1 candidate(s) at [1,5] 可知当时 [1,5] 位置可以直接填值。先走完这一步:

Order please:
step
820 753 601
003 600 807
070 090 203 050 007 104
000 045 706
007 100 935 001 009 568
008 500 319
090 000 472 Steps:507
Candidates:
[1,3]: 4 9 [1,8]: 4 9 [2,1]: 1 4 5 9
[2,2]: 1 4 [2,5]: 1 2 [2,6]: 1 2 4
[2,8]: 4 5 9 [3,1]: 1 4 5 6 [3,3]: 4 5 6
[3,4]: 4 8 [3,6]: 1 4 8 [3,8]: 4 5
[4,1]: 2 3 6 9 [4,3]: 2 6 9 [4,4]: 2 3 8 9
[4,5]: 2 3 6 8 [4,8]: 2 8 [5,1]: 1 2 3 9
[5,2]: 1 3 8 [5,3]: 2 9 [5,4]: 2 3 8 9
[5,8]: 2 8 [6,1]: 2 4 6 [6,2]: 4 6 8
[6,5]: 2 6 8 [6,6]: 2 6 8 [7,1]: 2 3 4 7
[7,2]: 3 4 [7,4]: 2 3 4 [7,5]: 2 3 7
[8,1]: 2 4 6 7 [8,2]: 4 6 [8,5]: 2 6 7
[8,6]: 2 4 6 [9,1]: 3 5 6 [9,3]: 5 6
[9,4]: 3 8 [9,5]: 1 3 6 8 [9,6]: 1 6 8
The foremost cell with 2 candidate(s) at [1,3] At guess level 11 [1,4] 2 Order please:

走完 507 步时,第 2 行的空位候选值分布情况为:

[2,1]: 1 4 5 9
[2,2]: 1 4 [2,5]: 1 2 [2,6]: 1 2 4
[2,8]: 4 5 9

这里,5 和 9 只能填入两个空位,即 [2,1] 和 [2,8] 中,因此,这两个空位只能是一个填 5 另一个填 9。于是,这两个空位里的除 5 和 9 之外的其他候选值都可以排除掉。这和上一篇的实例分析部分发现的 grp 算法可改进之处还不一样,这是新发现的一处可以改进 grp 算法的地方。

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

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

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

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

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

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

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

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

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

  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. 过WAF的小思路

    过WAF的小思路 前言 最近在学习了一波CMS漏洞,尝试看了几个菠菜站,有宝塔WAF...向WHOAMI大佬取经回来后,绕过了一个WAF.觉得是时候要认真总结一下了:) 前期的过程 菠菜采用的是Thi ...

  2. 矩阵BFS

    leetcode 1091矩阵BFS 在一个 N × N 的方形网格中,每个单元格有两种状态:空(0)或者阻塞(1). 一条从左上角到右下角.长度为 k 的畅通路径,由满足下述条件的单元格 C_1, ...

  3. 一个Django项目中实现的简单HTML页面布局

    1 - 基础页面(被继承的模板) {% load static %} <!DOCTYPE html> <html lang="en"> <head&g ...

  4. 机器学习之支持向量机(python)

    参考链接:https://blog.csdn.net/weixin_33514582/article/details/113321749.https://blog.csdn.net/weixin_44 ...

  5. 20210712考试-2021noip11

    这篇总结比我写的好多了建议直接去看 T1 简单的序列 考场:愣了一会,想到以最大值分治.每次枚举最大值两侧更小的区间,st表预处理前缀和和最大值,用桶统计答案. 注意分治时要去掉最大值. const ...

  6. GoLang设计模式05 - 原型模式

    原型模式也是一种创建型模式,它可以帮助我们优雅地创建对象的拷贝.在这种设计模式里面,将克隆某个对象的职责交给了要被克隆的这个对象.被克隆的对象需要提供一个clone()方法.通过这个方法可以返回该对象 ...

  7. Git 系列教程(2)- Git 安装

    前言 直接复制官网的教程了,不对自己百度吧,不然就参考下我的几篇文章 Linux安装Git(源码安装) https://www.cnblogs.com/poloyy/p/12186802.html 在 ...

  8. AntDesign VUE:上传组件自定义限制的两种方式(Boolean、Promise)

    AntD上传组件 AntDesign VUE文档 第一种方式 beforeUpload(file) { let isLt = true if (filesSize) { isLt = file.siz ...

  9. Dubbo No provider问题排查思路

    本文已收录 https://github.com/lkxiaolou/lkxiaolou 欢迎star. 不想看字的同学可直接划到底部查看思维导图 问题分析 使用过Dubbo的朋友很多都碰到过如下报错 ...

  10. DISCUZ论坛添加页头及页尾背景图片的几种方法

    先给大家分享页头添加背景图片的两种方法:1. 第一种效果,是给discuz的整体框架添加背景图片,见图示: 添加方法如下:找到你现在使用模板common文件下的header.html文件,在<h ...