Prolog可以解决约束满足问题吗?


18

“晚会上”之类的问题可解的Prolog的?例如:

Burdock Muldoon和Carlotta Pinkstone都说如果Albus Dumbledore来,他们会来的。阿不思·邓布利多(Albus Dumbledore)和黛西·多德里奇(Daisy Dodderidge)都表示,如果卡洛塔·平石(Carlotta Pinkstone)来,他们会来的。阿不思·邓布利多(Albus Dumbledore),牛d(Burdock Muldoon)和卡洛塔·平斯通(Carlotta Pinkstone)都说,如果埃尔弗里达·克拉格(Elfrida Clagg)来,他们会来的。卡洛塔·平斯通斯通(Carlotta Pinkstone)和黛西·多德里奇(Daisy Dodderidge)都说,如果法尔科·埃萨隆(Falco Aesalon)来,他们会来的。Burdock Muldoon,Elfrida Clagg和Falco Aesalon都说,如果Carlotta Pinkstone和Daisy Dodderidge都来,他们会来的。黛西·多德里奇(Daisy Dodderidge)表示,如果阿不思·邓布利多(Albus Dumbledore)和Burdock Muldoon都来,她会来的。需要说服谁参加聚会,以确保所有受邀者都参加?

我试图在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)仍然是相同的结果。
Tegiri Nenashi 2012年

我显然没有太久没做过Mercury / Prolog,当然svick的观点是正确的,并且您的第一个程序大致相当于说“如果某人被接纳,则某人被接纳”。用具体术语替换变量后,您便遇到了我的答案中解释的问题。
2012年

简单的答案是“是”,因为Prolog是图灵完备的语言。
David Richerby

Answers:


13

与任何编程语言一样,要解决Prolog的问题,无论是声明性的还是命令性的,都必须考虑解决方案和输入的表示形式。

由于这是一个编程问题,因此它在StackOverflow.com上很流行,程序员可以在其中解决编程问题。在这里,我将尝试变得更加科学。

为了解决OP中的问题,必须逆转输入中所述依赖项所定义的关系。形式为A t t e n d X A t t的子句易于逆转。条款ë Ñ d d ë Ñ d 一种ŤŤËñdX一种ŤŤËñdÿ一种ŤŤËñdž一种ŤŤËñd一种d一种ŤŤËñd中号一种ŤŤËñddd

黛西·多德里奇(Daisy Dodderidge)表示,如果阿不思·邓布利多(Albus Dumbledore)和Burdock Muldoon都来,她会来的

更难治疗。

使用Prolog,第一种简单的方法是避免关系的完全逆转,而要以目标为导向。

假设客人名单上的订单并使用规则

{一种X一种ÿ一种ž一种w ^一种X一种w ^一种ÿX<žÿ<ž}一种w ^一种ž

(为了简短起见,我们使用而不是A t t e n d X 一种X一种ŤŤËñdX

该规则易于实施。

一个相当幼稚的方法

为了提高可读性followsbrings将其作为输入给出的关系,并将其取反。

然后输入由

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]).

不管用。

解决原始难题的相当复杂的方法

为了完全解决该问题,您实际上必须让系统尝试为以后的访客证明出勤率,而不会在搜索树中引入无限循环。有多种方法可以实现此目标。每种都有其优点和缺点。

一种方法是重新定义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).

in中的最后一个参数brings/4对于避免in中的无限循环是必需的try_bring

这给出了以下答案:Albus,Carlotta,Elfrida和Falco。但是,此解决方案并不是最有效的解决方案,因为引入了回溯,有时可以避免回溯。

通用解决方案

在将第三次国际NoCOUG SQL和NoSQL挑战赛的链接添加到原始问题之后,很明显,我们所追求的是对来宾集的子集的通用可达性检查器,其中过渡关系由给出的规则使得规则r的应用[RX小号VV

如果小号VV=V{X}

我们对最小子集感兴趣 VüV

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]]。这意味着除卡洛特和法尔科之外,所有客人都必须被邀请。

该解决方案给我的答案与Wicked Witch文章中给出的解决方案相匹配,但数据集#6除外,在该数据集中产生了更多的解决方案。这似乎是正确的解决方案。

最后,我必须提到Prolog的CLP(FD)库,它特别适合此类问题。


正确答案还包括F(即A,C,E,F)。您在规则中有错别字,或者在程序中有更严重的问题。
2012年


来自文章ideone.com/21AmX中链接的站点的数据集#2 似乎不起作用...
Tegiri Nenashi 2012年

您的解决方案是否可以处理多种选择(数据集#8)ideone.com/rBjXi
Tegiri Nenashi 2012年

@TegiriNenashi在链接的网站上有6个“不假设”的假设。我的解决方案不满足№2和№5。№5似乎很容易解决:概括两个“%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)要确定是否attend(ad)成立,Prolog将尝试确定是否成立,将通过检查是否attend(cp)成立来进行确定,依此类推,直到堆栈溢出为止。

我不相信Vanilla Prolog会尝试确定是否存在这种周期并进行检查 其他方法来实现一个attend(cp)attend(ad)而不是陷入无限循环。

可能存在或可能不存在支持该功能的Prolog版本。我对Mercury更加熟悉,并且我认为Mercury的“最小模型表”正是这种情况下所需的。我从未真正使用过它,但是我的理解是,如果有其他证明方式表明它本身,则它或多或少都被认为是正确的,否则,它就为假,而不会陷入无限循环。请参阅Mercury文档的相关部分,如果您对描述实现的论文感兴趣,请参见。

Mercury是一种强制执行的纯逻辑编程语言,具有与Prolog类似的语法,但具有强大的类型和模式系统,并且经过编译而不是解释。

我只是略过了本文的介绍(我已经有一段时间没有读过了),它提到制表已在多个版本的Prologs中实现,因此您可以通过谷歌搜索制表来获得进一步的发展。在Prolog中。



0

撇开小写/大写问题,这些子句中有一个循环:

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

因此,当您调用自上而下的解释器时,它将循环。您可能会对答案集编程(ASP)感到更幸运,它可以自下而上地工作。这是通过库的代码(minimum / 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).
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.