+-
SudokuSolver 2.0:用C++实现的数独解题程序 【二】

本篇是 SudokuSolver 2.0:用C++实现的数独解题程序 【一】 的续篇。

filterRowCandidatesEx 接口

u8 CQuizDealer::filterRowCandidatesEx(u8 row)
{
u8 vals[10] = {0}; // last item denotes sum of zeros
u8 base = row * 9;
for (u8 col = 0; col < 9; ++col) {
vals[col] = m_seqCell[base + col].val;
if (vals[col] == 0) {
vals[9] += 1;
}
}
if (vals[9] == 0)
return RET_PENDING;
u8 blkBase = (row / 3) * 3;
u8 blkTakens[21] = {0};
setBlkRowTakens(blkBase, row % 3, blkTakens);
if (blkTakens[0] == 0 && blkTakens[7] == 0 && blkTakens[14] == 0)
return RET_PENDING;
u8 ret = RET_PENDING;
for (u8 idx = 0; idx < 3; ++idx) {
if (ret = filterRowByPolicy1(row, idx, vals, blkTakens))
return ret;
}
for (u8 idx = 0; idx < 3; ++idx) {
if (ret = filterRowByPolicy2(row, idx, vals, blkTakens))
return ret;
}
return RET_PENDING;
}

filterRowCandidatesEx 接口实现对由参数 row 指定的行实施第二类候选值集合收缩算法。第 3 行到第 12 行的代码段是把该行当前各个 cell 的值填到数组 vals 中,并用 vals[9] 记录 0-cell 数量,若 vals[9] 为 0,则说明该行没有 0-cell,即不具备收缩价值,直接返回 RET_PENGING,好让调用者进一步去考察其它行。

第 13 行到第 17 行的代码段是调用 setBlkRowTakens 接口填制 blkTakens 数组。blkTakens 数组实际记录了三个集合。举例说明一下:

   000 000 000
   023 010 780
   100 406 009

   400 050 001
   900 000 006
   060 000 090
   ...

第二行从左至右每 3 个一节,被划分为三节,这三节分别落在第 1 宫、第 2 宫、第 3 宫。事实上,每一行都可以依次方法划分为三节,每一节对应左、中、右各一个宫。blkTakens 数组的单元,每 7 个一组分成三组,每组记录一个宫中除去指定的行之外已填数字的集合。在上例中,填入 blkTakens 数组的三个集合分别是 {1}、{4, 6}、{9},对应到 blkTakens 数组的单元取值,则有:

blkTakens[0] = 1,blkTakens[1] = 1;
blkTakens[7] = 2,blkTakens[8] = 4,blkTakens[9] = 6;
blkTakens[14] = 1,blkTakens[15] = 9。

第 2 行有 4 个空位(0-cell),具体分布是第一节 1 个,第二节 2 个,第三节 1 个。而 blkTakens 数组的中宫集合,即 {4, 6} ,里的元素不能填到第 2 行的第二节(不然中宫里会出现两个 4 或两个 6),于是 4 和 6 只能填到第 2 行的第一节和第三节的空位上,而 [2,9] 空位的候选值里不能有 6(因为第 9 列里已经有 6),于是可以推定 [2,9] = 4,进而又有 [2,1] = 6。

仍以上面的例子为例,考察第 4 行,则填入 blkTakens 数组的三个集合分别是 {6,9}、{}、{6,9},对应到 blkTakens 数组的单元取值,则有:

blkTakens[0] = 2,blkTakens[1] = 9,blkTakens[2] = 6;
blkTakens[7] = 0;
blkTakens[14] = 2,blkTakens[15] = 6,blkTakens[16] = 9。

第 4 行有 6 个空位,具体分布是每一节各 2 个。而 blkTakens 数组的左宫集合与右宫集合相等,更一般而言,它们的交集为 {6, 9} ,易知这个交集里的元素只能填到第 4 行的第二节的空位上,而 [4,6] 空位的候选值里不能有 6(因为第 6 列里已经有 6),于是可以推定 [4,6] = 9,进而又有 [4,4] = 6。

上面例子里的第 2 行和第 4 行收缩示例实际就是 filterRowByPolicy2 和 filterRowByPolicy1 里分别实现的方法。

setBlkRowTakens 接口

void CQuizDealer::setBlkRowTakens(u8 blkBase, u8 innRow, u8* pTakens)
{
for (u8 idx = 0; idx < 3; ++idx) {
u8 blk = blkBase + idx;
u8* pVals = pTakens + idx * 7;
u8 colBase = idx * 3;
u8 row = block2row(blk, ((innRow + 1) % 3) * 3);
setRowTakensInBlk(row * 9, colBase, pVals);
row = block2row(blk, ((innRow + 2) % 3) * 3);
setRowTakensInBlk(row * 9, colBase, pVals);
}
}

其中调用的 setRowTakensInBlk 接口为:

void CQuizDealer::setRowTakensInBlk(u8 rowBase, u8 colBase, u8* pVals)
{
for (u8 col = colBase; col < colBase + 3; ++col) {
u8 val = m_seqCell[rowBase + col].val;
if (val != 0) {
u8 sum = pVals[0] + 1;
pVals[sum] = val;
pVals[0] = sum;
}
}
}

filterRowByPolicy1 接口

u8 CQuizDealer::filterRowByPolicy1(u8 row, u8 idx, u8* pVals, u8* pBlkTakens)
{
u8 colBase = idx * 3;
u8 v1 = pVals[colBase], v2 = pVals[colBase + 1], v3 = pVals[colBase + 2];
u8 zeroSum = (u8)(v1 == 0) + (u8)(v2 == 0) + (u8)(v3 == 0);
if (zeroSum == 0)
return RET_PENDING;
u8 isVals[7] = {0};
if (idx == 0)
intersection(pBlkTakens + 7, pBlkTakens + 14, isVals);
else if (idx == 1)
intersection(pBlkTakens, pBlkTakens + 14, isVals);
else
intersection(pBlkTakens, pBlkTakens + 7, isVals);
if (isVals[0] == 0)
return RET_PENDING;
return shrinkRowCandidatesP1(isVals, pVals, zeroSum, row, colBase);
}

filterRowCandidatesEx 接口里调用 filterRowByPolicy1 的代码段如下:

19     for (u8 idx = 0; idx < 3; ++idx) {
20         if (ret = filterRowByPolicy1(row, idx, vals, blkTakens))
21             return ret;
22     }

沿用上面的例子说明一下 filterRowByPolicy1 接口的实现代码:

   000 000 000
   023 010 780
   100 406 009

   400 050 001
   900 000 006
   060 000 090
   ...

考察第四行,上面代码段里至多会调用 filterRowByPolicy1 接口三次,idx 由 0 开始,逐次加一。

先看 idx = 0 的情形,四个传入参数分别为:row = 3(第四行的行下标为 3)、idx = 0、vals 中记录了第四行各 cell 的取值、blkTakens 中记录了三个集合,即左宫集合 {6, 9},中宫集合 {},右宫集合 {6, 9}。这一次调用是求中宫集合与右宫集合的交集,并试图往第四行第一节的空位上填值。

第 3 行到第 7 行的代码段,考察第四行的第一节三个 cell 中有几个空位,若没有空位,则无需填值;此时有两个空位,继续往下走。

第 8 行到第 16 行的代码段,求集合交集。因为 {} ∩ {6, 9} = {},即交集为空,没有明确要填空的值,直接返回 RET_PENDING(即 0)。回到 filterRowCandidatesEx 接口,idx 加一,再次调用 filterRowByPolicy1。

此时 idx = 1,要试图往第四行第二节填空,第二节有空位,继续考察左宫集合 {6, 9} 与右宫集合 {6, 9} 的交集,交集不为空,进一步调用 shrinkRowCandidatesP1 接口,发现可以对第四行做收缩处理,返回 RET_OK,回到 filterRowCandidatesEx 接口,继续向上级调用返回 RET_OK。

intersection 函数实现求两个集合的交集,具体实现为:

