到 Codex 選 Setting -> Plugins -> Add MCP servers
各欄輸入如下
Mame (可自取): Comfy MCP
Type: STDIO
Command to launch (填入電腦裡安裝的 uvx 路徑): C:\Users\HugoKuo\AppData\Local\Programs\Python\Python312\Scripts\uvx Arguments (設定如下,配合 Command to launch 的設定等同於 Codex 會執行 uvx --with comfy-cli>=1.14.0 comfy-mcp): --with comfy-cli>=1.14.0 comfy-mcp Environment variables (填入你 local 端 run 起來的 ComfyUI 端點) : COMFY_LOCAL_URL = http://127.0.0.1:8188
生活記事簿
這裏記錄了我生活中碰到的問題及解決方法,也分享一些我知道的有用資訊及奇聞軼事,一方面怕自己忘記,也希望能幫助到需要的人
2026年10月22日 星期四
在 ChatGPT Codex 中設定 Comfy MCP
Ollama AI 筆記 - 整合 Ollama AI, Open Web UI, MCP, MCPO, Stable Difussion, ComfyUI, Docker Compose
------------------------- WSL 設定安裝 Nvidia Container Toolkit -----------
# Windows 可在 WSL 上安裝 Nvidia Container Toolkit 讓 Docker container
可以使用 GPU 加速,參考 ollama/ollama - Docker Image 的 Nvidia GPU 部份
#設定 Repository
curl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey \
| sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg
curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list \
| sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' \
| sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list
sudo apt-get update
#安裝 Nvidia Container Toolkit
sudo apt-get install -y nvidia-container-toolkit
#安裝完 Nvidia Container Toolkit 後可能會需要重新啟動 Docker
service docker restart //或 systemctl restart docker
-----------------------------------------------------------------------------------
#可用 Docker 安裝 Ollama,例如 Docker compose 如下範例,啟動方:
docker-compose.yml
:
volumes:
ollama-volume:
open-webui-volume:
services:
#有設定 profile,啟動方式例如 docker compose --profile <profile name> up -d,
#例 docker compose --profile service up -d
ollama:
image: curl -fsSL https://ollama.com/install.sh | sh:0.32.15
container_name: ollama/ollama:0.32.15
restart: unless-stopped
profiles: [service]
volumes:
- ollama-volume:/root/.ollama
ports:
- "11434:11434"
environment:
#可依需求自行調整 OLLAMA_CONTEXT_LENGTH
#太小可能會造成 context 被截斷 AI 無成正確回應,例如AI應該要找到並使用 MCP Tool 但卻回找不到 Tool 可用
- OLLAMA_CONTEXT_LENGTH=8192
#讓 container 使用 GPU,等同 docker run 的 --gpus=all 參數
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
logging:
driver: "json-file"
options:
max-size: "1k"
max-file: "3"
#除了直接用 CLI command, HTTP, Node JS, Python... 等使用 Ollama
#也可撘配 Open Web UI 用瀏覽器使用 Ollama
open-webui:
image: ghcr.io/open-webui/open-webui:main
container_name: open-webui
restart: unless-stopped
profiles: [service]
ports:
- "3001:8080"
volumes:
- open-webui-volume:/app/backend/data
environment:
- OLLAMA_BASE_URL=http://ollama:11434
# 如果 Ollama 是直接另外 run 在 host 宿主機上的話
#(如果沒有寫在 docker compose 裡所以得不到 ollama domain 的話),
# 也可設定 extra_hosts 然後設定 OLLAMA_BASE_URL 如下
#- OLLAMA_BASE_URL=http://host.docker.internal:11434
#extra_hosts:
# - "host.docker.internal:host-gateway"
depends_on:
- ollama
# 使用 mcpo 將本地使用 stdio 的 mcp server 包成 Open Web UI 支援的 Open API endpoint
mcp-server:
image: ghcr.io/open-webui/mcpo:main
container_name: mcp-server
restart: unless-stopped
ports:
- "8000:8000"
profiles: [service]
volumes:
- ./mcp-server:/mcp-server
command:
- --config
- /mcp-server/config.json
# ComfyUI 是底層使用 AI 繪圖 Stable Difussion 的一種介面實現
comfyui:
image: ghcr.io/lecode-official/comfyui-docker:0.6.3-comfyui-0.8.2-comfyui-manager-4.0.5-pytorch-2.9.1-cuda-12.6-cudnn-9
container_name: comfyui
restart: unless-stopped
profiles: [service]
ports:
- "8188:8188"
volumes:
- ./comfyui/models:/opt/comfyui/models
- ./comfyui/output:/opt/comfyui/output
- ./comfyui/custom_nodes:/opt/comfyui/custom_nodes
- ./comfyui/workflows:/opt/comfyui/user/default/workflows
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
#備分 docker volume 使可使用
#可執行 sudo docker compose --profile backup_volume run --rm backup_volume
backup_volume:
image: alpine:3.23.3
container_name: backup_volume
profiles: [backup_volume]
volumes:
- ollama-volume:/usr/local/ollama-volume
- open-webui-volume:/usr/local/open-webui-volume
- ./backup_volume:/usr/local/backup_volume
command:
- sh
- -c
- |
tar -cvf /usr/local/backup_volume/ollama-volume.tar -C /usr/local ollama-volume
tar -cvf /usr/local/backup_volume/open-webui-volume.tar -C /usr/local open-webui-volume
#還原備分 docker volume 使可使用
#可執行 sudo docker compose --profile restore_volume run --rm restore_volume
restore_volume:
image: alpine:3.23.3
container_name: restore_volume
profiles: [restore_volume]
volumes:
- ollama-volume:/usr/local/ollama-volume
- open-webui-volume:/usr/local/open-webui-volume
- ./backup_volume:/usr/local/backup_volume
command:
- sh
- -c
- |
tar -xvf /usr/local/backup_volume/ollama-volume.tar -C /usr/local
tar -xvf /usr/local/backup_volume/open-webui-volume.tar -C /usr/local
/mcp-server/config.json (mcpo 的 mcp config,可以自行放入多個想要設定的 MCP server),這裡的例子放了 fetch (可抓取網頁內容) 和 chrome-devtools (實際上可能 docker container 裡面還要裝 chrome 及一些設定才能使用):
{
"mcpServers": {
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"]
},
"chrome-devtools": {
"command": "npx",
"args": ["-y", "chrome-devtools-mcp@latest"]
}
}
}
-------------------- 常用指令 ------------------
#進入 Ollama
互動介面 menu,畫面如下所示
ollama
#如果 Ollama 沒有被啟動,可用指令手動啟動
ollama serve
#下載安裝模型 model
#官方模型列表
ollama pull <model name>
#例如:
ollama
pull gemma4:e4b
#模型下載列表
https://ollama.com/search
#運行模型 (模型沒下載會自行下載),進行 chat
ollama run <model
name>
#例如:
ollama run gemma4
#列出模型
ollama
list
#刪除模型
ollama rm <model name>
# 也可使用 http 呼叫,例如:
curl http://localhost:11434/api/generate -d '{
"model": "gemma4",
"prompt": "Why is the sky blue?",
"stream": false
}'
# 也可使用 Node JS, Python Library 呼叫
# 以 Node JS 為例,先 npm install
ollama 安裝 package 後,執行如下
一般的非 Streaming 回答:
import ollama from 'ollama'
//一般的非 Streaming 回答
const response = await ollama.chat({
model: 'gemma4:e4b',
messages: [{ role: 'user', content: 'Why is the sky blue?' }],
})
console.log(response.message.content);
Streaming 回答:
import ollama from 'ollama'
//Streaming 回答
const message = { role: 'user', content: 'Why is the sky blue?' }
const response = await ollama.chat({
model: 'gemma4:e4b',
messages: [message],
stream: true,
})
for await (const part of response) {
process.stdout.write(part.message.content)
}
---------------------------- 與 Open Web UI 的搭配使用 ------------------
參考上方的 docker-compose.yml ,
使用 docker compose up -d 指令把 Ollama
和 Open Web UI 啟動起來後,
就可以用瀏覽器用下面網址進到 Open Web UI
介面去使用 Ollama
http://localhost:3001
---------------------------- Open Web UI 與 MCPO 搭配使用 ------------------
如果我們本地端的 MCP Server 是走 Stdio 的話,
可以使用 MCPO 去把本地 MCP Server 包裝成 Open API 型式,
使用方式可參考上述的 docker-compose.yml 和 /mcp-server/config.json,
啟動好 MCPO Server 後,我們就可以用
http://localhost:8000/docs
觀看 MCP 的 Open API document 文件。
-
Settings -> Models -> 選擇 Model -> Advance Params ->
Function Calling 選 Native (雖然它寫 Default 是 Native,但實測發現要選
Native MCP tool 才會被使用,可能是 bug ?)。
-
Settings 的 Tools 區塊 -> Integrations -> 在 External Tool Servers
按 + 號 (Add connection) 加入新的 MCP tool,Name 可自己取,url 填 MCP 的
endpoint,例如 http://mcp-server:8000/fetch
-
Settings -> Models -> 選擇 Model -> Tools 按 Select Tool
勾選要用的 MCP tool
- 跟 AI聊天的地方就可以看到可用的 MCP Tool 顯示在 Avaliable Tools 了
- 以下是跟 AI 聊天時使用了 fetch 這個 MCP tool 的畫面
ComfyUI 是底層使用 Stable Difussion AI 繪圖的 UI 介面,
使用串接各種 Node 的 input/ouput 的 workflow 方式來使用 Stable Difussion 進行 AI 繪圖,
docker 的配置可參考上述的 docker-compose.yml,
啟動 docker container 後,
可用
http://localhost:8188
進入 ComfyUI 的介面如下,範例為最基礎的文生圖 flow:
Civitai (主要以AI繪圖為主) : https://civitai.com/
例如顯示各 node 的 id
ComfyUI Manager 的 Git: ComfyUI Manger Git
#可在設定中開啟各 node 的 id,之後方便跟 Open Web UI 整合,如下所示:
#將 ComfyUI 整合到 Open Web UI 的方式,
可參考 Open WebUI連接Stable Diffusion生成圖片,讓AI指揮AI繪圖 · Ivon的部落格
可以直接從 Flow Graph 中看到各 Node 的 ID。
產生一張如下描述的圖片,請先將描述轉換成適合 Stable Difussion 的 英文 Prompt, 再用 Prompt 來產生圖片: 一隻狗在河裡遊泳
2026年8月1日 星期六
Rust 程式語言使用筆記
Rust
說明文件:
https://rust-lang.tw/book-tw/ch01-01-installation.html
Windows 可到這安裝:
https://rust-lang.org/tools/install/
安裝後,可再自行設定客制的環境變數:
RUSTUP_HOME :預設是 C:\Users\{userName}\.rustup,存放 Rustup 的 metadata 和 toolchains
CARGO_HOME :預設是 C:\Users\{userName}\.cargo,是 Cargo home,Cargo, rustc, rustup 及一些其他 command 會放在這 %CARGO_HOME%\bin
IDE 可以用 Visual Studio Code + rust-analyzer plugin
-----------------------------------------------------------------------------
Rust 生態:
rustup : 是一個用來負責安裝及管理 Rust 的工具
Cargo :是一個 Rust
的建構系統與套件管理工具
Crates.io : 是個讓 Rust
生態系統中的每個人都能發佈它們的開源 Rust 專案並讓其他人使用的地方 (類似 Java
的 Maven、Node.js 的 npm)
---------------- rustup 相關 --------------------------------------------------------------
#更新 Rust 版本rustup update
#移除 Rust 及 rustup
rustup self uninstall
---------------- Rust 相關 --------------------------------------------------------------
#查看 Rust 版本
rustc --version
#編譯並建立執行檔 (如果在 Windows 中執行檔會是 .exe 檔)
rustc
<filename>.rs
---------------- Cargo 相關 --------------------------------------------------------------
#查看 Cargo 版本
cargo --version
#建立 Cargo 專案
cargo new <專案名稱>
#編譯建立執行檔
#預設是 debug build,會將建立的執行檔 () 放至 <project
folder>/target/debug/ 中
cargo build
#如果是要產生正式發佈用的執行檔,可以指定
--release,Cargo 會在編譯時最佳化編譯結果,產生的執行檔會放在 <project
folder>/target/release 而不是 debug 資料夾
cargo build --release
#編譯並執行專案執行檔 (也可自己用 command line
手動執行,只是要自己指定執行檔位置),如果程式碼沒有改變,cargo run
不會重新執行 build
cargo run
#檢查程式碼,可用來確保程式碼會不會通過編譯,但不會產生執行檔
cargo check
#加入要依賴的 Crates
cargo add <crates名稱>[@<版本號>]
#範例:
cargo
add rand
cargo add rand@0.8.5
#建構所有本地端依賴函式庫的技術文件,並在你的瀏覽器中開啟
cargo doc
--open
------------ 語法特色 -------------------
# 變數的 shadowing
# 變數可以遮蔽上一個同名變數,直到離開作用域,例:
fn main() {
let number = "123";
// u32 型別的 number 遮蔽(shadow) 了 &str 的 number
let number: u32 = number.parse()
.expect("不是個數字");
println!("這是個 {number}"); // 這是個 123
}
# tuple
# tuple 可存放多個不同型別的變數,例:
//tuple 例子
//也可以不特別寫出型別宣告 : (u32, &str, f64)
let tuple: (u32, &str, f64) = (3, "abc", 1.2);
println!("{}", tuple.1); // abc
let (_x, _y, z) = tuple;
println!("{z}") // 1.2
# statement (陳述式) 和 expression (表達式),參考
#statement: 不會回傳值。
#expression :會回傳值,不用分號結尾。
#例 :
let x = 1 + 3; 是個 statement,其中 1 + 3 是個 expression
let x = {
let y = 1 + 3;
y
}
其中 y 是 expression,會回傳 y 的值給 x
# function 的定義、回傳值方式
# 用 fn 定義 function,用 -> 定前回傳型別
# 可以用 return xxx; 的 statement 提前回傳值,
# 或是在最後一行用 expression 表示回傳
fn main() {
println!("{}", add(1, 5)); // 6
println!("{}", add(0, 0)); // 0
}
fn add(x:u32, y:u32) -> u32 {
if x == 0 && y == 0 {
//此為用 statement 回傳值
return 0;
}
//此為用 expression 回傳值
x + y
}
#enumeration
#可用 match 處理 enumeration 的各變體 (variants)
use std::cmp::Ordering;
fn main() {
let x = 1;
let y = 2;
//使用 match 處理 cmp() 回傳的 enumeration
match x.cmp(&y) {
Ordering::Less => println!("less"),
Ordering::Equal => print!("equal"),
Ordering::Greater => print!("greater")
};
//處理 enumeration 並回傳值的例子
let number = "123abc";
let number: u32 = match number
.parse() {
Ok(num) => num,
Err(_) => {
println!("無法轉成數字");
0
}
};
println!("{number}");
}
TiddlyWiki 筆記
------------------- Macro 相關 ------------------
#Macro 呼叫
#簡寫,但 param 只能用純文字,無法使用 text reference 或 transclude
<<my-procedure param="This is the parameter value">>
#完整寫法 參考
<$transclude $variable="macroName" parameterName="parameterValue"/>
#範例,以 list-tagged-draggable Macro 為例:
<$transclude $variable="list-tagged-draggable" tag={{!!title}}/>
------------------- Filter 相關 ------------------
參考
#使用方式
#多個 step 可用 +, - 連接
#step 的使用方式
# 純文字可用 [parameter]
[operator:suffix[parameter]]
#範例:
[tag[xxxTag]]
# text reference 可用 {text reference}
[operator:suffix{text reference}]
#範例:
[tag{!!title}]
# variable 可用 <variable>
[operator:suffix<variable>
#範例:
[tag<currentTiddler>]
------------------- 實作範例筆記 ------------------
# 在 Tiddler 中建立名為 task-end-date 的 field,用 datetime format: YYYYMMDDhhmmss 儲存,
然後再用 $list widget 將這些 Tiddler 以 task-end-date field 由新到舊排序。
#雖然 sort filter 是把 task-end-date 當成文字來排,但 datetime format 被當成文字排也符合照日期排序
<$list filter="[tag{!!title}sort[task-end-date]reverse[]]">
<div style="margin-bottom:6px;">
<span style="color:#888;">
<$view field="task-end-date" format="date" template="YYYY-0MM-0DD"/>
</span>
<$link to=<<currentTiddler>>/>
</div>
</$list>
2026年6月20日 星期六
數獨求解 Java 實現三部曲 - 第三部 - 數獨求解器的 Java 實現
數獨求解 Java 實現三部曲:
- 數獨求解 Java 實現三部曲 - 第一部 - 精確覆蓋問題 (Exact cover) 與 X演算法 (AlgorithmX)
- 數獨求解 Java 實現三部曲 - 第二部 - 舞蹈鏈 (Dancing Links) (DLX) - 可用來解數獨、8皇后問題(8 Queens, Puzzle) 等問題
-
數獨求解 Java 實現三部曲 - 第三部 - 數獨求解器的 Java 實現
經過上次兩篇文章後,我們知道了如何使用 AlgorithmX 來解 Exact cover 問題,並也知道了如何使用 DLX 的方式用程式實作解法後,今天我們要開始把學到的方法用來解數獨問題。
首先我們要把數獨問題轉成 Exact cover 問題,
只要得到了對應數獨的 Exact
cover 矩陣後就可以用解 Exact cover 的方式來解數獨。
數獨的規則有四個約束 (constraint) 如下:
- 相同的格子 (cell) 只能有一個數字,例如一個 cell 如果是 1 就不能是 2~9 其他數字。
- 相同的 row 中相同的數字只能有一個,例如 row 1 上只能有一個 1、只能有一個 2 等。
- 相同的 column 中相同的數字只能有一個,例如 column 1 上只能有一個 1、只能有一個 2 等。
- 相同的宮 (box, 九宮格) 中相同的數字只能有一個,例如 box 1 上只能有一個 1、只能有一個 2 等。
為了簡化及方便討論,我們這裡先用 4x4 的數獨來討論,之後 9x9 的數獨原理也是一樣的。
4x4 的數獨如下所示,從左到右從上到下我們把各 cell 標上所在的 (row, column):
| (0, 0) | (0, 1) | (0, 2) | (0, 3) |
| (1, 0) | (1, 1) | (1, 2) | (1, 3) |
| (2, 0) | (2, 1) | (2, 2) | (2, 3) |
| (3, 0) | (3, 1) | (3, 2) | (3, 3) |
我們把所有 cell 的可能 (可能是 1, 2, 3, 或 4) 和對應的約束關係寫成 exact cover 的矩陣如下,如果符合約束條件的話用 1 表示,不符合就不寫
| (0, 0) 有一個數字 | (0, 1) 有一個數字 | (0, 2) 有一個數字 | (0, 3) 有一個數字 | (1, 0) 有一個數字 | (1, 1) 有一個數字 | (1, 2) 有一個數字 | (1, 3) 有一個數字 | (2, 0) 有一個數字 | (2, 1) 有一個數字 | (2, 2) 有一個數字 | (2, 3) 有一個數字 | (3, 0) 有一個數字 | (3, 1) 有一個數字 | (3, 2) 有一個數字 | (3, 3) 有一個數字 | row 0 有數字 1 | row 0 有數字 2 | row 0 有數字 3 | row 0 有數字 4 | row 1 有數字 1 | row 1 有數字 2 | row 1 有數字 3 | row 1 有數字 4 | row 2 有數字 1 | row 2 有數字 2 | row 2 有數字 3 | row 2 有數字 4 | row 3 有數字 1 | row 3 有數字 2 | row 3 有數字 3 | row 3 有數字 4 | column 0 有數字 1 | column 0 有數字 2 | column 0 有數字 3 | column 0 有數字 4 | column 1 有數字 1 | column 1 有數字 2 | column 1 有數字 3 | column 1 有數字 4 | column 2 有數字 1 | column 2 有數字 2 | column 2 有數字 3 | column 2 有數字 4 | column 3 有數字 1 | column 3 有數字 2 | column 3 有數字 3 | column 3 有數字 4 | box 0 有數字 1 | box 0 有數字 2 | box 0 有數字 3 | box 0 有數字 4 | box 1 有數字 1 | box 1 有數字 2 | box 1 有數字 3 | box 1 有數字 4 | box 2 有數字 1 | box 2 有數字 2 | box 2 有數字 3 | box 2 有數字 4 | box 3 有數字 1 | box 3 有數字 2 | box 3 有數字 3 | box 3 有數字 4 | |
| (0, 0) 填 1 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (0, 0) 填 2 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (0, 0) 填 3 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (0, 0) 填 4 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (0, 1) 填 1 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (0, 1) 填 2 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (0, 1) 填 3 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (0, 1) 填 4 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (0, 2) 填 1 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (0, 2) 填 2 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (0, 2) 填 3 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (0, 2) 填 4 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (0, 3) 填 1 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (0, 3) 填 2 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (0, 3) 填 3 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (0, 3) 填 4 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (1, 0) 填 1 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (1, 0) 填 2 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (1, 0) 填 3 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (1, 0) 填 4 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (1, 1) 填 1 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (1, 1) 填 2 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (1, 1) 填 3 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (1, 1) 填 4 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (1, 2) 填 1 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (1, 2) 填 2 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (1, 2) 填 3 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (1, 2) 填 4 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (1, 3) 填 1 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (1, 3) 填 2 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (1, 3) 填 3 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (1, 3) 填 4 | 1 | 1 | 1 | 1 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (2, 0) 填 1 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (2, 0) 填 2 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (2, 0) 填 3 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (2, 0) 填 4 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (2, 1) 填 1 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (2, 1) 填 2 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (2, 1) 填 3 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (2, 1) 填 4 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (2, 2) 填 1 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (2, 2) 填 2 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (2, 2) 填 3 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (2, 2) 填 4 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (2, 3) 填 1 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (2, 3) 填 2 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (2, 3) 填 3 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (2, 3) 填 4 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (3, 0) 填 1 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (3, 0) 填 2 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (3, 0) 填 3 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (3, 0) 填 4 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (3, 1) 填 1 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (3, 1) 填 2 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (3, 1) 填 3 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (3, 1) 填 4 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (3, 2) 填 1 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (3, 2) 填 2 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (3, 2) 填 3 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (3, 2) 填 4 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (3, 3) 填 1 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (3, 3) 填 2 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (3, 3) 填 3 | 1 | 1 | 1 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| (3, 3) 填 4 | 1 | 1 | 1 |
寫出數獨對應 Exact cover 的 Algorithm X 用矩陣後,
就可以套用之前講過的
Dancing Links 求解法求出數獨的答案,
際際上一則數獨會有一些格子已經有填好的數子,而一個已有填好數字的格子不可能會再是其他數字了,
所以上述的矩陣可以先拿掉某些不可能的
row,
例如 (1, 1) 如果已經填好是 1 了,那我們就不需要 (1, 1) 填 2、(1, 1)
填 3 等的 row。
如上所述,我們可以用一樣的方法推導出一個 9X9
的數獨,各個格字是什麼數字時,轉成 Algorithm X 矩陣的 row 的公式如下:
private int[] getDancingLinksRow(int row, int col, int number) {
int[] algorithmXRow = new int[9 * 9 * 4]; //4 個約束,各約束有 9 個 column
//一個格字 (cell) 需有一個數字
int cellConstraint = row * 9 + col;
//一個 row 需有一個 1, 2, 3, 4, 5, 6, 7, 8, 9
int rowConstraint = 9 * 9 + row * 9 + (number - 1);
//一個 column 需有一個 1, 2, 3, 4, 5, 6, 7, 8, 9
int colConstraint = 2 * 9 * 9 + col * 9 + (number - 1);
//一個 box 需有一個 1, 2, 3, 4, 5, 6, 7, 8, 9
int boxConstraint = 3 * 9 * 9 + ((row / 3) * 3 + (col / 3)) * 9 + (number - 1);
algorithmXRow[cellConstraint] = 1;
algorithmXRow[rowConstraint] = 1;
algorithmXRow[colConstraint] = 1;
algorithmXRow[boxConstraint] = 1;
return algorithmXRow;
}
有了 getDancingLinksRow() 後,我們就能把一個 9x9 的數獨盤面轉成完整的 Algorithm X 矩陣了,如下:
//將 int[][] board : 數獨的盤面 轉換成 Exact cover 問題的 Algorithm X 矩陣
private int[][] buildSudokuDancingLinksMatrix(int[][] board) {
List<int[]> rows = new ArrayList<>();
for (int row = 0; row < board.length; row++) {
for (int col = 0; col < board[0].length; col++) {
if (board[row][col] != 0) {// 如果數獨的某一格已經有一個數字
int num = board[row][col];
rows.add(getDancingLinksRow(row, col, num));
} else {
// 如果數獨的某一格還沒有數字,就產生對應 1~9 各種可能的 Algorithm X 矩陣 row
for (int num = 1; num <= 9; num++) {
rows.add(getDancingLinksRow(row, col, num));
}
}
}
}
return rows.toArray(new int[0][0]);
}
有了 Algorithm X 矩陣後,就可以用之前介紹的方式找出 Exact Cover 的解,
再把被選為答案的一組
Algorithm X 中的 row 子集
轉換成對應數獨的哪一個格子要填哪一個數字就可以了。
下面分享一下我的實作專案,裡面包括了三種數獨的求解實作,
分別是:
- 最簡單的 Backtracking 深度查詢遞迴演算法,效能最差,重試錯率很高。
- 用 Java 物件的方式實作 Dancing links,效能較差,且因為物件、鏈結等處理成本,有時花的時間會比 Backtracking 的時間還久。
-
用陣列 (pure int array) 的方式實作 Dancing
links,效能較好,當數獨盤面的解很多時
(例如數獨一開始的已填好數字很少,存在很多個可能的解答盤面時),效能比用物件方式實作快很多。
以以下數獨盤面為例,有 8008 個解答:
| 9 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 0 | 0 | 0 | 8 | 0 | 0 | 6 | 0 | 0 |
| 0 | 7 | 8 | 0 | 0 | 6 | 0 | 0 | 0 |
| 0 | 2 | 0 | 0 | 0 | 0 | 0 | 9 | 0 |
| 0 | 0 | 0 | 5 | 0 | 0 | 4 | 3 | 0 |
| 0 | 0 | 6 | 0 | 0 | 3 | 2 | 0 | 0 |
| 0 | 1 | 0 | 0 | 9 | 4 | 0 | 0 | 0 |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 8 |
- Backtracking : 耗時 20578 ms。
- 用 Java 物件的方式實作 Dancing links : 耗時 41717 ms。
- 用陣列 (pure int array) 的方式實作 Dancing links :耗時 277 ms。
源碼分享: