为什么改变事实的顺序会改变谓词的行为?

时间:2011-11-09 20:16:22

标签: list prolog permutation dcg

这是我的第一个想法:

perm([X|Y],Z) :- takeout(X,Z,W), perm(Y, W).   
perm([],[]).

当我尝试运行-? perm([1, 2, 3], P).时,它显示了堆栈溢出问题。

但是如果我们改变两个语句的顺序,它就会起作用。

perm([X|Y],Z) :- perm(Y, W), takeout(X,Z,W).  
perm([],[]). 

为什么呢?我是Prolog的初学者,请帮忙。

4 个答案:

答案 0 :(得分:2)

您引用的takeout/3通常称为select(X, Xs0, Xs)

这是另一个定义 - 用于说明DCG的罕见用法。

perm(Xs,Ys) :-
   phrase(perm(Xs),[],Ys).

perm([]) --> [].
perm([X|Xs]) --> perm(Xs), ins(X).

ins(X),[X] --> [].
ins(X),[Y] --> [Y], ins(X).

答案 1 :(得分:0)

好吧,您的takeout谓词可能看起来像这样:

takeout( X, [X|R], R ).
takeout( X, [F|R], [F|S] ) :-
    takeout( X, R, S ).

SWI-Prolog有一个名为trace的有用谓词。

在第一种情况下:

X = [1, 2, 3] ;
   Redo: (10) takeout(3, _G477, _G485) ? creep
   Call: (11) takeout(3, _G480, _G483) ? creep
   Exit: (11) takeout(3, [3|_G483], _G483) ? creep
   Exit: (10) takeout(3, [_G479, 3|_G483], [_G479|_G483]) ? creep
   Call: (10) perm([], [_G479|_G483]) ? creep
   Fail: (10) perm([], [_G479|_G483]) ? creep
   Redo: (11) takeout(3, _G480, _G483) ? creep
   Call: (12) takeout(3, _G486, _G489) ? creep
   Exit: (12) takeout(3, [3|_G489], _G489) ? creep
   Exit: (11) takeout(3, [_G485, 3|_G489], [_G485|_G489]) ? creep
   Exit: (10) takeout(3, [_G479, _G485, 3|_G489], [_G479, _G485|_G489]) ? creep
   Call: (10) perm([], [_G479, _G485|_G489]) ? creep
   Fail: (10) perm([], [_G479, _G485|_G489]) ? creep
   Redo: (12) takeout(3, _G486, _G489) ? creep
   Call: (13) takeout(3, _G492, _G495) ? creep
   Exit: (13) takeout(3, [3|_G495], _G495) ? creep
   Exit: (12) takeout(3, [_G491, 3|_G495], [_G491|_G495]) ? creep
   Exit: (11) takeout(3, [_G485, _G491, 3|_G495], [_G485, _G491|_G495]) ? creep
   Exit: (10) takeout(3, [_G479, _G485, _G491, 3|_G495], [_G479, _G485, _G491|_G495]) ? creep
   Call: (10) perm([], [_G479, _G485, _G491|_G495]) ? creep
   Fail: (10) perm([], [_G479, _G485, _G491|_G495]) ? creep
   Redo: (13) takeout(3, _G492, _G495) ? creep
   Call: (14) takeout(3, _G498, _G501) ? creep
   Exit: (14) takeout(3, [3|_G501], _G501) ? creep
   Exit: (13) takeout(3, [_G497, 3|_G501], [_G497|_G501]) ? creep
   Exit: (12) takeout(3, [_G491, _G497, 3|_G501], [_G491, _G497|_G501]) ? creep
   Exit: (11) takeout(3, [_G485, _G491, _G497, 3|_G501], [_G485, _G491, _G497|_G501]) ? creep

在第二种情况下:

X = [1, 2, 3] ;
   Redo: (8) takeout(1, _G451, [2, 3]) ? creep
   Call: (9) takeout(1, _G532, [3]) ? creep
   Exit: (9) takeout(1, [1, 3], [3]) ? creep
   Exit: (8) takeout(1, [2, 1, 3], [2, 3]) ? creep
   Exit: (7) perm([1, 2, 3], [2, 1, 3]) ? creep

因此,谓词枚举的顺序实际上很重要。在第一种情况下,您生成了许多具有未知值的状态。最好(尽可能)获取一份文件清单,运行trace并绘制真实情况。

但简而言之,在第一种情况下,你会产生许多涂有takeout事实的未知变量,这些变量无法与perm匹配。

答案 2 :(得分:0)

Prolog使用SLD resolution,因此子句中的子句顺序和文字顺序确实有所不同。基本上,引擎试图通过以深度优先的方式从上到下搜索来解决子句头。换句话说,在声明性语义的顶部有一个过程语义。这种区别有时会使初学者感到困惑,但另一方面,它是Prolog真正成为编程语言(即图灵完成)的关键原因。

答案 3 :(得分:-1)

你的基本情况perm([],[])需要先出现,否则它将继续下降到perm谓词,直到你的堆栈空间用完为止。对于未来的谓词也要记住这一点,它在prolog中非常重要。

另外,你应该改变烫发和烫发的顺序。在另一个谓词中取出。