void intersection(u8* pA, u8* pB, u8* pOut)
{
u8 sumA = pA[0], sumB = pB[0];
if (sumA == 0 || sumB == 0)
return;
for (u8 pos = 1; pos <= sumA; ++pos)
for (u8 idx = 1; idx <= sumB; ++idx)
if (pB[idx] == pA[pos]) {
u8 sum = pOut[0] + 1;
pOut[sum] = pA[pos];
pOut[0] = sum;
break;
}
}

shrinkRowCandidatesP1 接口

u8 CQuizDealer::shrinkRowCandidatesP1(u8* pBlk, u8* pRow, u8 zeroSum, u8 row, u8 colBase)
{
for (u8 col = colBase; col < colBase + 3; ++col) {
if (pRow[col] != 0)
if (!removeVal(pBlk, pRow[col]))
return RET_PENDING;
}
if (zeroSum < pBlk[0]) {
printf("shrinking row %d with ply1[blk:%d] went WRONG: 0Sum [%d] < vSum [%d]\n", (int)row + 1, (int)colBase / 3 + 1, (int)zeroSum, (int)pBlk[0]);
return RET_WRONG;
}
u8 rowBase = row * 9;
u8 exVals[4] = {0};
if (zeroSum > pBlk[0]) {
calcExCols(exVals, pBlk, pRow, rowBase, colBase, true);
if (zeroSum - exVals[0] < pBlk[0]) {
printf("shrinking row %d with ply1[blk:%d] went WRONG: 0Sum [%d] - xSum [%d] < vSum [%d]\n", (int)row + 1, (int)colBase / 3 + 1, (int)zeroSum, (int)exVals[0], (int)pBlk[0]);
return RET_WRONG;
}
if (zeroSum - exVals[0] > pBlk[0])
return RET_PENDING;
}
u8 ret = RET_PENDING;
bool shrunken = false;
for (u8 col = colBase; col < colBase + 3; ++col) {
if (pRow[col] != 0 || inSet(col, exVals))
continue;
ret = shrinkCandidates(rowBase + col, pBlk);
if (ret == RET_WRONG) {
printf("shrinking [%d,%d] with row-ply1 went WRONG\n", (int)row + 1, (int)col + 1);
return RET_WRONG;
}
if (ret == RET_OK)
shrunken = true;
}
if (shrunken) {
ret = RET_OK;
printf("row %d shrunken ply-1 by blk %d\n", (u8)row + 1, (u8)colBase / 3 + 1);
}
return ret;
}

shrinkRowCandidatesP1 接口实现考虑的情形比前述例子里的情形要复杂一些。

第 3 行到第 11 行的代码段是对传入的交集集合 pBlk 做进一步的削减处理,因为交集中某个数字如果已经在要填入的对应行的对应节中出现,则需要从交集中剔除(removeVal 函数实现在上一篇里有)。做完剔除处理后,集合里的数字都是待填值,此时若空位数小于待填值的数量,则必然会导致无法求解的局面,因此返回 RET_WRONG,好让上层调用尽快做出调整。

第 12 行到第 22 行的代码段的逻辑还要复杂一些,通过调用 calcExCols 接口考察对应行的对应节中的空位的候选值情况,若某个空位的候选值集合和做完剔除处理的待填值集合无交集,则把该空位的列下标记入 exVals 数组。exVals 里记录的空位是不能填值的,因此,此时若空位数减去 exVals 里的空位数小于待填值的数量,还是会导致无法求解的局面,同样需要返回 RET_WRONG。

第 23 行到第 40 行的代码段是遍历对应行的对应节的空位,调用 shrinkCandidates 接口试图对空位的候选值做收缩处理。

上面用到的 inSet 函数实现为:

bool inSet(u8 val, u8* pVals)
{
for (u8 idx = 1; idx <= pVals[0]; ++idx)
if (val == pVals[idx])
return true;
return false;
}

calcExCols 接口

