Prologで制約充足の問題を解決できますか?


18

である「パーティー出席」プロローグで解ける問題のタイプ?例えば:

ゴボウマルドーンとカルロッタピンクストーンは、アルバスダンブルドアが来たら来ると言いました。アルバス・ダンブルドアとデイジー・ドッデリッジは両方とも、カルロッタ・ピンクストーンが来たら来たと言った。アルバス・ダンブルドア、ゴボウ・マルドーン、カルロッタ・ピンクストーンは全員、エルフリダ・クラッグが来れば来ると言った。Carlotta PinkstoneとDaisy Dodderidgeは、どちらもFalco Aesalonが来たら来ると言っていました。ゴボウ・マルドーン、エルフリダ・クラッグ、ファルコ・アエサロンは、カルロッタ・ピンクストーンとデイジー・ドッデリッジの両方が来たら来ると言った。デイジー・ドッデリッジは、アルバス・ダンブルドアとゴボウ・マルドゥーンの両方が来たら彼女が来ると言った。すべての招待者が出席することを保証するために、誰がパーティーに出席するよう説得する必要がありますか?

私はこれをGNU Prologで表現しようとしました:

attend(BM) :- attend(AD).
attend(CP) :- attend(AD).
attend(AD) :- attend(CP).
attend(DD) :- attend(CP). 
attend(AD) :- attend(EC).
attend(BM) :- attend(EC).
attend(CP) :- attend(EC). 
attend(CP) :- attend(FA).
attend(DD) :- attend(FA).
attend(BM) :- attend(CP),attend(DD).
attend(EC) :- attend(CP),attend(DD).
attend(FA) :- attend(CP),attend(DD).
attend(DD) :- attend(AD),attend(BM).

attend(FA). /* try different seed invitees in order to see if all would attend*/

/* input:
write('invited:'),nl,
  attend(X),write(X),nl,
  fail.*/

私はスタックオーバーフロー(しゃれなし)を経験しており、プロローグ評価の知識がありません。これが私が尋ねている理由です。

一般的に、この問題はブールCNF満足度式(6つのブール変数)にキャストできます。したがって、プロローグの視点にはメリットがありますか?


2
私は、あなたの問題は大文字の識別子が変数であるということであると考えるattend(BM) :- attend(AD).と全く同じであるattend(X) :- attend(Y).
svick

小さい文字(ideone.com/w622Z)で試してみましたが、まだ同じ結果です。
手切ねなし

私は明らかに長い間Mercury / Prologを実行していません。もちろんsvickのポイントは正しいです。あなたの最初のプログラムはおおよそ「誰かが認められれば誰かが認められます」と言うことに対応します。変数を具体的な用語に置き換えたら、私の答えで説明した問題にぶつかります。
ベン、

Prologはチューリング完全言語なので、簡単な答えは「はい」です。
デビッドリチャービー

回答:


13

Prologで問題を解決するには、他のプログラミング言語と同様に、宣言型であれ、命令型であれ、ソリューションの表現と入力について考える必要があります。

これはプログラミングの質問なので、プログラマがプログラミングの問題を解決するStackOverflow.comで人気があったでしょう。ここで、私はもっと科学的になろうとします。

Attend(X)Attend(Y)Attend(Z)Attend(AD)Attend(BM)Attend(DD)

デイジー・ドッデリッジは、アルバス・ダンブルドアとゴボウ・マルドゥーンの両方が来たら彼女が来ると言った

治療がより困難です。

Prologを使用する最初の簡単なアプローチは、関係の完全な逆転を回避し、代わりに目標を指示することです。

ゲストのリストで注文を想定し、ルールを使用する

{A(X)A(Y)A(Z),A(W)A(X),A(W)A(Y),X<Z,Y<Z}A(W)A(Z)

A(X)Attend(X)

このルールは簡単に実装できます。

かなり素朴なアプローチ

読みやすくするためfollowsに、入力として指定された関係を、bringsその逆にします。

次に、入力は

follows(bm,[ad]).
follows(cp,[ad]).
follows(ad,[cp]).
follows(dd,[cp]).
follows(ad,[ec]).
follows(bm,[ec]).
follows(cp,[ec]).
follows(cp,[fa]).
follows(dd,[fa]).
follows(bm,[cp,dd]).
follows(ec,[cp,dd]).
follows(fa,[cp,dd]).
follows(dd,[ad,bm]).

またbrings、次のように定義できます。

brings(X,S):-brings(X,S,[]).

brings(_X,[],_S).
brings(X,[X|L],S):-brings(X,L,[X|S]).
brings(X,[Y|L],S):-follows(Y,[X]),brings(X,L,[Y|S]).
brings(X,[Y|L],S):-follows(Y,[A,B]),
          member(A,S),member(B,S),brings(X,L,[Y|S]).

brings/3(X,L,S)X

定義する場合

 partymaker(X):-Guests=[ad,bm,cp,dd,ec,fa],member(X,Guests),brings(X,Guests).

次のユニークなソリューションがあります。

 [ad,ec]

これは完全なリストではありません。アルファベット順で句が

 follows(bm,[cp,dd]).

動かない。

元のパズルのかなり複雑なソリューション

問題を完全に解決するには、検索ツリーに無限ループを導入することなく、実際にシステムに後のゲストの出席を証明させなければなりません。この目標を達成する方法は複数あります。それぞれに長所と短所があります。

1つの方法はbrings/2、次のように再定義することです。

brings(X,S):-brings(X,S,[],[]).

% brings(X,RemainsToBring,AlreadyTaken,AlreadyTried).
%
% Problem solved
brings(_X,[],_S,_N). 
% Self
brings(X,[X|L],S,N):-brings(X,L,[X|S],N). 
% Follower
brings(X,[Y|L],S,N):-follows(Y,[X]),brings(X,L,[Y|S],N). 
% Y is not a follower, but X can bring 2
brings(X,[Y|L],S,N):- \+member(Y,N),\+follows(Y,[X]), 
                   follows(Y,[A,B]),
                   try_bring(X,A,L,S,[Y|N]),
                   try_bring(X,B,L,S,[Y|N]),brings(X,L,[Y|S],N).
% Y is not a follower, but X can bring 1
brings(X,[Y|L],S,N):- \+member(Y,N),\+follows(Y,[X]),\+follows(Y,[_A,_B]), 
                   follows(Y,[C]),
                   try_bring(X,C,L,S,[Y|N]),brings(X,L,[Y|S],N).

try_bring(_X,A,_L,S,_N):-member(A,S).
try_bring(X,A,L,S,N):- \+member(A,S),sort([A|L],Y),brings(X,Y,S,N).

の最後の引数brings/4は、の無限ループを回避するために必要try_bringです。

これにより、Albus、Carlotta、Elfrida、Falcoの回答が得られます。ただし、このソリューションは、バックトラックが時々回避できる場所に導入されるため、最も効率的なソリューションではありません。

一般的な解決策

r(X,S):VV

SVV=V{X}

VUV

add_element(X,V,U):- ( var(V) -> % set difference that works in both modes
                           member(X,U),subtract(U,[X],V);
                      \+member(X,V),sort([X|V],U) ).

support(V,U):- guests(G), % rule application
               member(X,G),
               add_element(X,V,U),
               follows(X,S),
               subset(S,V).

set_support(U,V):- support(V1,U), % sort of a minimal set
               ( support(_V2,V1) -> 
                      set_support(V1,V) ; 
                 V = V1). 

is_duplicate(X,[Y|L]):- ( subset(Y,X) ; is_duplicate(X,L) ).

% purging solutions that are not truly minimal
minimal_support(U,L):-minimal_support(U,[],L).
minimal_support([],L,L).
minimal_support([X|L],L1,L2):-( append(L,L1,U),is_duplicate(X,U) -> 
                                    minimal_support(L,L1,L2); 
                                minimal_support(L,[X|L1],L2) ).


solution(L):- guests(G),setof(X,set_support(G,X),S),
              minimal_support(S,L).

たとえば、データセット#2が次のように指定されている場合

follows(fa,[dd,ec]).
follows(cp,[ad,bm]).
guests([ad,bm,cp,dd,ec,fa]).

答えはL = [[ad、bm、dd、ec]]になります。つまり、CarlotteとFalco以外のすべてのゲストを招待する必要があります。

このソリューションが私に与えた答えは、より多くのソリューションが作成されたデータセット#6を除き、Wicked Witchの記事で示されたソリューションと一致しました。これが正しい解決策のようです。

最後に、この種の問題に特に適したPrologのCLP(FD)ライブラリについて言及する必要があります。


