SudokuSolver 2.2 程序实现

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

CQuizDealer 类声明部分的修改

  1. class CQuizDealer
  2. {
  3. public:
  4. ...
    void run(ulong tilsteps = 0);
  5. void setOnlyGrpMode() {m_onlyGrp = true;}
  6. ...
    private:
  7. ...
  8. CQuizDealer() : m_state(STA_UNLOADED), ..., m_onlyGrp(false) {};
  9. inline void incSteps() {++m_steps;}
  10. inline bool IsDone(u8 ret) {return (ret == RET_OK || ret == RET_WRONG);}...
  11. u8 filterOneGroup(u8* pGrp);
  12. bool completeShrinkByGrp(u8* pGrp, u8* pTimes);
  13. u8 incompleteShrinkByGrp(u8 valSum, u8* pCelSumExs, u8* pGrp);
  14. bool sameCandidates(u8 cel1, u8 cel2);
  15. ...
    ulong m_steps;
  16. bool m_onlyGrp;
  17. ...
  18. };

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

filterCandidates 接口实现修改

  1. u8 CQuizDealer::filterCandidates()
  2. {
  3. incSteps();
  4. u8 ret = RET_PENDING;
  5. for (u8 row = 0; row < 9; ++row)
  6. if (ret = filterRowGroup(row))
  7. return ret;
  8. for (u8 col = 0; col < 9; ++col)
  9. if (ret = filterColGroup(col))
  10. return ret;
  11. for (u8 blk = 0; blk < 9; ++blk)
  12. if (ret = filterBlkGroup(blk))
  13. return ret;
  14. if (!m_onlyGrp) {
  15. for (u8 row = 0; row < 9; ++row) {
  16. ret = filterRowCandidatesEx(row);
  17. if (IsDone(ret))
  18. return ret;
  19. }
  20. for (u8 col = 0; col < 9; ++col) {
  21. ret = filterColCandidatesEx(col);
  22. if (IsDone(ret))
  23. return ret;
  24. }
  25. }
  26. if (ret == RET_SHRUNKEN) {
  27. printf("incomplete shrink met, filter again\n");
  28. return filterCandidates();
  29. }
  30. return ret;
  31. }

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

filterRowGroup 接口实现修改

  1. u8 CQuizDealer::filterRowGroup(u8 row)
  2. {
  3. u8 celIdxs[10] = {0}; // first item denotes sum of zeros
  4. u8 base = row * 9;
  5. for (u8 col = 0; col < 9; ++col) {
  6. if (m_seqCell[base + col].val == 0) {
  7. celIdxs[0] += 1;
  8. celIdxs[celIdxs[0]] = base + col;
  9. }
  10. }
  11. if (celIdxs[0] == 0)
  12. return RET_PENDING;
  13. u8 ret = filterOneGroup(celIdxs);
  14. if (ret == RET_OK)
  15. printf("%u) row %d complete shrunken by group\n", m_steps, (int)row + 1);
  16. else if (ret == RET_WRONG)
  17. printf("%u) row %d shrink by group went WRONG\n", m_steps, (int)row + 1);
  18. else if (ret == RET_SHRUNKEN)
  19. printf("%u) row %d incomplete shrunken by group\n", m_steps, (int)row + 1);
  20. return ret;
  21. }

filterColGroup 和 filterBlkGroup 接口修改类似。

filterOneGroup 接口新实现

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

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

  1. bool CQuizDealer::completeShrinkByGrp(u8* pGrp, u8* pTimes)
  2. {
  3. u8 size = pGrp[0];
  4. for (u8 idx = 1; idx <= size; ++idx) {
  5. u8 sum = m_seqCell[pGrp[idx]].candidates[0];
  6. for (u8 inn = 1; inn <= sum; ++inn) {
  7. u8 val = m_seqCell[pGrp[idx]].candidates[inn];
  8. pTimes[val] += 1;
  9. pTimes[val + 9] = pGrp[idx];
  10. }
  11. }
  12. bool ret = false;
  13. for (u8 val = 1; val <= 9; ++val) {
  14. if (pTimes[val] == 1) {
  15. ret = true;
  16. u8 celIdx = pTimes[val + 9];
  17. m_seqCell[celIdx].candidates[0] = 1;
  18. m_seqCell[celIdx].candidates[1] = val;
  19. }
  20. }
  21. return ret;
  22. }