void CQuizDealer::calcExCols(u8* pEx, u8* pBlk, u8* pRow, u8 rowBase, u8 colBase, bool ply1)
{
u8 lower = (ply1 ? colBase : 0);
u8 upper = (ply1 ? colBase + 3 : 9);
for (u8 col = lower; col < upper; ++col) {
if (ply1) {
if (pRow[col] != 0)
continue;
}
else {
if (pRow[col] != 0 || (col >= colBase && col <= colBase + 2))
continue;
}
u8 vals[10] = {0};
intersection(m_seqCell[rowBase + col].candidates, pBlk, vals);
if (vals[0] == 0) {
u8 sum = pEx[0] + 1;
pEx[sum] = col;
pEx[0] = sum;
}
}
}

calcExCols 接口为 shrinkRowCandidatesP1 和 shrinkRowCandidatesP2 共用。

shrinkCandidates 接口

u8 CQuizDealer::shrinkCandidates(u8 cellPos, u8* pVals)
{
u8 vals[10] = {0};
intersection(m_seqCell[cellPos].candidates, pVals, vals);
if (vals[0] == 0)
return RET_WRONG;
if (vals[0] != m_seqCell[cellPos].candidates[0]) {
for (u8 idx = 0; idx <= pVals[0]; ++idx)
m_seqCell[cellPos].candidates[idx] = vals[idx];
if (vals[0] == 1)
return RET_OK;
}
return RET_PENDING;
}

shrinkCandidates 接口对下标为 cellPos 的 0-cell 做候选值收缩处理,逻辑不难,不做额外解释。

filterRowByPolicy2 接口

u8 CQuizDealer::filterRowByPolicy2(u8 row, u8 idx, u8* pVals, u8* pBlkTakens)
{
u8 colBase = idx * 3;
u8 v1 = pVals[colBase], v2 = pVals[colBase + 1], v3 = pVals[colBase + 2];
u8 zeroSum = pVals[9] - (u8)(v1 == 0) - (u8)(v2 == 0) - (u8)(v3 == 0);
if (zeroSum == 0)
return RET_PENDING;
u8* pBlk = pBlkTakens + (idx * 7);
if (pBlk[0] == 0)
return RET_PENDING;
return shrinkRowCandidatesP2(pBlk, pVals, zeroSum, row, colBase);
}

filterRowByPolicy2 和 filterRowByPolicy1 类似。

继续沿用上面的例子说明一下 filterRowByPolicy2 接口的实现代码:

   000 000 000
   023 010 780
   100 406 009

   400 050 001
   900 000 006
   060 000 090
   ...

考察第二行,filterRowCandidatesEx 接口里至多会调用 filterRowByPolicy2 接口三次,idx 由 0 开始,逐次加一。

先看 idx = 0 的情形,四个传入参数分别为:row = 3(第四行的行下标为 3)、idx = 0、vals 中记录了第四行各 cell 的取值、blkTakens 中记录了三个集合,即左宫集合 {1},中宫集合 {4, 6},右宫集合 {9}。这一次调用是考察左宫集合 {1},并试图往第二行的第二节和第三节的空位上填值。

第 3 行到第 7 行的代码段,考察第二行的第二节和第三节共有几个空位,若没有空位,则无需填值;此时有三个空位,继续往下走。

第 8 行到第 10 行的代码段,求 idx = 0 对应的集合,即左宫集合 {1}。不为空,继续调用 shrinkRowCandidatesP2 接口做进一步处理,后者会因为左宫集合 {1} 里的 1 已经出现在第二行第二节里而返回 RET_PENDING。

回到 filterRowCandidatesEx 接口,idx 加一,再次调用 filterRowByPolicy2。

此时 idx = 1,要试图往第二行的第一节和第三节填空,此两节共有两个空位,而 idx = 1 对应中宫集合 {4, 6},不为空,进一步调用 shrinkRowCandidatesP2 接口,后者发现可以对第二行做收缩处理,返回 RET_OK,回到 filterRowCandidatesEx 接口,继续向上级调用返回 RET_OK。

shrinkRowCandidatesP2 接口

