Transcript Document
数値・記号処理(11)
Prolog の実行制御
慶應義塾大学理工学部
櫻井彰人
等号そして否定について
等価性を調べる方法はいくつかある.
X = Y
項 X とY が unify するとき成功.
X is Y
X =:= Y
X =\= Y
式 Y の計算結果が項 X の値と照合するとき成功.
2つの式の計算結果が照合するとき成功.
2つの式の計算結果が照合しないとき成功.
X == Y
X \== Y
2項が文字通り同じとき (literal equality) に成功
literal equality = 構造が同じで要素が同じ名前.
2項が文字通りには同じでないとき成功.
\+ Goal
not( Goal )
Goal が失敗したとき成功
同上
等号の例
| ?- 3+4 = 4+3.
no % 項として比較
| ?- 3+4 = 3+4.
yes
| ?- X = 4+3.
X = 4+3 ?
yes
| ?- X is 4+3.
X = 7 ?
yes
| ?- 3+4 is 4+3.
no % 左は項であるべし
| ?- 3+4 =:= 4+3.
yes % 両方の値を計算する
| ?- 3+4 =\= 4+3.
no
| ?- 3+4
no
| ?- 3+4
yes
| ?- 3+X
X = 4 ?
| ?- 3+X
no
== 4+3.
\== 4+3.
= 3+4.
yes
== 3+4.
| ?- \+ 3+4 == 4+3.
yes
Prolog における評価方法と trace
ゴール G を call するには:
1. G と照合する最初の節頭部を捜す:
1. 全変数を対応させながら、変数束縛する,
2. 本体のゴールを call する;
3. すべてが成功なら, G は succeeds
(そして exits).
2. 失敗なら次の節を試す;
3. 次の節がなければ, ゴールG は fail.
ゴールが fail すると:
最も最近成功したゴールを redo する
ゴールを redo するには:
1.
2.
3.
この前成功したときの変数束縛は解除;
当該ゴールに対し、未試行の節を試す;
もしなければ, ゴールは失敗.
Byrd Box model
tracer が用いる実行モデル.
Lawrence Byrd による.
EXIT
CALL
GOAL
FAIL
Exception (error)
REDO
ゴールの Redo
fact(b,1).
fact(b,2).
a :- fact(b,N), fact(c,N).
|?- a.
CALL
EXIT
fact(b,N)
N=1
ゴールの Redo (2)
fact(b,1).
fact(b,2).
a :- fact(b,N), fact(c,N).
|?- a.
CALL
EXIT
fact(b,N)
CALL
N=1
REDO
FAIL
fact(c,1)
ゴールの Redo (3)
fact(b,1).
fact(b,2).
a :- fact(b,N), fact(c,N).
|?- a.
EXIT
N=1
EXIT
CALL
fact(b,N)
N=2
REDO
ゴールの Redo (4)
fact(b,1).
fact(b,2).
a :- fact(b,N), fact(c,N).
|?- a.
no.
EXIT
N=1
EXIT
CALL
FAIL
fact(b,N)
CALL
N=2
REDO
FAIL
fact(c,2)
Prolog の粘り腰
副目標(sub-goal)が fail すると, Prolog は, 最も最近に成
功したゴールに backtrack し、他に照合するものを探す.
この副目標に照合するものがなければ, 再び backtrack す
る; ただし、親のゴールに戻る前に, 副目標の retry は全て
やりおえる.
call はどの節頭部とも照合を試みる.
新しい インスタンス化
redo はかつて照合したものは無視する.
a:- b, c, d, e, f, g, h, I, j .
a:- b, c, d, e, f, g, h, I, j .
Succeed
Fail
Redo
Backtrack
a:- b, c, d, e, f, g, h, I, j .
Cut !
backtrack を抑止することは, redo できる副目標を制
御することにより行う。これは、 cut = ! で行う.
節本体内では、一つのゴールのように扱う.
cut は callされると成功する, しかし backtrack 時に
それを redo しようとすると, 親のゴール(当該 cut を含む
節本体の頭部に照合したゴール)を fail させる.
当該述語中でなした決定に commit することである.
cut の前や後では、制限なく backtrack できる。しかし,
backtrack は cut を通りぬけることはできない.
immediate fail
a:- b, c, d, e, !, f, g, h, I, j .
a:- b, c, d, e, !, f, g, h, I, j .
親ゴールを fail する
a:- b, c, d, e, !, f, g, h, I, j .
a:- b, c, d, e, !, f, g, h, I, j .
a:- k.
a:- k.
a:- m .
これらはないかの
ように扱われる
a:- m .
この節となした選択に
commit する
Cut は call されれば成功し, 親ゴールが起動され
てから当該 cut が起動されるまでの間になされた選
択に Prolog システムを commit させる.
これには, 当該 cut を含む節への commit も含む.
= 当該ゴールが成功するのは, この節が成功するときに限られる.
Cut を経由する backtrack が試みられると
当該節は直ちに fail し,
他の候補節を探索することはしない.
互いに排他的な節
Cut を使用するのは, ある節集合が互いに排他的な場合(一節
が成功すれば, 他の節は成功しない)のみに限るべきである.
もしある節集合が互いに排他的であれば, 一度成功した節以
外の節を Prolog システムが試行する必要はない
節本体に cut を入れるということは, その節に commit すること
である.
Cut を本体の先頭に置くことは, 頭部の unification が終了するや否やそ
の節に commit することを意味する.
a(1,X):- !, b(X), c(X).
Cut を本体のどこか(末尾も含め)におくことは, cut 以前の副ゴールが成
功したときに, 当該節に commit することを表す.
a(_,X):- b(X), c(X), !.
互いに排他的な節 (2)
f(X,0):- X < 3.
f(X,1):- 3 =< X, X < 6.
f(X,2):- 6 =< X.
|?- trace, f(2,N).
1
1 Call:
f(2,_487) ?
2
2 Call: 2<3 ?
2
2 Exit: 2<3 ? ?
1
1 Exit: f(2,0) ?
N = 0 ? ;
1
1 Redo: f(2,0) ?
3
2 Call: 3=<2 ?
3
2 Fail: 3=<2 ?
4
2 Call: 6=<2 ?
4
2 Fail: 6=<2 ?
1
1 Fail:
f(2,_487) ?
no
安全な cut !
f(X,0):- X < 3, !.
f(X,1):- 3 =< X, X < 6, !.
f(X,2):- 6 =< X.
この点に到達すれば, もう他の
節を試みることはない.
|?- trace, f(2,N).
1
1 Call:
f(2,_487) ?
2
2 Call: 2<3 ?
2
2 Exit: 2<3 ? ?
1
1 Exit: f(2,0) ?
N = 0 ? ;
no
注意: cut があろうとなかろうと, 答えは同じである.
その理由は, cut はプログラムの論理的動作を変えることはないからで
ある.
cut が変えるのは手続き的動作である. すなわち, どのゴールをいつ調
べるかということを変える.
これは安全なcut green cut と呼ばれる. Cut の正しい用法である.
安全な cut を用いるときには, 当該節集合が実際に互いに排他
的であることを確かめるべき.
危険な cut !
f(X,0):- X < 3, !.
f(X,1):- 3 =< X, X < 6, !.
f(X,2):- 6 =< X.
Redundant ではないか?
| ?- f(7,N).
1
1 Call:
f(7,_475) ?
2 Call: 7<3 ?
2 Fail: 7<3 ?
2 Call: 3=<7 ?
3
2 Exit:
4
2 Call:
4
2 Fail:
5
2 Call:
5
2 Exit:
1
1 Exit:
N = 2 ?
yes
2
2
3
3=<7 ?
7<6 ?
7<6 ?
6=<7 ?
6=<7 ?
f(7,2) ?
節は互いに排他的であり, 並んでいる順序は, より上の節がfailするのは
ある条件が成立したときのみということが分かっているので.
効率向上のために, 余分な副ゴールの実行を省略したい.
危険な cut !
f(X,0):- X < 3.
f(X,1):- X < 6.
f(X,2).
f(X,0):- X < 3, !.
f(X,1):- X < 6, !.
f(X,2).
| ?- f(7,N).
1
1 Call:
f(7,_475) ?
2 Call: 7<3 ?
2 Fail: 7<3 ?
2 Call: 7<6 ?
2 Fail: 7<6 ?
1 Exit: f(7,2) ?
N = 2 ?
yes
2
2
3
3
1
| ?- f(1,Y).
1
1 Call:
2
2 Call:
2
2 Exit:
1
1 Exit:
Y = 0 ? ;
1
1 Redo:
3
2 Call:
3
2 Exit:
1
1 Exit:
Y = 1 ? ;
1
1 Redo:
1
1 Exit:
Y = 2 ?
yes
f(1,_475) ?
1<3 ?
1<3 ? ?
f(1,0) ?
f(1,0) ?
1<6 ?
1<6 ? ?
f(1,1) ?
f(1,1) ?
f(1,2) ?
Cut の使用法
危険な cut は述語の論理的動作を変える.
危険なcut の使用は避けよ!
危険なcutの使用は, プログラムを読みにくくし, 節の特定の並び
方に依存することになる(データベースに書き込むことになると,
変わってしまう可能性がある).
プログラムの実行効率を上げたいなら, 安全なcut を用いて
backtrack を制御すべきである.
(ある節を選ぶかどうかの)テストの代わりに cut を使うのは避
けるべき.
論理を分かりやすくするには, cut は:
p(X):- test1(X), !, call1(X).
p(X):- test2(X), !, call2(X).
p(X):- testN(X), !, callN(X).
p(1,X):- !, call1(X).
p(2,X):- !, call2(X).
p(3,X):- !, callN(X).
testI 述語は互いに排他的だとする.
互いに排他的なテストは, 節の
頭部に入れた.
Cut - fail
あるゴールが成功する条件を記述したいのと同様に, ゴール
が fail するときを記述したいことがある.
これには, 組み込み述語の fail を cut とを組み合わせて次
のようにする: “ !, fail. “
= この点に到達すれば, 他の節の如何に関わらず, fail する.
(例) 事実 ‘Mary likes all animals except snakes’ を表現したい
ときには
likes(mary,X):snake(X), !, fail.
likes(mary,X):\+ snake(X),
animal(X).
cut と fail を組み合わせることに
より、backtrack 時に発生する,
第2の節への redundant な call
を削除することがてきる.
Cut – fail: なぜ?
しかしながら, cut-fail を用いると, プログラムコードは
読みにくくなる.
ある事実が偽となる条件を記述するより, 真となる条
件を記述する方が, 一般には, 明確で簡単である.
likes(mary,X):\+ snake(X),
animal(X).
当該事実を表すにはこれで充分.
しかしながら, 時には, 真であることより偽であることを
記述する方が簡単であることがあり, そのような時には
cut-fail を用いればプログラムの効率は向上する.
どのcutにも言えることだが; 注意して使え.
まとめ
等号: =, is, =:=, =\=, ==, \==, \+
REDO 対 CALL
Backtrack を制御する: cut !
効率: (成功しえない)不要な REDO の削除.
プログラムの単純化: 節を選択する条件の記述が簡単
になる.
述語のロバスト化: REDO が強制された時でも正当な
動作を行う.
安全な cut = cut は述語論理を変えない = good
危険な cut = cut なくせば論理が変わる = bad
Cut – fail: 偽であることを証明することが真であること
を証明するより楽な場合.
補遺: プログラム例
簡単な数値計算
探索
Knapsack
8-queens
項は評価せず
項はさまざまに書けるが, 評価(計算)はなされな
い
以下は, Prolog では同じ項:
+(1,*(2,3))
1+ *(2,3)
+(1,2*3)
(1+(2*3))
1+2*3
これらはいずれも 7 とは unify しない(計算しな
いから)
式を評価(計算)させるには
?- X is 1+2*3.
X = 7
Yes
組み込み述語 is を用いると, 数式の評価ができ
る
is(X,Y) (X is Y と同じ)は項 Y を評価しそ
の結果のアトムを X に unify する
インスタンス化済みが必要条件
?- Y=X+2, X=1.
Y = 1+2
X = 1
Yes
?- Y is X+2, X=1.
ERROR: Arguments are not sufficiently instantiated
?- X=1, Y is X+2.
X = 1
Y = 3
Yes
評価可能な述語
“ X is Y ” が正しく動くためには, Y に現れるk
述語は評価可能述語 evaluable predicates でな
いといけない
評価可能述語の例: +, -, * , /
組み込みの評価可能述語はその他にもある:
abs(Z) , sqrt(Z) など
整数と浮動小数点数
?- X is
X = 0.5
Yes
?- X is
X = 0.5
Yes
?- X is
X = 2
Yes
?- X is
X = 2
Yes
1/2.
2種類の数値がある: 整数と
不動小数点数.
1.0/2.0.
評価可能述語は上記2つの
全ての組合せに対し
overload されている(同じ名
前で型が異なっても大丈夫).
2/1.
2.0/1.0.
Prolog は動的型付け言語;
型は実行時に用いられて
overload の解消をする.
ゴール 2=2.0 は fail する.
比較
数値の比較演算子:
<, >, =<, >=, =:=, =\=
数値比較のゴールを解決するために, Prolog は
まず両辺を評価し, その結果を数値的に比較す
る
従って、両辺の変数はすべて束縛されていなけ
ればならない
比較
?- 1+2 < 1*2.
No
?- 1<2.
Yes
?- 1+2>=1+3.
No
?- X is 1-3, Y is 0-2, X =:= Y.
X = -2
Y = -2
Yes
Prolog における等号
関係はあるが異なる等価比較演算子:
X is Y は Y を評価し, 結果を X とunifyする: 3 is
1+2 は成功するが, 1+2 is 3 は fail する
X = Y は X と Y をunifyする. 評価はしない: 3 = 1+2
と 1+2 = 3 はともに fail.
X =:= Y は二つを評価し比較する:
3 =:= 1+2 と 1+2 =:= 3 はともに成功する
例: mylength
mylength([],0).
mylength([_|Tail], Len) :mylength(Tail, TailLen),
Len is TailLen + 1.
?- mylength([a,b,c],X).
X = 3
Yes
?- mylength(X,3).
X = [_G266, _G269, _G272]
Yes
例とならない例: mylength
mylength([],0).
mylength([_|Tail], Len) :mylength(Tail, TailLen),
Len = TailLen + 1.
?- mylength([1,2,3,4,5],X).
X = 0+1+1+1+1+1
Yes
例: sum
sum([],0).
sum([Head|Tail],X) :sum(Tail,TailSum),
X is Head + TailSum.
?- sum([1,2,3],X).
X = 6
Yes
?- sum([1,2.5,3],X).
X = 6.5
Yes
例: gcd
gcd(X,Y,Z) :X =:= Y,
Z is X.
gcd(X,Y,Denom) :X < Y,
NewY is Y - X,
gcd(X,NewY,Denom).
gcd(X,Y,Denom) :X > Y,
NewX is X - Y,
gcd(NewX,Y,Denom).
注: ここは次の形ではだめ
gcd(X,X,X)
gcd 述語を動かしてみる
?- gcd(5,5,X).
X = 5
Yes
?- gcd(12,21,X).
X = 3
Yes
?- gcd(91,105,X).
X = 7
Yes
?- gcd(91,X,7).
ERROR: Arguments are not sufficiently instantiated
例: factorial
factorial(X,1) :X =:= 1.
factorial(X,Fact) :X > 1,
NewX is X - 1,
factorial(NewX,NF),
Fact is X * NF.
?- factorial(5,X).
X = 120
Yes
?- factorial(20,X).
X = 2.4329e+018
Yes
?- factorial(-2,X).
No
補遺: プログラム例
簡単な数値計算
探索
Knapsack
8-queens
探索
Prolog の強みは(当然)数値計算ではない
最も得意とするのは, 問題空間内の探索である
問題の論理的な定義を与えれば
Prolog が解を見つけてくれる
(といっても バカ力探索ですが)
Knapsack 問題
キャンプ用の荷物を詰めている
収納庫には次のものがある:
品目
重さ(kg)
カロリー
パン
4
9200
パスタ
2
4600
バター
1
6700
ベビーフード
3
6900
ナップザックには 4 kg 入る.
どう得られべば 4 kg でカロリー最大となるか.
Greedy 法はうまくいかない
品目
重さ(kg)
カロリー
パン
4
9200
パスタ
2
4600
バター
1
6700
ベビーフード
3
6900
最大カロリー優先: パンだけ, 9200
最軽優先: バター + パスタ, 11300
(最適選択: バター + ベビーフード, 13600)
探索
この問題に対するどんなアルゴリズムも次の2条
件を満足することはできないことが知られている
常に最良の答えを出し
指数時間未満で動作する
従って、力ずく探索を使っても、まあ、恥ずかしい
ことではない
これは Prolog にとってはありがたい. 探索は
Prolog が得意とするところである
さて、表現は?
個々の食料品(のデータ)を項 food(N,W,C)
で表そう
収納庫は
[food(bread,4,9200),
food(pasta,2,4500),
food(peanutButter,1,6700),
food(babyFood,3,6900)]
ナップザックの中身も同じ表現としよう
/*
weight(L,N) は食料のリスト L を入力とし、その重さの合計
を求める. なお各食料は food(Name,Weight,Calories) の形
をした項である. 結果は N にunifyする.
*/
weight([],0).
weight([food(_,W,_) | Rest], X) :weight(Rest,RestW),
X is W + RestW.
/*
calories(L,N) は食料の項のリスト L を入力とし, そのカロリー
の合計を求める. なお各食料は food(Name,Weight,Calories)
の形をした項とする. 結果は N にunifyする.
*/
calories([],0).
calories([food(_,_,C) | Rest], X) :calories(Rest,RestC),
X is C + RestC.
/*
subseq(X,Y) が成功するのは, リスト X が, リスト Y の要素から
0個以上の要素を省いたリスとと同じになるとき.
どんなインスタンス化の状態でも動作する.
*/
subseq([],[]).
subseq([Item | RestX], [Item | RestY]) :subseq(RestX,RestY).
subseq(X, [_ | RestY]) :subseq(X,RestY).
リストの subsequence は当該リストの要素から任
意個数要素を取り除いたもののコピーである
(ナップザックの中身は, 収納庫内の中味の
subsequence である)
?- subseq([1,3],[1,2,3,4]).
Yes
?- subseq(X,[1,2,3]).
X
X
X
X
X
X
X
X
=
=
=
=
=
=
=
=
No
[1, 2,
[1, 2]
[1, 3]
[1] ;
[2, 3]
[2] ;
[3] ;
[] ;
3] ;
;
;
;
注: subseq はあるリストが他の
リストの部分列であるかどうかを
テストするだけではない; 部分列
を生成することができる! これを
用いて、ナップザック問題をとく
/*
knapsackDecision(Pantry,Capacity,Goal,Knapsack) の入力
は食料項のリスト Pantry, 正の数 Capacity, 正の数 Goal.
Knapsack は Pantry の部分列で全カロリーが Goal 以上, かつ
総重量が Capacity 以下という制約を満たすものと unify する
*/
knapsackDecision(Pantry,Capacity,Goal,Knapsack) :subseq(Knapsack,Pantry),
weight(Knapsack,Weight),
Weight =< Capacity,
calories(Knapsack,Calories),
Calories >= Goal.
?- knapsackDecision(
|
[food(bread,4,9200),
|
food(pasta,2,4500),
|
food(peanutButter,1,6700),
|
food(babyFood,3,6900)],
|
4,
|
10000,
|
X).
X = [food(pasta, 2, 4500),
food(peanutButter, 1, 6700)]
Yes
これは、与えられたカロリーのゴールに合う解を
(もしあれば)提示してくれる
ということは、求めているものと少々違う、、、、
決定と最適化
先ほど解いたのはナップザック決定問題
decision problem
解きたいのはナップザック最適化問題
optimization problem
これを行うにため, 組み込み述語 findall を
用いる
findall 述語
findall(X,Goal,L)
Goal を証明する全ての方法(X で表現)をみつける
それぞれの証明について, Goal 証明可能インスタン
スとする代入を X に適用する
Unifies L を, 上記のような X のリストと unify する
解を数える
?- findall(1,subseq(_,[1,2]),L).
L = [1, 1, 1, 1]
Yes
これは, subseq(_,[1,2])に4通りの証明方
法があることを示している
一証明につき1個の1をリストとする
解インスタンスを集める
?- findall(subseq(X,[1,2]),subseq(X,[1,2]),L).
X = _G396
L = [subseq([1, 2], [1, 2]), subseq([1], [1, 2]),
subseq([2], [1, 2]), subseq([], [1, 2])]
Yes
findall の第1、第2パラメータは同じ
これにより、ゴール subseq(X,[1,2]) の全4
個の証明可能インスタンスが集まる
特定の代入を集める
?- findall(X,subseq(X,[1,2]),L).
X = _G312
L = [[1, 2], [1], [2], []]
Yes
findall の普通の用法: 第1パラメータは第2
パラメータに含まれる変数
これにより、ゴールsubseq(X,[1,2]) を証明
可能とする, 4個の X を集める
/*
legalKnapsack(Pantry,Capacity,Knapsack)の入力
は食料項のリスト Pantry, 正の数 Capacity, 正の数 Goal.
Knapsack は Pantry の部分列で
総重量が Capacity 以下という制約を満たすものと unify する.
*/
legalKnapsack(Pantry,Capacity,Knapsack):subseq(Knapsack,Pantry),
weight(Knapsack,W),
W =< Capacity.
/*
maxCalories(List,Result) は食料項のリストを入力とする. Result
と unify するのは総カロリーを最大化するリスト要素である. そのために
補助述語 maxC を用意する. これには4個のパラメータがある: 食料リスト
の残りのリスト, これまでみた食料項目のリストで最良のもの,
その総カロリー, 最終結果である
*/
maxC([],Sofar,_,Sofar).
maxC([First | Rest],_,MC,Result) :calories(First,FirstC),
MC =< FirstC,
maxC(Rest,First,FirstC,Result).
maxC([First | Rest],Sofar,MC,Result) :calories(First,FirstC),
MC > FirstC,
maxC(Rest,Sofar,MC,Result).
maxCalories([First | Rest],Result) :calories(First,FirstC),
maxC(Rest,First,FirstC,Result).
/*
knapsackOptimization(Pantry,Capacity,Knapsack)の入力
は食料項のリスト Pantry, 正の数 Capacity, 正の数 Goal.
Knapsack は Pantry の部分列で, 総カロリーが最大, かつ
総重量が Capacity 以下という制約を満たすものと unify する.
*/
knapsackOptimization(Pantry,Capacity,Knapsack) :findall(K,legalKnapsack(Pantry,Capacity,K),L),
maxCalories(L,Knapsack).
?- knapsackOptimization(
|
[food(bread,4,9200),
|
food(pasta,2,4500),
|
food(peanutButter,1,6700),
|
food(babyFood,3,6900)],
|
4,
|
Knapsack).
Knapsack = [food(peanutButter, 1, 6700),
food(babyFood, 3, 6900)]
Yes
補遺: プログラム例
簡単な数値計算
探索
Knapsack
8-queens
The 8-Queens Problem
チェスに関する知識:
8 x 8 の盤面を使用
Queen は垂直, 水平, 対角線方向に何枡でも動ける
2つの queen がチェックの位置にあるというのは, 両方
が同一の水平線上, 垂直線上, または対角線上に
あって, 相手の升目に移動できる状態をいう
問題: 8個の queen を、互いに相手を check しな
いような位置に(queen 以外はない盤面上に) 配
置しなさい
表現は?
第2列・第5行にクィーンがあることを
queen(2,5) で表す
もっとコンパクトかつ読みやすい方法はないか
他の駒はないのであるから—例えば
pawn(X,Y) はないし king(X,Y)もない—
X/Y という形の項を用いることにしよう
(注: (数値計算の) 商だと思って評価することは
ない)
例
盤面はクィーンのリストで表現できる
右下の盤面は [2/5,3/7,6/1]
8
7
Q
6
5
Q
4
3
2
Q
1
1
2
3
4
5
6
7
8
/*
nocheck(X/Y,L) の入力は, クィーン X/Y とクィーンのリスト
である. 成功するのは、 X/Y クィーンがチェックに入らないとき
に限る.
*/
nocheck(_, []).
nocheck(X/Y, [X1/Y1 | Rest]) :X =\= X1,
Y =\= Y1,
abs(Y1-Y) =\= abs(X1-X),
nocheck(X/Y, Rest).
/*
legal(L) が成功するのは L がクィーンの正しい置き方になって
いるとき: すなわち全てのクィーンの座標は所定の範囲内にあり
どのクィーンもチェック状態にない.
*/
legal([]).
legal([X/Y | Rest]) :legal(Rest),
member(X,[1,2,3,4,5,6,7,8]),
member(Y,[1,2,3,4,5,6,7,8]),
nocheck(X/Y, Rest).
そこそこ
これはもう充分な解: 質問 legal(X) に対し、
正しい配置が全て求まる:
?- legal(X).
X = [] ;
X = [1/1] ;
X = [1/2] ;
X = [1/3]
8-Queen 問題の解
勿論、長い時間がかかる: 一個のクィーンがおけ
る 64個の解を見つけ、それを元に、2個のクィー
ンの解、次には3個のクィーンの解と求めていく
(そうではなく)すぐに 8-queens 問題 にすぐ取り
掛かるように, 別の query を考える:
?- X = [_,_,_,_,_,_,_,_], legal(X).
X = [8/4, 7/2, 6/7, 5/3, 4/6, 3/8, 2/5, 1/1]
Yes
例
8-queen 問題のある解
[8/4, 7/2, 6/7, 5/3,
4/6, 3/8, 2/5, 1/1]
8
Q
7
Q
6
Q
5
Q
Q
4
Q
3
Q
2
1
Q
1
2
3
4
5
6
7
8
改善の余地あり
遅い
次から次へとトリビアルな置換を見つけている:
?- X = [_,_,_,_,_,_,_,_], legal(X).
X = [8/4, 7/2, 6/7, 5/3, 4/6, 3/8, 2/5, 1/1] ;
X = [7/2, 8/4, 6/7, 5/3, 4/6, 3/8, 2/5, 1/1] ;
X = [8/4, 6/7, 7/2, 5/3, 4/6, 3/8, 2/5, 1/1] ;
X = [6/7, 8/4, 7/2, 5/3, 4/6, 3/8, 2/5, 1/1]
改善
明らかに、どの解も各列に一個のクィーンがある
従って、どの解もある固定した順に書くことがで
きる:
X=[1/_,2/_,3/_,4/_,5/_,6/_,7/_,8/_]
この形のゴール項から開始することにより探索を
制限する(従って、探索速度を上げる)ことができ、
trivial な順列を抑止することができる
/*
eightqueens(X) が成功するのは X が8個のクィーンの
正しいおき方であって, X 座標の順にリストされているときのみ
である.
*/
eightqueens(X) :X = [1/_,2/_,3/_,4/_,5/_,6/_,7/_,8/_],
legal(X).
nocheck(_, []).
nocheck(X/Y, [X1/Y1 | Rest]) :% X =\= X1, X達は互いに異なると仮定
Y =\= Y1,
abs(Y1-Y) =\= abs(X1-X),
nocheck(X/Y, Rest).
legal([]).
legal([X/Y | Rest]) :legal(Rest),
% member(X,[1,2,3,4,5,6,7,8]), X は領域内と仮定
member(Y,[1,2,3,4,5,6,7,8]),
nocheck(X/Y, Rest).
全 X-座標はすでに範囲内にあり異なることが分
かっているので, 少し最適化に寄与する
改良版 8-Queen 問題解
速くなった
煩わしい順列がない
?- eightqueens(X).
X = [1/4, 2/2, 3/7, 4/3, 5/6, 6/8, 7/5, 8/1] ;
X = [1/5, 2/2, 3/4, 4/7, 5/3, 6/8, 7/6, 8/1]
実験
legal([]).
legal([X/Y | Rest]) :legal(Rest),
% member(X,[1,2,3,4,5,6,7,8]), X は範囲内と仮定
1=<Y, Y=<8, % 旧 member(Y,[1,2,3,4,5,6,7,8]),
nocheck(X/Y, Rest).
Fail する: “arguments not sufficiently instantiated”
この member による条件は座標が範囲内にある
かの単なるテスト test ではない; それを生成
generate している
実験もう一つ
legal([]).
legal([X/Y | Rest]) :% member(X,[1,2,3,4,5,6,7,8]), X は範囲内と仮定
member(Y,[1,2,3,4,5,6,7,8]),
nocheck(X/Y, Rest),
legal(Rest). % もとは最初の条件
Fail する: “arguments not sufficiently instantiated”
legal(Rest) の条件は最初にくるべき, という
のも, これが部分解を生成 generate して、それが
nocheck でテストされるから
The Farmer-Wolf-Goat-Cabbage Problem
要素: farmer,wolf,goat and the cabbage.
川を渡らなければいけない. 制約がある.
•
•
•
ボートは一度に2個(人)しか運べない
wolf と goat だけを一緒にすることはできない
goat と cabbage だけを一緒にすることはできない
川の対岸へ、上記要素を全て運ぶ手順を考えよ.