新增 incompleteShrinkByGrp 接口实现

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

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

  1. struct Item {
  2. u8 sameSum;
  3. u8 celIdxs[9];
  4. Item() {sameSum = 0;}
  5. };

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 接口实现小修改

  1. u8 CQuizDealer::filterRowCandidatesEx(u8 row)
  2. {
  3. ...
    for (u8 idx = 0; idx < 3; ++idx) {
  4. ret = filterRowByPolicy1(row, idx, vals, blkTakens);
  5. if (IsDone(ret))
  6. return ret;
  7. }
  8. for (u8 idx = 0; idx < 3; ++idx) {
  9. ret = filterRowByPolicy2(row, idx, vals, blkTakens);
  10. if (IsDone(ret))
  11. return ret;
  12. }
  13. return ret;
  14. }

filterColCandidatesEx 接口的修改类似。

shrinkRowCandidatesP1 接口实现小修改

  1. u8 CQuizDealer::shrinkRowCandidatesP1(u8* pBlk, u8* pRow, u8 zeroSum, u8 row, u8 colBase)
  2. {
  3. ...
  4. u8 shrunken = 0;
  5. for (u8 col = colBase; col < colBase + 3; ++col) {
  6. ...
    if (ret != RET_PENDING) {
  7. printf(" worked by row-ply1.\n");
  8. if (ret == RET_OK)
  9. shrunken = 1; // complete
  10. else if (ret == RET_SHRUNKEN && shrunken == 0)
  11. shrunken = 2; // incomplete
  12. }
  13. }
  14. if (shrunken == 1) {
  15. ret = RET_OK;
  16. printf("%u) row %d shrunken ply-1 by blk %d\n", m_steps, (u8)row + 1, (u8)colBase / 3 + 1);
  17. }
  18. else if (shrunken == 2)
  19. ret = RET_SHRUNKEN;
  20. return ret;
  21. }

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