u8 CQuizDealer::shrinkRowCandidatesP2(u8* pBlk, u8* pRow, u8 zeroSum, u8 row, u8 colBase)
{
u8 rowBase = row * 9;
for (u8 col = 0; col < 9; ++col) {
if (col >= colBase && col <= colBase + 2)
continue;
if (pRow[col] != 0)
if (!removeVal(pBlk, pRow[col]))
return RET_PENDING;
}
if (zeroSum < pBlk[0]) {
printf("shrinking row %d with ply2[blk:%d] went WRONG: 0Sum [%d] < vSum [%d]\n", (int)row + 1, (int)colBase / 3 + 1, (int)zeroSum, (int)pBlk[0]);
return RET_WRONG;
}
u8 exVals[7] = {0};
if (zeroSum > pBlk[0]) {
calcExCols(exVals, pBlk, pRow, rowBase, colBase, false);
if (zeroSum - exVals[0] < pBlk[0]) {
printf("shrinking row %d with ply2[blk:%d] went WRONG: 0Sum [%d] - xSum [%d] < vSum [%d]\n", (int)row + 1, (int)colBase / 3 + 1, (int)zeroSum, (int)exVals[0], (int)pBlk[0]);
return RET_WRONG;
}
if (zeroSum - exVals[0] > pBlk[0])
return RET_PENDING;
}
u8 ret = RET_PENDING;
bool shrunken = false;
for (u8 col = 0; col < 9; ++col) {
if (pRow[col] != 0 || (col >= colBase && col <= colBase + 2) || inSet(col, exVals))
continue;
ret = shrinkCandidates(rowBase + col, pBlk);
if (ret == RET_WRONG) {
printf("shrinking [%d,%d] with row-ply2 went WRONG\n", (int)row + 1, (int)col + 1);
return RET_WRONG;
}
if (ret == RET_OK)
shrunken = true;
}
if (shrunken) {
ret = RET_OK;
printf("row %d shrunken ply-2 by blk %d\n", (u8)row + 1, (u8)colBase / 3 + 1);
}
return ret;
}

shrinkRowCandidatesP2 接口实现和 shrinkRowCandidatesP1 类似。

filterColCandidatesEx 接口

u8 CQuizDealer::filterColCandidatesEx(u8 col)
{
u8 vals[10] = {0}; // last item denotes sum of zeros
for (u8 row = 0; row < 9; ++row) {
u8 celIdx = row * 9 + col;
vals[row] = m_seqCell[celIdx].val;
if (vals[row] == 0) {
vals[9] += 1;
}
}
if (vals[9] == 0)
return RET_PENDING;
u8 blkBase = col / 3;
u8 blkTakens[21] = {0};
setBlkColTakens(blkBase, col % 3, blkTakens);
if (blkTakens[0] == 0 && blkTakens[7] == 0 && blkTakens[14] == 0)
return RET_PENDING;
u8 ret = RET_PENDING;
for (u8 idx = 0; idx < 3; ++idx) {
if (ret = filterColByPolicy1(col, idx, vals, blkTakens))
return ret;
}
for (u8 idx = 0; idx < 3; ++idx) {
if (ret = filterColByPolicy2(col, idx, vals, blkTakens))
return ret;
}
return RET_PENDING;
}

filterColCandidatesEx 接口实现和 filterRowCandidatesEx 接口的实现实质是一样的,相当于对 9×9 方阵旋转 90 度后做一次 filterRowCandidatesEx。

setBlkColTakens 和 setColTakensInBlk 接口

void CQuizDealer::setBlkColTakens(u8 blkBase, u8 innCol, u8* pTakens)
{
for (u8 idx = 0; idx < 3; ++idx) {
u8 blk = blkBase + idx * 3;
u8* pVals = pTakens + idx * 7;
u8 rowBase = idx * 27;
u8 col = block2col(blk, (innCol + 1) % 3);
setColTakensInBlk(col, rowBase, pVals);
col = block2col(blk, (innCol + 2) % 3);
setColTakensInBlk(col, rowBase, pVals);
}
}
void CQuizDealer::setColTakensInBlk(u8 col, u8 rowBase, u8* pVals)
{
for (u8 idx = rowBase; idx < rowBase + 27; idx += 9) {
u8 val = m_seqCell[idx + col].val;
if (val != 0) {
u8 sum = pVals[0] + 1;
pVals[sum] = val;
pVals[0] = sum;
}
}
}

filterColByPolicy1 和 shrinkColCandidatesP1 接口

u8 CQuizDealer::filterColByPolicy1(u8 col, u8 idx, u8* pVals, u8* pBlkTakens)
{
u8 segRowBase = idx * 3;
u8 v1 = pVals[segRowBase], v2 = pVals[segRowBase + 1], v3 = pVals[segRowBase + 2];
u8 zeroSum = (u8)(v1 == 0) + (u8)(v2 == 0) + (u8)(v3 == 0);
if (zeroSum == 0)
return 0;
u8 isVals[7] = {0};
if (idx == 0)
intersection(pBlkTakens + 7, pBlkTakens + 14, isVals);
else if (idx == 1)
intersection(pBlkTakens, pBlkTakens + 14, isVals);
else
intersection(pBlkTakens, pBlkTakens + 7, isVals);
if (isVals[0] == 0)
return RET_PENDING;
return shrinkColCandidatesP1(isVals, pVals, zeroSum, col, segRowBase);
}
u8 CQuizDealer::shrinkColCandidatesP1(u8* pBlk, u8* pCol, u8 zeroSum, u8 col, u8 segRowBase)
{
for (u8 idx = segRowBase; idx < segRowBase + 3; ++idx) {
if (pCol[idx] != 0)
if (!removeVal(pBlk, pCol[idx]))
return RET_PENDING;
}
if (zeroSum < pBlk[0]) {
printf("shrinking col %d with ply1[vblk:%d] went WRONG: 0Sum [%d] < vSum [%d]\n", (int)col + 1, (int)segRowBase / 3 + 1, (int)zeroSum, (int)pBlk[0]);
return RET_WRONG;
}
u8 exVals[4] = {0};
if (zeroSum > pBlk[0]) {
calcExRows(exVals, pBlk, pCol, col, segRowBase, true);
if (zeroSum - exVals[0] < pBlk[0]) {
printf("shrinking col %d with ply1[vblk:%d] went WRONG: 0Sum [%d] - xSum [%d] < vSum [%d]\n", (int)col + 1, (int)segRowBase / 3 + 1, (int)zeroSum, (int)exVals[0], (int)pBlk[0]);
return RET_WRONG;
}
if (zeroSum - exVals[0] > pBlk[0])
return RET_PENDING;
}
u8 ret = RET_PENDING;
bool shrunken = false;
for (u8 row = segRowBase; row < segRowBase + 3; ++row) {
if (pCol[row] != 0 || inSet(row, exVals))
continue;
ret = shrinkCandidates(row * 9 + col, pBlk);
if (ret == RET_WRONG) {
printf("shrinking [%d,%d] with col-ply1 went WRONG\n", (int)row + 1, (int)col + 1);
return RET_WRONG;
}
if (ret == RET_OK)
shrunken = true;
}
if (shrunken) {
ret = RET_OK;
printf("col %d shrunken ply-1 by vblk %d\n", (u8)col + 1, (u8)segRowBase / 3 + 1);
}
return ret;
}

filterColByPolicy2 和 shrinkColCandidatesP2 接口

