関数型電卓プログラム fcalc の使用例として、前回作成した簡単なライブラリを使ってパズルの解法プログラムを作成します。
1 から 9 までの数字を順番に並べ、間に + と - を補って 100 になる式を作ってください。
例 : 1 + 2 + 3 - 4 + 5 + 6 + 78 + 9 = 100
パズルの世界では、1 から 9 までの数字を 1 個ずつすべて使った数字を「小町数」といいます。たとえば、123456789 とか 321654987 のような数字です。「小町算」というものもあり、たとえば 123 + 456 + 789 とか 321 * 654 + 987 のようなものです。問題1は小町算の中でも特に有名なパズルです。
S E N D
+ M O R E
-------------
M O N E Y
図 : 覆面算
計算式の数字を文字や記号に置き換えて、それを元の数字に戻すパズルを「覆面算」といいます。異なる文字は異なる数字を表し、同じ文字は同じ数字を表します。使用する数字は 0 から 9 までで、最上位の桁に 0 を入れることはできません。問題2はデュードニーが 1924 年に発表したもので、覆面算の古典といわれる有名なパズルです。
8クイーンは、8 行 8 列のチェスの升目に、8 個のクイーンを互いの利き筋が重ならないように配置する問題です。コンピュータに解かせるパズルの中でもとくに有名な問題です。クイーンは将棋の飛車と角をあわせた駒で、縦横斜めに任意に動くことができます。解答の一例を次に示します。
列
0 1 2 3 4 5 6 7
*-----------------*
0 | Q . . . . . . . |
1 | . . . . Q . . . |
2 | . . . . . . . Q |
行 3 | . . . . . Q . . |
4 | . . Q . . . . . |
5 | . . . . . . Q . |
6 | . Q . . . . . . |
7 | . . . Q . . . . |
*-----------------*
図 : 8 クイーンの解答例
8クイーンの解をすべて求めてください。
┌─┬─┬─┐ 式
│A│B│C│ A + B + C = N, A + E + I = N
├─┼─┼─┤ D + E + F = N, C + E + G = N
│D│E│F│ G + H + I = N
├─┼─┼─┤ A + D + G = N
│G│H│I│ B + E + H = N
└─┴─┴─┘ C + F + I = N
図 : 魔方陣
上図の A から I の場所に 1 から 9 までの数字をひとつずつ配置します。縦横斜めの合計が等しくなるように数字を配置してください。
「マスターマインド」は 0 から 9 までの重複しない 4 つの数字からなる隠しコードを当てるゲームです。数字は合っているが位置が間違っている個数を cows で表し、数字も位置も合っている個数を bulls で表します。bulls が 4 になると正解です。
(6 2 8 1) : 正解
---------------------------------
1. (0 1 2 3) : cows 2 : bulls 0
2. (1 0 4 5) : cows 1 : bulls 0
3. (2 3 5 6) : cows 2 : bulls 0
4. (3 2 7 4) : cows 0 : bulls 1
5. (3 6 0 8) : cows 2 : bulls 0
6. (6 2 8 1) : cows 0 : bulls 4
図 : マスターマインドの動作例
マスターマインドを解くプログラムを作成してください。
┌─┬─┬─┬─┬─┬─┬─┐
│●│●│●│ │○│○│○│ スタート
└─┴─┴─┴─┴─┴─┴─┘
┌─┬─┬─┬─┬─┬─┬─┐
│○│○│○│ │●│●│●│ ゴール
└─┴─┴─┴─┴─┴─┴─┘
図 : 蛙跳びゲーム
蛙跳びゲームは黒石と白石を使って遊ぶ、いわゆる「飛び石ゲーム」と呼ばれる種類のパズルです。上図のように、蛙跳びゲームは黒石と白石を入れ替えることができれば成功です。スタートからゴールまでの最短手順を求めてください。
石を動かす規則は次のとおりです。
石の跳び越しは次の図を参考にしてください。
┌───┐ ┌───┐
↓ │ │ ↓
┬─┬─┬─┬─┬ ┬─┬─┬─┬─┬
│ │●│○│ │ │ │●│○│ │
┴─┴─┴─┴─┴ ┴─┴─┴─┴─┴
白石の移動 黒石の移動
図 : 石の跳び越し
ペグ・ソリテアは、盤上に配置されたペグ (駒) を、最後にはひとつ残るように取り除いていく古典的なパズルです。ペグは次のルールに従って移動し、除去することができます。
盤は今までに多数考案されていますが、33 穴英国盤、37 穴フランス盤、41 穴盤が有名でしょう。33 穴英国盤と Hoppers を図に示します。Hoppers は芦ヶ原伸之氏が考案されたペグ・ソリテアです。
●─●─●
│ │ │
●─●─● ●───●───●
│ │ │ │\ /│\ /│
●─●─●─●─●─●─● │ ● │ ● │
│ │ │ │ │ │ │ │/ \│/ \│
●─●─●─○─●─●─● ●───○───●
│ │ │ │ │ │ │ │\ /│\ /│
●─●─●─●─●─●─● │ ● │ ● │
│ │ │ │/ \│/ \│
●─●─● ●───●───●
│ │ │
●─●─● (2) Hoppers
(1) 33 穴英国盤
図 : ペグ・ソリテア
それぞれのマスにペグがありますが、そこからひとつペグを取り除いてゲームを始めます。図では黒丸でペグを表し、白丸で空き場所を表しています。ルールに従ってペグを移動し、最後にひとつだけ残ればクリアとなります。ただし、ペグを取り除く位置によって、解けない場合もあるので注意してください。
それでは問題です。図 (2) に示したように、Hoppers の中央のペグを取り除きます。この状態から始めて、最後のペグが中央の位置に残る跳び方の最小手数を求めてください。
大きな容器に水が入っています。目盛の付いていない 8 リットルと 5 リットルの容器を使って、大きな容器から 4 リットルの水を汲み出してください。4 リットルの水は、どちらの容器に入れてもかまいません。水をはかる最短手順を求めてください。なお、水の総量に制限はありません。
「15 パズル」でお馴染みのスライドパズルです。それでは問題です。
┌─┬─┬─┐ ┌─┬─┬─┐
│4│5│ │ │1│2│3│
├─┼─┼─┤ ├─┼─┼─┤
│1│2│3│ │4│5│ │
└─┴─┴─┘ └─┴─┴─┘
スタート ゴール
図 : 5 パズル
スタートからゴールまでの最短手順を求めてください。
9 行 9 列盤の「ナンバープレース (数独) 」を解くプログラムを作ってください。
リスト : 小町算
def komachi(n)
let rec
# 式の計算
calc, iter = fn(expr, a)
if null(expr) then
a
else
if equal(car(expr), "+") then
calc(cddr(expr), a + cadr(expr))
else
calc(cddr(expr), a - cadr(expr))
end
end
end,
# 式の組み立て
fn(xs, expr)
if null(xs) then
let e = reverse(expr) in
if calc(cdr(e), car(e)) == n then
print(reverse(expr)),
print(" = "),
print(n),
putc(10)
end
end
else
iter(cdr(xs), cons(car(xs), cons("+", expr))),
iter(cdr(xs), cons(car(xs), cons("-", expr))),
iter(cdr(xs), cons(car(expr) * 10 + car(xs), cdr(expr)))
end
end
in
iter(iota(2, 9), list(1))
end
end
Calc> komachi(100); (1 + 2 + 3 - 4 + 5 + 6 + 78 + 9) = 100 (1 + 2 + 34 - 5 + 67 - 8 + 9) = 100 (1 + 23 - 4 + 5 + 6 + 78 - 9) = 100 (1 + 23 - 4 + 56 + 7 + 8 + 9) = 100 (12 + 3 + 4 + 5 - 6 - 7 + 89) = 100 (12 + 3 - 4 + 5 + 67 + 8 + 9) = 100 (12 - 3 - 4 + 5 - 6 + 7 + 89) = 100 (123 + 4 - 5 + 67 - 89) = 100 (123 + 45 - 67 + 8 - 9) = 100 (123 - 4 - 5 - 6 - 7 + 8 - 9) = 100 (123 - 45 - 67 + 89) = 100 0
解は全部で 11 通りあります。
リスト : 覆面算
def hukumen()
# 0 1 2 3 4 5 6
# s e n d o r y
# m = 1
permutation(fn(xs)
let
n1 = nth(xs,0) * 1000 + nth(xs,1) * 100 + nth(xs,2) * 10 + nth(xs,3),
n2 = 1000 + nth(xs,4) * 100 + nth(xs,5) * 10 + nth(xs, 1),
n3 = 10000 + nth(xs,4) * 1000 + nth(xs,2) * 100 + nth(xs,1) * 10 + nth(xs,6)
in
if n1 + n2 == n3 then
print(n1), print(" + "), print(n2), print(" = "), print(n3),
print("\n")
end
end
end,
7,
list(0,2,3,4,5,6,7,8,9))
end
Calc> hukumen(); 9567 + 1085 = 10652
答えは 9567 + 1085 = 10652 の 1 通りしかありません。
リスト : 8クイーン
def attack(x, xs)
let rec
iter = fn(n, ys)
if null(ys) then
1
else
if x == car(ys) + n or x == car(ys) - n then
0
else
iter(n + 1, cdr(ys))
end
end
end
in
iter(1, xs)
end
end
def queen(n)
let rec
iter = fn(nums, board)
if null(nums) then
print(board), print("\n")
else
foreach(fn(x)
if attack(x, board) then
iter(remove(fn(y) x == y end, nums), cons(x, board))
end
end,
nums
)
end
end
in
iter(iota(1, n), nil)
end
end
Calc> queen(8); (4 2 7 3 6 8 5 1) (5 2 4 7 3 8 6 1) (3 5 2 8 6 4 7 1) (3 6 4 2 8 5 7 1) ・・・省略・・・ (6 3 5 7 1 4 2 8) (6 4 7 1 3 5 2 8) (4 7 5 2 6 1 3 8) (5 7 2 6 3 1 4 8)
重複解 (鏡像解や回転解) を除かない場合、解は全部で 92 通りあります。
リスト : 魔方陣
def magic()
let
lineNum = fn(xs, ls) foldl(fn(x, a) nth(xs, x) + a end, 0, ls) end,
line = list(list(0, 1, 2), list(3, 4, 5), list(6, 7, 8),
list(0, 3, 6), list(1, 4, 7), list(2, 5, 8),
list(0, 4, 8), list(2, 4, 6))
in
permutation(fn(xs)
let
ns = map(fn(ls) lineNum(xs, ls) end, line)
in
if every(fn(x) car(ns) == x end, cdr(ns)) then
print(xs), putc(10)
end
end
end,
9,
iota(1, 9)
)
end
end
Calc> magic(); (2 7 6 9 5 1 4 3 8) (2 9 4 7 5 3 6 1 8) (4 3 8 9 5 1 2 7 6) (4 9 2 3 5 7 8 1 6) (6 1 8 7 5 3 2 9 4) (6 7 2 1 5 9 8 3 4) (8 1 6 3 5 7 4 9 2) (8 3 4 1 5 9 6 7 2)
解は 8 通り出力されましたが、重複解を取り除くと解は一通りしかありません。重複解のチェックは面倒だと思われる方もいるでしょう。ところが、下図のように四隅の大小関係を利用すると簡単です。
┌─┬─┬─┐
│A│B│C│
├─┼─┼─┤ A < C < G
│D│E│F│
├─┼─┼─┤ A < I
│G│H│I│
└─┴─┴─┘
図 : 対称解のチェック
魔方陣の場合、回転解が 4 種類あって、鏡像解が 2 種類あります。四隅の大小関係をチェックすることで、これらの対称解を排除することができます。また、順列を生成するとき、重複解のチェックを入れると枝刈りと同じ効果を得ることができます。興味のある方は試してみてください。
リスト : マスターマインドの解法
def countBulls(xs, ys)
foldl2(fn(x, y, a) if x == y then a + 1 else a end end, 0, xs, ys)
end
def countSameNum(xs, ys)
foldl(fn(x, a) if member(x, ys) then a + 1 else a end end, 0, xs)
end
# query = ((code bulls cows) ...)
def checkQuery(code, query)
every(fn(xs)
let
newBulls = countBulls(code, car(xs)),
newSames = countSameNum(code, car(xs))
in
newBulls == second(xs) and newSames - newBulls == third(xs)
end
end,
query)
end
def mastermind(code)
let
query = nil
in
callcc(fn(k)
permutation(fn(xs)
if checkQuery(xs, query) then
let
bulls = countBulls(code, xs),
sames = countSameNum(code, xs)
in
print(xs), print(" : bulls = "), print(bulls),
print(", cows = "), print(sames - bulls), putc(10),
if bulls == 4 then k(nil) end,
query = cons(list(xs, bulls, sames - bulls), query)
end
end
end,
4,
iota(0, 9)
)
end)
end
end
Calc> mastermind(list(6,7,8,9)); (0 1 2 3) : bulls = 0, cows = 0 (4 5 6 7) : bulls = 0, cows = 2 (5 4 8 9) : bulls = 2, cows = 0 (6 7 8 9) : bulls = 4, cows = 0 Calc> mastermind(list(9,8,7,6)); (0 1 2 3) : bulls = 0, cows = 0 (4 5 6 7) : bulls = 0, cows = 2 (5 4 8 9) : bulls = 0, cows = 2 (6 7 9 8) : bulls = 0, cows = 4 (8 9 7 6) : bulls = 2, cows = 2 (9 8 7 6) : bulls = 4, cows = 0 Calc> mastermind(list(9,4,3,1)); (0 1 2 3) : bulls = 0, cows = 2 (1 0 4 5) : bulls = 0, cows = 2 (2 3 5 4) : bulls = 0, cows = 2 (3 4 0 6) : bulls = 1, cows = 1 (3 5 6 1) : bulls = 1, cows = 1 (6 5 0 2) : bulls = 0, cows = 0 (7 4 3 1) : bulls = 3, cows = 0 (8 4 3 1) : bulls = 3, cows = 0 (9 4 3 1) : bulls = 4, cows = 0
肝心の質問回数ですが、5, 6 回で当たる場合が多いようです。実際に、5040 個のコードをすべて試してみたところ、平均は 5.56 回になりました。質問回数の最大値は 9 回で、そのときのコードは (9 4 3 1), (9 2 4 1), (5 2 9 3), (9 2 0 4), (9 2 1 4) でした。
リスト : 蛙飛びゲーム
B = 1;
W = 2;
S = 0;
def solve(s, goal, move)
if equal(car(move), goal) then
foreach(fn(x) print(x), print("\n") end, reverse(move)),
print("\n")
else
let newboard = copy(car(move)) in
if s > 0 and newboard[s - 1] == B then
newboard[s] = B,
newboard[s - 1] = S,
solve(s - 1, goal, cons(newboard, move)),
newboard[s] = S,
newboard[s - 1] = B
end,
if s > 1 and newboard[s - 2] == B and newboard[s - 1] == W then
newboard[s] = B,
newboard[s - 2] = S,
solve(s - 2, goal, cons(newboard, move)),
newboard[s] = S,
newboard[s - 2] = B
end,
if s < 6 and newboard[s + 1] == W then
newboard[s] = W,
newboard[s + 1] = S,
solve(s + 1, goal, cons(newboard, move)),
newboard[s] = S,
newboard[s + 1] = W
end,
if s < 5 and newboard[s + 2] == W and newboard[s + 1] == B then
newboard[s] = W,
newboard[s + 2] = S,
solve(s + 2, goal, cons(newboard, move)),
newboard[s] = S,
newboard[S + 2] = W
end
end
end
end
Calc> solve(3, [2,2,2,0,1,1,1], list([1,1,1,0,2,2,2])); [1, 1, 1, 0, 2, 2, 2] [1, 1, 0, 1, 2, 2, 2] [1, 1, 2, 1, 0, 2, 2] [1, 1, 2, 1, 2, 0, 2] [1, 1, 2, 0, 2, 1, 2] [1, 0, 2, 1, 2, 1, 2] [0, 1, 2, 1, 2, 1, 2] [2, 1, 0, 1, 2, 1, 2] [2, 1, 2, 1, 0, 1, 2] [2, 1, 2, 1, 2, 1, 0] [2, 1, 2, 1, 2, 0, 1] [2, 1, 2, 0, 2, 1, 1] [2, 0, 2, 1, 2, 1, 1] [2, 2, 0, 1, 2, 1, 1] [2, 2, 2, 1, 0, 1, 1] [2, 2, 2, 0, 1, 1, 1] [1, 1, 1, 0, 2, 2, 2] [1, 1, 1, 2, 0, 2, 2] [1, 1, 0, 2, 1, 2, 2] [1, 0, 1, 2, 1, 2, 2] [1, 2, 1, 0, 1, 2, 2] [1, 2, 1, 2, 1, 0, 2] [1, 2, 1, 2, 1, 2, 0] [1, 2, 1, 2, 0, 2, 1] [1, 2, 0, 2, 1, 2, 1] [0, 2, 1, 2, 1, 2, 1] [2, 0, 1, 2, 1, 2, 1] [2, 2, 1, 0, 1, 2, 1] [2, 2, 1, 2, 1, 0, 1] [2, 2, 1, 2, 0, 1, 1] [2, 2, 0, 2, 1, 1, 1] [2, 2, 2, 0, 1, 1, 1] 0
蛙飛びゲームは後戻りができないので、単純な「深さ優先探索」で解くことができます。
リスト : ペグ・ソリテア
# 跳び先表
jump_table = [
[[1,2],[3,6],[5,10]],
[[3,5],[4,7],[6,11]],
[[1,0],[4,6],[7,12]],
[[6,9]],
[[6,8]],
[[3,1],[6,7],[8,11]],
[[3,0],[4,2],[8,10],[9,12]],
[[4,1],[6,5],[9,11]],
[[6,4]],
[[6,3]],
[[5,0],[8,6],[11,12]],
[[6,1],[8,5],[9,7]],
[[7,2],[9,6],[11,10]]
];
# 反復深化による解法
def peg13()
let
board = makeVector(13, 1),
i = 0,
MAX_JUMP = 11, # 12 個のペグが 1 つになるまで跳ぶ
GOAL = 6,
SIZE = 13,
cnt = 0
in
let rec
ids = fn(jc, limit, move)
if jc <= limit then
if length(move) == MAX_JUMP then
if board[GOAL] then
print(reverse(move)), print("\n"), cnt = cnt + 1
end
else
let j = 0 in
while j < SIZE do
if board[j] then
foreach(
fn(x)
let
del = x[0], to = x[1]
in
if board[del] and not board[to] then
board[j] = 0, board[del] = 0, board[to] = 1,
ids(if j == cdar(move) then jc else jc + 1 end,
limit,
cons(cons(j, to), move)),
board[j] = 1, board[del] = 1, board[to] = 0
end
end
end,
jump_table[j]
)
end,
j = j + 1
end
end
end
end
end
in
# 初手を 0 -> 6 に限定
board[0] = 0, board[3] = 0,
i = 2,
while i <= MAX_JUMP and cnt == 0 do
print("move = "), print(i), print("\n"),
ids(1, i, cons(cons(0, 6), nil)),
i = i + 1
end
end
end
end
Calc> peg13(); move = 2 move = 3 move = 4 move = 5 move = 6 move = 7 ((0 . 6) (9 . 3) (2 . 0) (0 . 6) (11 . 1) (10 . 0) (0 . 2) (2 . 6) (8 . 4) (12 . 2) (2 . 6)) ((0 . 6) (9 . 3) (2 . 0) (0 . 6) (11 . 1) (10 . 6) (4 . 8) (12 . 2) (2 . 0) (0 . 10) (10 . 6)) ((0 . 6) (9 . 3) (2 . 0) (0 . 6) (11 . 1) (12 . 2) (2 . 6) (8 . 4) (10 . 0) (0 . 2) (2 . 6)) ((0 . 6) (9 . 3) (2 . 6) (8 . 4) (10 . 0) (0 . 2) (2 . 6) (7 . 5) (12 . 10) (10 . 0) (0 . 6)) ((0 . 6) (9 . 3) (2 . 6) (8 . 4) (10 . 0) (0 . 2) (2 . 6) (11 . 1) (12 . 2) (2 . 0) (0 . 6)) ((0 . 6) (9 . 3) (2 . 6) (8 . 4) (10 . 0) (0 . 6) (7 . 5) (12 . 10) (10 . 0) (0 . 2) (2 . 6)) ((0 . 6) (9 . 3) (2 . 6) (8 . 4) (12 . 2) (2 . 0) (0 . 6) (5 . 7) (10 . 12) (12 . 2) (2 . 6)) ((0 . 6) (9 . 3) (2 . 6) (8 . 4) (12 . 2) (2 . 0) (0 . 6) (11 . 1) (10 . 0) (0 . 2) (2 . 6)) ((0 . 6) (9 . 3) (2 . 6) (8 . 4) (12 . 2) (2 . 6) (5 . 7) (10 . 12) (12 . 2) (2 . 0) (0 . 6)) ((0 . 6) (9 . 3) (10 . 0) (0 . 6) (7 . 5) (2 . 0) (0 . 10) (10 . 6) (4 . 8) (12 . 10) (10 . 6)) ((0 . 6) (9 . 3) (10 . 0) (0 . 6) (7 . 5) (2 . 6) (8 . 4) (12 . 10) (10 . 0) (0 . 2) (2 . 6)) ((0 . 6) (9 . 3) (10 . 0) (0 . 6) (7 . 5) (12 . 10) (10 . 6) (4 . 8) (2 . 0) (0 . 10) (10 . 6)) ((0 . 6) (9 . 3) (10 . 6) (4 . 8) (2 . 0) (0 . 6) (11 . 1) (12 . 2) (2 . 0) (0 . 10) (10 . 6)) ((0 . 6) (9 . 3) (10 . 6) (4 . 8) (2 . 0) (0 . 10) (10 . 6) (7 . 5) (12 . 10) (10 . 0) (0 . 6)) ((0 . 6) (9 . 3) (10 . 6) (4 . 8) (2 . 0) (0 . 10) (10 . 6) (11 . 1) (12 . 2) (2 . 0) (0 . 6)) ((0 . 6) (9 . 3) (10 . 6) (4 . 8) (12 . 10) (10 . 0) (0 . 6) (1 . 11) (2 . 12) (12 . 10) (10 . 6)) ((0 . 6) (9 . 3) (10 . 6) (4 . 8) (12 . 10) (10 . 0) (0 . 6) (7 . 5) (2 . 0) (0 . 10) (10 . 6)) ((0 . 6) (9 . 3) (10 . 6) (4 . 8) (12 . 10) (10 . 6) (1 . 11) (2 . 12) (12 . 10) (10 . 0) (0 . 6)) 0
7 手で解くことができました。解は全部で 18 通りになりました。最近のパソコンは高性能なので、穴の数が少ない盤面であれば、単純な反復深化でも高速に解くことができます。
リスト : 水差し問題
maxA = 8;
maxB = 5;
def getA(s) car(s) end
def getB(s) cdr(s) end
action = list(fn(s) cons(maxA, getB(s)) end, # A を満杯
fn(s) cons(0, getB(s)) end, # A を空
fn(s) let w = maxB - getB(s) in # A -> B
if getA(s) <= w then
cons(0, getB(s) + getA(s))
else
cons(getA(s) - w, getB(s) + w)
end
end
end,
fn(s) cons(getA(s), maxB) end, # B を満杯
fn(s) cons(getA(s), 0) end, # B を空
fn(s) let w = maxA - getA(s) in # B -> A
if getB(s) <= w then
cons(getA(s) + getB(s), 0)
else
cons(getA(s) + w, getB(s) - w)
end
end
end);
def water(goal)
callcc(fn(k)
let q = makeQueue() in
enqueue(q, list(cons(0, 0))),
while not isEmptyQueue(q) do
let move = dequeue(q) in
if getA(car(move)) == goal or getB(car(move)) == goal then
print(reverse(move)), putc(10),
k(nil)
else
foreach(fn(act) let ns = act(car(move)) in
if not find(fn(s) equal(s, ns) end, move) then
enqueue(q, cons(ns, move))
end
end
end,
action)
end
end
end
end
end)
end
Calc> water(4); ((0 . 0) (0 . 5) (5 . 0) (5 . 5) (8 . 2) (0 . 2) (2 . 0) (2 . 5) (7 . 0) (7 . 5) (8 . 4))
最短手数は 10 手になります。今回は「幅優先探索」で解きましたが、「反復深化」でも簡単に解けると思います。興味のある方は挑戦してみてください。
リスト : 5 パズル
# 隣接リスト
adjacent = [[1, 3], [0, 2, 4], [1, 5],
[0, 4], [1, 3, 5], [2, 4]];
# state = (board, prevState, spacePosition)
def getBoard(state) first(state) end
def getPrev(state) second(state) end
def getSpace(state) third(state) end
def makeBoard(b, s, p)
let
n = copy(b)
in
n[s] = n[p],
n[p] = 0,
n
end
end
def printAnswer(state)
if getPrev(state) then printAnswer(getPrev(state)) end,
print(getBoard(state)),
putc(10)
end
def solve5(start, goal)
callcc(fn(exit)
let
q = makeQueue(), a = nil
in
a = cons(start, a),
enqueue(q, list(start, nil, position(fn(x) x == 0 end, start))),
while not isEmptyQueue(q) do
let
s0 = dequeue(q)
in
foreach(fn(x)
let
s1 = list(makeBoard(getBoard(s0), getSpace(s0), x), s0, x)
in
if equal(getBoard(s1), goal) then
printAnswer(s1),
exit(nil)
else
if not find(fn(b) equal(b, getBoard(s1)) end, a) then
a = cons(getBoard(s1), a),
enqueue(q, s1)
end
end
end
end,
adjacent[getSpace(s0)]
)
end
end
end
end)
end
Calc> solve5([4,5,0,1,2,3],[1,2,3,4,5,0]); [4, 5, 0, 1, 2, 3] [4, 0, 5, 1, 2, 3] [0, 4, 5, 1, 2, 3] [1, 4, 5, 0, 2, 3] [1, 4, 5, 2, 0, 3] [1, 0, 5, 2, 4, 3] [1, 5, 0, 2, 4, 3] [1, 5, 3, 2, 4, 0] [1, 5, 3, 2, 0, 4] [1, 5, 3, 0, 2, 4] [0, 5, 3, 1, 2, 4] [5, 0, 3, 1, 2, 4] [5, 2, 3, 1, 0, 4] [5, 2, 3, 1, 4, 0] [5, 2, 0, 1, 4, 3] [5, 0, 2, 1, 4, 3] [0, 5, 2, 1, 4, 3] [1, 5, 2, 0, 4, 3] [1, 5, 2, 4, 0, 3] [1, 0, 2, 4, 5, 3] [1, 2, 0, 4, 5, 3] [1, 2, 3, 4, 5, 0]
最短手数は 21 手、これが最長手数の局面です。ご参考までに、最長手数の局面を求めるプログラムを示します。
リスト : 最長手数の局面を求める
def solve5_max()
let
start = list([1,2,3,4,5,0], 0, 5),
q = makeQueue(), a = nil,
in
a = cons(start, a),
enqueue(q, start),
while not isEmptyQueue(q) do
let
s0 = dequeue(q)
in
foreach(fn(x)
let
s1 = list(makeBoard(getBoard(s0), getSpace(s0), x), second(s0) + 1, x)
in
if not find(fn(b) equal(getBoard(b), getBoard(s1)) end, a) then
a = cons(s1, a),
enqueue(q, s1)
end
end
end,
adjacent[getSpace(s0)]
)
end
end,
let max_move = second(car(a)) in
print("max = "),
print(max_move),
print("\n"),
while max_move == second(car(a)) do
print(getBoard(car(a))),
print("\n"),
a = cdr(a)
end
end
end
end
Calc> solve5_max(); max = 21 [4, 5, 0, 1, 2, 3] 0
リスト : ナンバープレース
# 大域変数
size = 9;
board = nil;
xflag = nil;
yflag = nil;
gflag = nil;
# 初期化
def initialize()
board = makeVector(size * size, 0),
xflag = makeVector(size, nil),
yflag = makeVector(size, nil),
gflag = makeVector(size, nil),
let i = 0 in
while i < size do
xflag[i] = iota(1, 9),
yflag[i] = iota(1, 9),
gflag[i] = iota(1, 9),
i = i + 1
end
end
end
# グループ番号の取得
def get_group(x, y)
3 * (x / 3) + y / 3
end
# 可能性のある数字を求める
def get_numbers(x, y)
let g = get_group(x, y) in
intersection(intersection(xflag[x], yflag[y]), gflag[g])
end
end
# 数字のセット
def set_number(x, y, n)
let g = get_group(x, y) in
board[x * size + y] = n,
xflag[x] = remove(fn(z) n == z end, xflag[x]),
yflag[y] = remove(fn(z) n == z end, yflag[y]),
gflag[g] = remove(fn(z) n == z end, gflag[g])
end
end
# 数字を取り消す
def del_number(x, y, n)
let g = get_group(x, y) in
board[x * size + y] = 0,
xflag[x] = cons(n, xflag[x]),
yflag[y] = cons(n, yflag[y]),
gflag[g] = cons(n, gflag[g])
end
end
# 盤面の表示
def print_board(board)
let i = 0, k = size * size in
while i < k do
print(board[i]),
if i % size == size - 1 then print("\n") else print(" ") end,
i = i + 1
end,
print("\n")
end
end
# データの読み込み
def read_data(qs)
let i = 0, k = size * size in
while i < k do
if qs[i] > 0 then
let
x = i / size,
y = i % size
in
set_number(x, y, qs[i])
end
end,
i = i + 1
end
end
end
# ナンバープレースの解法
def number_place(qs)
let rec
solve = fn(x, y)
if y == size then
print_board(board)
else
if x == size then
solve(0, y + 1)
else
if board[x * size + y] == 0 then
foreach(
fn(n)
set_number(x, y, n),
solve(x + 1, y),
del_number(x, y, n)
end,
get_numbers(x, y)
)
else
solve(x + 1, y)
end
end
end
end
in
initialize(),
read_data(qs),
solve(0, 0)
end
end
# 問題 (出典: 数独 - Wikipedia の問題例)
qs = [5, 3, 0, 0, 7, 0, 0, 0, 0,
6, 0, 0, 1, 9, 5, 0, 0, 0,
0, 9, 8, 0, 0, 0, 0, 6, 0,
8, 0, 0, 0, 6, 0, 0, 0, 3,
4, 0, 0, 8, 0, 3, 0, 0, 1,
7, 0, 0, 0, 2, 0, 0, 0, 6,
0, 6, 0, 0, 0, 0, 2, 8, 0,
0, 0, 0, 4, 1, 9, 0, 0, 5,
0, 0, 0, 0, 8, 0, 0, 7, 9]
Calc> number_place(qs); 5 3 4 6 7 8 9 1 2 6 7 2 1 9 5 3 4 8 1 9 8 3 4 2 5 6 7 8 5 9 7 6 1 4 2 3 4 2 6 8 5 3 7 9 1 7 1 3 9 2 4 8 5 6 9 6 1 5 3 7 2 8 4 2 8 7 4 1 9 6 3 5 3 4 5 2 8 6 1 7 9
盤面が 9 * 9 の場合、単純なバックトラック法だけで簡単に解くことができました。