230 likes | 364 Views
探索問題のいろいろ. 白井英俊. 人食い人と宣教師. 状態の表現 mandc( ボートの位置、 左岸の宣教師と人食い人の 人数 のリスト、 右岸の宣教師と人食い人の 人数 のリスト) 初期状態 : mandc(left, [3,3], [0,0]) 目的状態 : mandc(right,[0,0],[3,3]). 初期・最終状態の表現と プログラムの実行. 縦型探索のプログラムを見てみよう test_dfs(Problem,Moves) :- initial_state(Problem,State),
E N D
探索問題のいろいろ 白井英俊
人食い人と宣教師 • 状態の表現 mandc(ボートの位置、 左岸の宣教師と人食い人の人数のリスト、 右岸の宣教師と人食い人の人数のリスト) 初期状態: mandc(left, [3,3], [0,0]) 目的状態: mandc(right,[0,0],[3,3])
初期・最終状態の表現とプログラムの実行 縦型探索のプログラムを見てみよう test_dfs(Problem,Moves) :- initial_state(Problem,State), solve_dfs(State,[State],Moves). これは、?- test_dfs(A,B). という実行を要求している。また、初期状態はinitial_state(Problem,State) という表現であることを要求している。だから、
これは何でもよい 初期・最終状態の表現とプログラムの実行(続) 初期状態は、 initial_state(mandc, mandc(left, [3,3], [0,0])) としなければならない また、これにより実行は、 ?- test_dfs(mandc, X). であって、Xには解までの『指し手のリスト」が返される
アルゴリズム:縦型探索 % solve_dfs(状態、過去の状態列、指手列) solve_dfs(State,History,[]) :- final_state(State). % 最終状態なら終了 solve_dfs(State,History,[Move|Moves]) :- move(State,Move), % 次の指し手を選択 update(State,Move,State1), %新状態を求める legal(State1), %新状態は条件遵守している \+ member(State1,History),%未探索の状態 solve_dfs(State1,[State1|History],Moves).
宣教師と人食い人問題における『指し手表現』宣教師と人食い人問題における『指し手表現』 • 指し手を「狼山羊キャベツ問題」にならって 「 move(状態、積荷)」で表すことにしよう • すると(「農夫だけ移動」というのがないので) • 積荷を選ぶ: move(mandc(left,L,R), Cargo) :- transport(L, Cargo). % 左岸の場合 move(mandc(right,L,R), Cargo) :- transport(R, Cargo). %右岸の場合 • ここで、transport(X,Y)とは、Yから、移送可能な「宣教師と人食い人の人数の組合せ」を返すものとする • Moveの第二引数は、[M,C]というリスト---宣教師の人数Mと人食い人の人数C
「指し手表現」の続き • 左岸でも右岸でも、積荷の状態を [ M, C ] で表していることに注意。たとえば初期状態: mandc(left, [3,3], [0,0]) と 目的状態mandc(right,[0,0],[3,3])では、 [3, 3] --- 宣教師も人食い人も3人ずついる [0, 0] --- 宣教師も人食い人も0人(いない) ことを表している
「指し手表現」の完成 • ボートは二人乗りだから、ボートの中の宣教師と人食い人の「可能な」状態は、 の5通りとなるーーー確かめよ。 これを考えると、 transport(Cargo, MC) は次のように書ける: (1) [2, 0] (2) [1, 0] (3) [1,1] (4) [0, 1] (5) [0, 2]
「指し手表現」の完成 • ボートに乗れる人数の制限 capacity([2,0]). capacity([1,0]). capacity([1,1]). capacity([0,1]). capacity([0,2]). • 運べる組み合わせ transport([M,C],[M1,C1]) :- capacity([M1, C1]), % ボートの人数制限 M >= M1, C >= C1. % 岸の人数からの制限
ここでPrologの組み込み述語の紹介: findall • findall(型, 式, 変数) 「式」がtrueとなるそれぞれの場合に対し、」「型」の例示化のリストと「変数」とを単一化する • 例: findall(k(X), append(X,Y,[a,b,c]), Z). => Z=[ k([ ]), k([a]), k([a,b]), k([a,b,c) ] • 参考: findallの仲間にbagofとsetofがある。違いを調べてみよう。
アルゴリズム:縦型探索 % solve_dfs(状態、過去の状態列、指手列) solve_dfs(State,History,[]) :- final_state(State). % 最終状態なら終了 solve_dfs(State,History,[Move|Moves]) :- move(State,Move), % 次の指し手を選択 update(State,Move,State1), %新状態を求める legal(State1), %新状態は条件遵守している \+ member(State1,History),%未探索の状態 solve_dfs(State1,[State1|History],Moves).
人食い人と宣教師の問題における『状態更新』人食い人と宣教師の問題における『状態更新』 • 状態更新を (狼山羊キャベツ問題に倣い) update(状態, 積荷, 新状態) で表す update(mandc(B,L,R),Cargo,mandc(B1,L1,R1)) :- update_boat(B, B1), % ボートの位置 update_banks(Cargo,B, L,R,L1,R1). % 積荷の状態
人食い人と宣教師問題における『状態更新』 ボートの位置の変化: update_boat(left, right). update_boat(right, left). 積荷の変化: update_banks([M,C],left, [ML,CL],[MR,CR],[ML1,CL1],[MR1,CR1]) :- ML1 is ML – M, CL1 is CL – C, MR1 is MR+ M, CR1 is CR+ C. update_banks([M,C],right [ML,CL],[MR,CR],[ML1,CL1],[MR1,CR1]) :- 書いてみよう
updateのチェック transportの時にやったように、update_banksやupdateでも、ちゃんと動いているかをチェックしよう。 ここで、updateはupdate_banksを使っているので、update_banksがちゃんと動いていなければ、updateも動くはずがない! ?-update_banks([1,1],left,[2,2],[1,1],L,R). ?-update_banks([1,1],right,[2,2],[1,1],L,R). が正しい答えを返すかどうかを調べて、必要ならプログラムを直そう。その後、updateも同様に直そう。
アルゴリズム:縦型探索 % solve_dfs(状態、過去の状態列、指手列) solve_dfs(State,History,[]) :- final_state(State). % 最終状態なら終了 solve_dfs(State,History,[Move|Moves]) :- move(State,Move), % 次の指し手を選択 update(State,Move,State1), %新状態を求める legal(State1), %新状態は条件遵守している \+ member(State1,History),%未探索の状態 solve_dfs(State1,[State1|History],Moves).
宣教師と人食い人問題における『状態の検査』宣教師と人食い人問題における『状態の検査』 • どの岸においても、「宣教師がいる以上は、宣教師の人数>=人食い人の人数」であることを検査 legal(mandc(left,[ML,CL],[MR,CR])) :- legal_sub(ML,CL), legal_sub(MR,CR). % 宣教師の人数と人食い人の人数を比較 legal_sub(0,_):-!. % 宣教師がいない場合はOK legal_sub(M,C) :- 条件を書いてみよう
Legal述語のチェック • legal述語は、問題の制約条件を記述しているので、ここをミスすると、問題が解けていないのに、答え(らしきもの)が出てしまう。 • だから、ここのチェックも欠かせない。 • まず、legal_sub(0,2)、 legal_sub(1,1), legal_sub(1,2) などがどのような答えを返すかをチェックし、必要ならプログラムを直す • 同様に legal(mandc(left,[1,2],[2,1]))などがどのような答えを返すかチェックしよう。
探索方法を変えてみよう:横型探索 % (状態, 指手の列) を一つの塊とする % :-solve_bfs([(初期状態, [ ] )], [ ], Moves). で起動 solve_bfs([(State,Moves)|_],History,Moves) :- final_state(State). % 最終状態なら終了 solve_bfs([(State,Moves)|RestAgenda],History,Result) :- findall((NextState,[Move|Moves]),% 可能な指手を収集 nextStates(State,Move,History,NextState),Bag), register (Bag,NewHistory,History), % 履歴の更新 append(RestAgenda,Bag,NewAgenda), % enqueue solve_bfs(NewAgenda,NewHistory,Result). solve_bfs([_|Rest],History,Result) :- % 行き止まりの場合 solve_bfs(Rest,History,Result). % 他の道を選ぶ
横型探索(続) % nextStates(状態,指手,履歴(過去の状態列),他選択肢) % 指手を選び、次状態を求める nextStates(State,Move,History,NextState) :- move(State,Move), update(State,Move,NextState), legal(NextState), \+ member(NextState,History). % register(Agenda,NewHistory,OldHistory) % nextStatesで求めた(次)状態を履歴に登録 register([],X,X). register([(S,_)|R],[S|Y],X) :- register(R,Y,X).
くどいようですが。。。 • 縦型探索でも横型探索でも「過去に調べた状態すべて」を History という引数に記憶し、memberを使ってある状態が『過去に既に調べたものかどうか』を検査している。 • しかし、上の方法は、記憶すべき状態数が多い場合は、あまり効率的とはいえない。 • 前に述べたように、Prologのデータベースを使って、『過去に調べた状態』を記憶したり、検査したりする方法の方が役に立つことが多い。
宿題(12月中) • 嫉妬深い夫の問題(簡略版) 「ある洪水で、3組の夫婦が水に囲まれてしまった。彼らは、その状態からボートで脱出しなければならないが、ボートには一度に二人しか乗ることができない。どの夫も嫉妬深く、彼自身が一緒にいない限り、ボートでも岸でも妻が他の夫といることを許さない。この3組の夫婦を脱出させる方法を求めよ」 レポートはメールで送信。題目を HW-J3 とすること。 (次の問題を解いて、この代わりとしても良い) • 状態表現として jealous(ボートの位置、左の岸にいる男と女のリスト、右の岸にいる男と女のリスト) を使う。(これ以上の詳しい表現方法は各自工夫すること) 注意:宣教師と人食い人問題と同様に「人数」だけで状態を表すことができるだろうか?そうでないとすればどのように表現したらよいだろうか?
嫉妬深い夫の問題(5組版) 「ある洪水で、5組の夫婦が水に囲まれてしまった。彼らは、その状態からボートで脱出しなければならないが、ボートには一度に3人しか乗ることができない。どの夫も嫉妬深く、彼自身が一緒にいない限り、ボートでも岸でも妻が他の夫といることを許さない。この5組の夫婦を脱出させる方法を求めよ」 これを解き、適切な解説をつけた報告を12月中に送ってきたものにボーナス点(最大20点) レポートはメールで送信。題目を HW-J5とすること。
次の問題 • 今までは計算機の力に任せて問題を解いていた:「アルゴリズム」の問題 • 計算機の力任せで解くには難しい問題として、8パズルや15パズルを考えよう。 1 2 3 4 1 2 3 5 6 7 8 4 5 6 9 10 11 12 7 8 13 14 15