u8 CQuizDealer::filterColByPolicy2(u8 col, u8 idx, u8* pVals, u8* pBlkTakens)
{
u8 segRowBase = idx * 3;
u8 v1 = pVals[segRowBase], v2 = pVals[segRowBase + 1], v3 = pVals[segRowBase + 2];
u8 zeroSum = pVals[9] - (u8)(v1 == 0) - (u8)(v2 == 0) - (u8)(v3 == 0);
if (zeroSum == 0)
return 0;
u8* pBlk = pBlkTakens + (idx * 7);
if (pBlk[0] == 0)
return RET_PENDING;
return shrinkColCandidatesP2(pBlk, pVals, zeroSum, col, segRowBase);
}
u8 CQuizDealer::shrinkColCandidatesP2(u8* pBlk, u8* pCol, u8 zeroSum, u8 col, u8 segRowBase)
{
for (u8 row = 0; row < 9; ++row) {
if (row >= segRowBase && row <= segRowBase + 2)
continue;
if (pCol[row] != 0)
if (!removeVal(pBlk, pCol[row]))
return RET_PENDING;
}
if (zeroSum < pBlk[0]) {
printf("shrinking col %d with ply2[vblk:%d] went WRONG: 0Sum [%d] < vSum [%d]\n", (int)col + 1, (int)segRowBase / 3 + 1, (int)zeroSum, (int)pBlk[0]);
return RET_WRONG;
}
u8 exVals[7] = {0};
if (zeroSum > pBlk[0]) {
calcExRows(exVals, pBlk, pCol, col, segRowBase, false);
if (zeroSum - exVals[0] < pBlk[0]) {
printf("shrinking col %d with ply2[vblk:%d] went WRONG: 0Sum [%d] - xSum [%d] < vSum [%d]\n", (int)col + 1, (int)segRowBase / 3 + 1, (int)zeroSum, (int)exVals[0], (int)pBlk[0]);
return RET_WRONG;
}
if (zeroSum - exVals[0] > pBlk[0])
return RET_PENDING;
}
u8 ret = 0;
bool shrunken = false;
for (u8 row = 0; row < 9; ++row) {
if (pCol[row] != 0 || (row >= segRowBase && row <= segRowBase + 2) || inSet(row, exVals))
continue;
ret = shrinkCandidates(row * 9 + col, pBlk);
if (ret == RET_WRONG) {
printf("shrinking [%d,%d] with col-ply2 went wrong\n", (int)row + 1, (int)col + 1);
return RET_WRONG;
}
if (ret == RET_OK)
shrunken = true;
}
if (shrunken) {
ret = RET_OK;
printf("col %d shrunken ply-2 by vblk %d\n", (u8)col + 1, (u8)segRowBase / 3 + 1);
}
return ret;
}

calcExRows 接口

void CQuizDealer::calcExRows(u8* pEx, u8* pBlk, u8* pCol, u8 col, u8 segRowBase, bool ply1)
{
u8 lower = (ply1 ? segRowBase : 0);
u8 upper = (ply1 ? segRowBase + 3 : 9);
for (u8 row = lower; row < upper; ++row) {
if (ply1) {
if (pCol[row] != 0)
continue;
}
else {
if (pCol[row] != 0 || (row >= segRowBase && row <= segRowBase + 2))
continue;
}
u8 vals[10] = {0};
intersection(m_seqCell[row * 9 + col].candidates, pBlk, vals);
if (vals[0] == 0) {
u8 sum = pEx[0] + 1;
pEx[sum] = row;
pEx[0] = sum;
}
}
}

其他细微代码修改

printCandidates 函数

去掉了末尾的一行语句,即 printf("\n");

showQuiz 接口

void CQuizDealer::showQuiz()
{
...
printf("\nSteps:%u\nCandidates:\n", m_steps); u8 count = 1; u8 foremost = 0; for (std::set<u8>::iterator it = m_setBlank.begin(); it != m_setBlank.end(); ++it, ++count) { u8 pos = *it; u8* pVals = m_seqCell[pos].candidates; if (foremost == 0 && pVals[0] == 1) foremost = pos; printCandidates(pos, pVals); if (count % 3 == 0) printf("\n"); else printf(" "); } if (count % 3 != 1) printf("\n"); if (foremost != 0) printf("The foremost cell with one candidate at [%d,%d]\n", (int)(foremost / 9 + 1), (int)(foremost % 9 + 1)); if (m_guessLevel != 0) printf("\nAt guess level %d [%d,%d] %d\n", (int)m_guessLevel, (int)(m_guessPos / 9 + 1), (int)(m_guessPos % 9 + 1), (int)m_guessValPos); }

parse 和 guess 接口

开头增加一条语句:++m_steps;

nextGuess 接口

void CQuizDealer::nextGuess()
{
...
++m_steps; Snapshot* pTop = m_stkSnap.top(); ...

step 接口

去掉语句:nextGuess(); 之后的一条语句:m_steps++;

showOrderList 函数

// Sudoku Solver 1.0 2021/9/20
#define STR_VER "Sudoku Solver 2.0 2021/10/2 by readalps\n\n"

void showOrderList()
{
printf(STR_VER);
...
}