正解にはF(つまり、A、C、E、F)も含まれます。ルールにタイプミスがあるか、プログラムにさらに深刻な問題があります。
手切ねなし


記事にリンク先サイトからのデータセット#2 ideone.com/21AmXそれを動作させるように見えるdoes notの...
Tegiri Nenashi

あなたのソリューションは複数の選択肢を処理しますか(dataset#8)ideone.com/rBjXi
Tegiri Nenashi

@TegiriNenashiリンクされているサイトには、6つの「想定しない」仮定があります。私の解決策は、№2と№5を満たしていません。№5は簡単に修正できるようです。2つの「%Not a follower」ルールを一般化します。これが修正されると、データセット#8の最初の回答が得られます。仮定№2が満たされるまで、例のデータセットはどちらも正しく解くことができません。
ドミトリチュバロフ

10

svickが発見したように、OPのコードの最初の問題は、大文字で始まる名前がPrologの変数であるということです。したがって、admit(CP) :- admit(AD)と同等です。これはattend(X) :- attend(Y)、Prologが、無限のループに入り、attend保持する用語を見つけることによって、その用語の保持を実証しようとする結果になりattendます。

ただし、イニシャルの各セットが具体的な個別の用語であることを意味する場合、サイクルがあるため、スタックオーバーフローが発生します。たとえば、

attend(cp) :- attend(ad).
attend(ad) :- attend(cp).

したがって、attend(cp)Prologはattend(ad)、ホールドかどうかを判断するために、ホールドかどうかを判断しようとしattend(cp)ます。これは、スタックがオーバーフローするまで、ホールドかどうかをチェックすることによって行われます。

バニラ・プロローグは、そのようなサイクルがあるかどうかを判断しようとは思わず、無限ループに陥るのではなく、いずれかまたは真になる他の方法を調べます。attend(cp)attend(ad)

そのような機能をサポートするPrologのバージョンがある場合とない場合があります。私はMercuryに精通しており、Mercuryの「最小モデルテーブル」はまさにこの場合に必要なものだと思います。実際に使用したことはありませんが、それを証明する他の方法がある場合、それ自体を意味する用語を多かれ少なかれ許可し、そうでない場合は無限ループに巻き込まれることなく偽と見なすことができます。Mercuryのドキュメントの関連セクションを参照し、興味がある場合は実装について説明したペーパーを参照してください。

Mercuryは、純度が強化された論理プログラミング言語であり、Prologと同様の構文を備えていますが、強力な型およびモードシステムであり、解釈されるのではなくコンパイルされます。

私は論文の紹介をもう一度読みました(しばらく読みませんでした)。また、Prologsのいくつかのバージョンで実装されたタブについて言及しているので、タブでググリングすることでさらに進むことができるかもしれません。プロローグで。



0

小文字/大文字の問題は別として、句にはサイクルがあります。

attend(cp) :- attend(ad).
attend(ad) :- attend(cp).

したがって、トップダウンインタープリターを呼び出すと、ループします。ボトムアップで機能するAnswer Set Programming(ASP)を使用すると、さらに運が上がるかもしれません。ライブラリ(minimal / asp)によるコーディングは次のとおりです。

:- use_module(library(minimal/asp)).

choose([admit(bm)]) <= posted(admit(ad)).
choose([admit(cp)]) <= posted(admit(ad)).
choose([admit(ad)]) <= posted(admit(cp)).
choose([admit(dd)]) <= posted(admit(cp)).
choose([admit(ad)]) <= posted(admit(ec)).
choose([admit(bm)]) <= posted(admit(ec)).
choose([admit(cp)]) <= posted(admit(ec)).
choose([admit(cp)]) <= posted(admit(fa)).
choose([admit(dd)]) <= posted(admit(fa)).
choose([admit(bm)]) <= posted(admit(cp)),posted(admit(dd)).
choose([admit(ec)]) <= posted(admit(cp)),posted(admit(dd)).
choose([admit(fa)]) <= posted(admit(cp)),posted(admit(dd)).
choose([admit(dd)]) <= posted(admit(ad)),posted(admit(bm)).

choose([admit(fa)]) <= posted(init).

実行例を次に示します。

Jekejeke Prolog 3, Runtime Library 1.3.8 (23 May 2019)

?- post(init), listing(admit/1).
admit(fa).
admit(cp).
admit(ad).
admit(bm).
admit(dd).
admit(ec).
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.