# [2020q3 Homework (quiz9)](https://hackmd.io/@sysprog/2020-quiz9)
contributed by < `zhu849` >
## 測驗 `2`
依循 [leetcode 37. Sudoku Solver](https://leetcode.com/problems/sudoku-solver/) 解決數獨問題
### 範例解:
```c=
typedef struct {
uint16_t row[9], col[9], box[9];
} masks_t;
const uint16_t ALL_NUMS = 0x1FF;
static inline int bi(int x, int y)
{
return (y / 3) * 3 + (x / 3);
}
static bool solve(char **board, masks_t *m)
{
for (int y = 0; y < 9; ++y) {
for (int x = 0; x < 9; ++x) {
if (board[y][x] != '.')
continue;
int b = bi(x, y);
uint16_t posmoves = m->row[x] & m->col[y] & m->box[b];
while (posmoves) {
size_t n = __builtin_ctz(posmoves);
uint16_t k = ~(1U << n);
board[y][x] = '1' + n;
m->row[x] &= k, m->col[y] &= k, m->box[b] &= k;
if (solve(board, m))
return true;
m->row[x] |= ~k, m->col[y] |= ~k, m->box[b] |= ~k;
posmoves &= posmoves - 1;//Q1
}
board[y][x] = '.';
return false;
}
}
return true;
}
void solveSudoku(char **board, int boardSize, int *boardColSize)
{
masks_t m;
for (int i = 0; i < 9; ++i)
m.row[i] = m.col[i] = m.box[i] = ALL_NUMS;
for (int y = 0; y < 9; ++y) {
for (int x = 0; x < 9; ++x) {
if (board[y][x] != '.') {
int n = board[y][x] - '1';
uint16_t k = ~(1U << n);
m.row[x] &= k, m.col[y] &= k, m.box[bi(x, y)] &= k;
}
}
}
for (int y = 0; y < 9; ++y) {
for (int x = 0; x < 9; ++x) {
if (board[y][x] != '.')
continue;
uint16_t mask = m.row[x] & m.col[y] & m.box[bi(x, y)];
if ((mask & (mask - 1)) == 0) { //Q2
size_t n = __builtin_ctz(mask);
uint16_t k = ~(1U << n);
board[y][x] = '1' + n;
m.row[x] &= k, m.col[y] &= k, m.box[bi(x, y)] &= k;
}
}
}
solve(board, &m);
}
```
### 數獨舉例
#### 舉例
![](https://i.imgur.com/nnEC93K.jpg)
#### 解答
![](https://i.imgur.com/UcSvKlO.jpg)
### 程式碼運作原理
* Line 2~4:定義一個 `masks_t` 的 type 便於紀錄整個數獨表格在三種不同角度觀察的狀態,總共有 9 個 row, col, box
* Line 6:定義一個 0x1FF 便於初始化 `masks_t` 內每格的值,剛好有 9 個 bits 為1,也因為這樣所以需要用 uint_16 才能容納
* Line 8~11:用於計算在 `masks_t` 中以 `box` 角度紀錄的 index,因為 `box` 在平面上是二維的,但是用一維陣列儲存
* Line 41~44:初始化 `m` 結構內的 `row`, `col`, `box` 陣列
* Line 45~53:將 board 上已填入的資訊更新到 `m` 中對應的 `row`, `col`, `box` 陣列
* Line 48~49:若 board 內數字是 (char)'5',則 n 會為 (int)4, k 會是 0x1111111111101111
* Line 50:利用 K 的 and operation 將 `row`, `col`, `box` 對應的 bit 做更新
* Line 55~67:初步檢查 board 上的 `row`, `col`, `box` ,利用現階段已知條件填入能夠確定的格子(某格已經能知道是某數,不用透過遞迴猜測)
* Line 57~58:若格內是 digit 則略過
* Line 59:將 `row`, `col`, `box` 做 and operation 得到一個 mask ,這個 mask 可以知道哪些 digit 有可能是滿足三者條件的答案
* Line 60:利用 x 和 x-1 做 and operation 確認該值的二進位型態是否僅有一個 1's bit(只有這個情況會使條件為 true,是 bitwise operation 的特性)
* Line 61-64:計算該 bit 後有幾個 0 存在 n,利用 n 去製作一個 k mask,用 k mask 將 `row`, `col`, `box` 中對應的 bit 消除為 0
* Line 69:遞迴猜測可能的解
* Line 21: `posmoves` 計算意義同 Line 59 的 `mask`,目的是得知哪些 digit 有可能是滿足三者條件的答案
* Line 22~31:直到 `posmoves` 中所有可能的遞迴猜測完畢後,才會跳出迴圈
* Line 23-26:和 line 48-50 同理,先嘗試填入該格數字
* Line 27:利用填入數字遞迴確認是否能夠填完整張 board,若可以則回傳 true
* Line 29:若沒辦法順利填入則需要將狀態恢復成遞迴前的狀態,將 `row`, `col`, `box` 和 ~k 做 or operation 即可復原
* Line 30:這裡利用 x 和 x-1 做 and operation 去消除最右邊的 1
* Line 32:若該格的內容皆無法順利填入,則復原為 '.'
### 結果
![](https://i.imgur.com/joYr0Yr.jpg)