其他小修改

  1. // 1.0 2021/9/20
  2. // 2.0 2021/10/2
  3. // 2.1 2021/10/4
  4. #define STR_VER "Sudoku Solver 2.2 2021/10/10 by readalps\n\n"
  5.  
  6. void showOrderList()
  7. {
  8. ...
  9. printf("onlygrp: only using group shrink\n");
  10. printf("step: step forward\n");
  11. ...
  12. }
  13.  
  14. void dealOrder(std::string& strOrder)
  15. {
  16. ...
    else if ("onlygrp" == strOrder)
  17. CQuizDealer::instance()->setOnlyGrpMode();
  18. else if ("step" == strOrder)
    ...

实例分析

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

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

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

  1. ...
    506) Forward guess [1,4] level 11 at 2 out of 2
  2. 820 703 601
  3. 003 600 807
  4. 070 090 203
  5.  
  6. 050 007 104
  7. 000 045 706
  8. 007 100 935
  9.  
  10. 001 009 568
  11. 008 500 319
  12. 090 000 472
  13.  
  14. Steps:506
  15. Candidates:
  16. [1,3]: 4 5 9 [1,5]: 5 [1,8]: 4 5 9
  17. [2,1]: 1 4 5 9 [2,2]: 1 4 [2,5]: 1 2 5
  18. [2,6]: 1 2 4 [2,8]: 4 5 9 [3,1]: 1 4 5 6
  19. [3,3]: 4 5 6 [3,4]: 4 8 [3,6]: 1 4 8
  20. [3,8]: 4 5 [4,1]: 2 3 6 9 [4,3]: 2 6 9
  21. [4,4]: 2 3 8 9 [4,5]: 2 3 6 8 [4,8]: 2 8
  22. [5,1]: 1 2 3 9 [5,2]: 1 3 8 [5,3]: 2 9
  23. [5,4]: 2 3 8 9 [5,8]: 2 8 [6,1]: 2 4 6
  24. [6,2]: 4 6 8 [6,5]: 2 6 8 [6,6]: 2 6 8
  25. [7,1]: 2 3 4 7 [7,2]: 3 4 [7,4]: 2 3 4
  26. [7,5]: 2 3 7 [8,1]: 2 4 6 7 [8,2]: 4 6
  27. [8,5]: 2 6 7 [8,6]: 2 4 6 [9,1]: 3 5 6
  28. [9,3]: 5 6 [9,4]: 3 8 [9,5]: 1 3 6 8
  29. [9,6]: 1 6 8
  30. The foremost cell with 1 candidate(s) at [1,5]
  31.  
  32. At guess level 11 [1,4] 2
  33. Run time: 326 milliseconds; steps: 506, solution sum: 0.
  34.  
  35. Order please:

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

  1. Order please:
  2. step
  3. 820 753 601
  4. 003 600 807
  5. 070 090 203
  6.  
  7. 050 007 104
  8. 000 045 706
  9. 007 100 935
  10.  
  11. 001 009 568
  12. 008 500 319
  13. 090 000 472
  14.  
  15. Steps:507
  16. Candidates:
  17. [1,3]: 4 9 [1,8]: 4 9 [2,1]: 1 4 5 9
  18. [2,2]: 1 4 [2,5]: 1 2 [2,6]: 1 2 4
  19. [2,8]: 4 5 9 [3,1]: 1 4 5 6 [3,3]: 4 5 6
  20. [3,4]: 4 8 [3,6]: 1 4 8 [3,8]: 4 5
  21. [4,1]: 2 3 6 9 [4,3]: 2 6 9 [4,4]: 2 3 8 9
  22. [4,5]: 2 3 6 8 [4,8]: 2 8 [5,1]: 1 2 3 9
  23. [5,2]: 1 3 8 [5,3]: 2 9 [5,4]: 2 3 8 9
  24. [5,8]: 2 8 [6,1]: 2 4 6 [6,2]: 4 6 8
  25. [6,5]: 2 6 8 [6,6]: 2 6 8 [7,1]: 2 3 4 7
  26. [7,2]: 3 4 [7,4]: 2 3 4 [7,5]: 2 3 7
  27. [8,1]: 2 4 6 7 [8,2]: 4 6 [8,5]: 2 6 7
  28. [8,6]: 2 4 6 [9,1]: 3 5 6 [9,3]: 5 6
  29. [9,4]: 3 8 [9,5]: 1 3 6 8 [9,6]: 1 6 8
  30. The foremost cell with 2 candidate(s) at [1,3]
  31.  
  32. At guess level 11 [1,4] 2
  33.  
  34. Order please:

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

  1. [2,1]: 1 4 5 9
  2. [2,2]: 1 4 [2,5]: 1 2 [2,6]: 1 2 4
  3. [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. Java实现小程序微信支付

    小程序支付流程交互图: 进入小程序,下单,请求下单支付,调用小程序登录API来获取Openid,生成商户订单 // pages/pay/pay.js var app = getApp(); Page( ...

  2. golang error错误处理

    error定义 数据结构 go语言error是一普通的值,实现方式为简单一个接口. // The error built-in interface type is the conventional i ...

  3. vue3 项目 用 vue-video-player 做直播 ( 亲测可用 )

    npm 安装 npm install --save vue-video-player npm install --save videojs-flash 1 <template> 2 < ...

  4. 20210712考试-2021noip11

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

  5. Python - 面向对象编程 - 小实战(3)

    需求 房子(House)有户型.总面积.家具名称列表:新房子没有任何的家具 家具(HouseItem)有名字.占地面积 席梦思(bed) 占地 4 平米 衣柜(bed) 占地 2 平米 餐桌(bed) ...

  6. Lambda表达式——注重过程的编程思想

    一.使用匿名内部类的匿名对象创建线程和Lambda表达式写法 Lambda表达式写法不用去定义一个Runable接口的实现类: 二.方法入参是一个接口或者接口的实现类 三.对某个类的一些对象实例进行排 ...

  7. Vue中使用 iview 之-踩坑日记

    导航列表: 一.iview单选框Select验证问题 二.iview表单v-if引起的问题 三.Upload 手动上传组件 使用是出现的问题 四.Tabs嵌套使用时的问题 五.Tooltip 换行问题 ...

  8. TypeScript 中命名空间与模块的理解?区别?

    一.模块 TypeScript 与ECMAScript 2015 一样,任何包含顶级 import 或者 export 的文件都被当成一个模块 相反地,如果一个文件不带有顶级的import或者expo ...

  9. 【第十一篇】- Git Gitee之Spring Cloud直播商城 b2b2c电子商务技术总结

    Git Gitee 大家都知道国内访问 Github 速度比较慢,很影响我们的使用. 如果你希望体验到 Git 飞一般的速度,可以使用国内的 Git 托管服务--Gitee(gitee.com). G ...

  10. 查看elasticsearch版本的方法

    查看elasticsearch版本的方法: 1.elasticsearch已经启动的情况下 使用curl -XGET localhost:9200命令查看: "version" : ...