了解序言列表

时间:2019-01-05 18:15:42

标签: prolog

我试图了解Prolog列表,以及如何在递归函数的末尾“返回” /实例化值。

我正在看这个简单的例子:

val_and_remainder(X,[X|Xs],Xs).
val_and_remainder(X,[Y|Ys],[Y|R]) :-
   val_and_remainder(X,Ys,R).

如果我致电val_and_remainder(X, [1,2,3], R).,则会得到以下输出:

X = 1, R = [2,3]; 
X = 2, R = [1,3];
X = 3, R = [1,2];
false.

但是我对为什么在基本情况(val_and_remainder(X,[X|Xs],Xs).Xs中必须如此显示感到困惑。

如果我要调用val_and_remainder(2, [1,2,3], R).,那么在我看来,它将像这样在程序中运行:

% Initial call
val_and_remainder(2, [1,2,3], R).

val_and_remainder(2, [1|[2,3]], [1|R]) :- val_and_remainder(2, [2,3], R).

% Hits base case
val_and_remainder(2, [2|[3]], [3]).

如果以上方法正确无误,那么它如何获得R的正确值?与上述情况一样,R的值应为R = [1,3]

4 个答案:

答案 0 :(得分:7)

在Prolog中,您需要将谓词视为功能,而不像通常使用其他语言那样。 谓词描述了可能包含有助于定义该关系的参数的关系。

例如,让我们举一个简单的例子:

same_term(X, X).

这是一个谓词,它定义了两个参数之间的关系。通过 unification 可以说,如果第一个和第二个参数是统一的,则它们是相同的(并且定义取决于我们,谓词的作者)。因此,same_term(a, a)将成功,same_term(a, b)将失败,而same_term(a, X)将通过X = a成功。

您也可以用更明确的形式写出来:

same_term(X, Y) :-
    X = Y.  % X and Y are the same if they are unified

现在让我们看一下您的示例val_and_remainder/3。首先,这是什么意思?

val_and_remainder(X, List, Rest)

这意味着XList的元素,而Rest是由其余所有元素组成的列表(无X)。 (注意:您没有立即解释这个含义,但是我正在从您的示例的实现中确定这个含义。)

现在我们可以写出描述规则了。首先,一个简单的基本案例:

val_and_remainder(X,[X|Xs],Xs).

这说:

  

Xs是列表[X|Xs]的其余部分,不含X

通过Prolog中列表的[X|Xs]语法定义,该语句应该很明显。您需要所有这些参数,因为第三个参数Xs必须与列表[X|Xs]的尾部(其余部分)统一,然后列表Xs的尾部(其余)(根据定义,同名变量,统一)。和以前一样,您可以将其写为:

val_and_remainder(X, [H|T], R) :-
    X = H,
    R = T.

但是简短的形式实际上更清晰。

现在递归子句说:

val_and_remainder(X, [Y|Ys], [Y|R]) :- 
    val_and_remainder(X, Ys, R).

所以这意味着:

  

[Y|R]是列表[Y|Ys]中没有X的其余部分如果 R是列表Ys中没有元素的其余部分X

您需要考虑该规则,以使自己确信该规则在逻辑上是正确的。 Y在第二个和第三个参数中是相同的,因为它们指的是相同的元素,因此必须统一。

因此,这两个谓词子句构成了覆盖这两种情况的两个规则。第一种情况是简单的情况,其中X是列表的第一个元素。第二种情况是当X不是第一个元素时的递归定义。

当您进行查询时,例如val_and_remainder(2, [1,2,3], R).,Prolog会看是否可以用事实或您的一个谓词从句的开头统一术语val_and_remainder(2, [1,2,3], R)。 。它无法与val_and_remainder(X,[X|Xs],Xs)统一,因为它需要与X统一2,这意味着它将需要与[1,2,3]统一[2|Xs]失败,因为[1,2,3]的第一个元素是1,但是[2|Xs]的第一个元素是2。

因此,Prolog继续前进,并通过将val_and_remainder(2, [1,2,3], R)与2,val_and_remainder(X,[Y|Ys],[Y|R])与1,XY统一来成功地与Ys统一[2,3] ,以及R[Y|R](注意,这很重要,您调用中的R变量与谓词定义中的R变量不同,因此我们应该将此名称命名为R1以避免混淆。我们将您的R命名为R1,并说R1[Y|R]是统一的。

执行第二个子句的主体时,它将调用val_and_remainder(X,Ys,R).或换句话说,称为val_and_remainder(2, [2,3], R)。现在,这将与第一个子句结合在一起,并给您R = [3]。放开所有这些内容时,得到R1 = [Y|[3]],并回想Y绑定为1,结果为R1 = [1,3]

答案 1 :(得分:4)

逐步复制Prolog的机制通常会导致混乱,而无济于事。您可能有类似“返回”之类的概念,意思很具体,更适合命令式语言。

以下是您始终可以使用的不同方法:

询问最普遍的查询

...,让Prolog向您解释这种关系是什么。

| ?- val_and_remainder(X, Xs, Ys).
     Xs = [X|Ys]
  ;  Xs = [_A,X|_B],
     Ys = [_A|_B]
  ;  Xs = [_A,_B,X|_C],
     Ys = [_A,_B|_C]
  ;  Xs = [_A,_B,_C,X|_D],
     Ys = [_A,_B,_C|_D]
  ;  Xs = [_A,_B,_C,_D,X|_E],
     Ys = [_A,_B,_C,_D|_E]
  ; ...

因此XsYs共享一个公用列表前缀,Xs之后又有一个X,后跟一个公用的其余部分。该查询将继续产生进一步的答案。有时,您想查看所有答案,那么您必须更加具体。但不要太具体:

| ?- Xs = [_,_,_,_], val_and_remainder(X, Xs, Ys).
     Xs = [X,_A,_B,_C],
     Ys = [_A,_B,_C]
  ;  Xs = [_A,X,_B,_C],
     Ys = [_A,_B,_C]
  ;  Xs = [_A,_B,X,_C],
     Ys = [_A,_B,_C]
  ;  Xs = [_A,_B,_C,X],
     Ys = [_A,_B,_C]
  ;  false.

因此,在这里,我们得到了四元素列表的所有可能答案。全部。

在进行具体推论时坚持地面目标

因此,与其考虑val_and_remainder(2, [1,2,3], R).(显然会使您发疯),不如考虑val_and_remainder(2, [1,2,3], [1,3]).然后 val_and_remainder(2, [2,3],[3])。从这一方面来看,应该很明显。

从右到左阅读Prolog规则

请参阅Prolog规则作为生产规则。因此,只要所有内容都位于规则的右侧,您就可以得出左侧的内容。因此,:-是1970年代早期的←

表示。

稍后,您可能还需要考虑更复杂的问题。喜欢

功能依赖性

  

第一个和第二个参数是否唯一地确定最后一个? XXsYs成立吗?

这是一个示例查询,要求相同的YsYs2的{​​{1}}和X不同。

Xs

因此,显然,给定的| ?- val_and_remainder(X, Xs, Ys), val_and_remainder(X, Xs, Ys2), dif(Ys,Ys2). Xs = [X,_A,X|_B], Ys = [_A,X|_B], Ys2 = [X,_A|_B], dif([_A,X|_B],[X,_A|_B]) ; ... Ys的{​​{1}}有不同的值。这是一个具体实例:

X

这里没有经典的回归。它不会返回一次,而是返回两次。更像是Xs

但是,实际上 是参数之间的功能依赖!你能找到吗?而且您可以Prolog明智地证明它(确实像Prolog可以做的一样多。)

答案 2 :(得分:3)

来自评论:

  

R的结果如何正确,因为如果您查看我的运行   程序调用的Xs的值不是[1,3],而是   最终输出;而是[3]与R结合(很明显,我是   一路上缺少一些东西,但是我不确定那是什么。)

这是正确的:

% Initial call
val_and_remainder(2, [1,2,3], R).

val_and_remainder(2, [1|[2,3]], [1|R]) :- val_and_remainder(2, [2,3], R).

% Hits base case
val_and_remainder(2, [2|[3]], [3]).

但是Prolog与其他编程语言不同,在Prolog中,您使用输入输入,然后在return语句中使用输出退出。在Prolog中,您将继续进行谓词语句的合并,并继续使用正确的谓词,并在回溯时也统一未绑定的变量。 (从技术上讲,这是不正确的,但如果您这样想,对某些人来说更容易理解。)

您没有考虑到现在回溯时已绑定的未绑定变量。

在遇到基本情况时,Xs已绑定到[3]

但是当您回溯时,您可以查看

val_and_remainder(2, [1|[2,3]], [1|R])

,尤其是第三个参数的[1|R]

由于在对基本案例的调用中XsR统一,即

val_and_remainder(X,[X|Xs],Xs).

R现在有[3]

现在第三个参数位置

val_and_remainder(2, [1|[2,3]], [1|R])

[1|R],它是[1|[3]],作为语法糖是[1,3],而不仅仅是[3]

现在查询时

val_and_remainder(2, [1,2,3], R).

运行时,查询R的第三个参数与谓词的第三个参数统一

val_and_remainder(X,[Y|Ys],[Y|R])

因此R[Y|R]统一了,其取消响应回溯为[1,3] 因此绑定到查询变量R的值为[1,3]

答案 3 :(得分:2)

我不了解您的谓词名称。无论如何,这是分心的。变量的不均匀命名也是一种干扰。让我们使用一些中性的,简短的单音节名称,以最清晰的形式关注代码本身:

foo( H, [H | T], T).                          % 1st clause

foo( X, [H | T], [H | R]) :- foo( X, T, R).   % 2nd clause

这是内置的select/3是的! ..

现在,您询问查询foo( 2, [1,2,3], R)以及R如何正确设置其值。缺少的主要内容是选择匹配子句时变量的 renaming 。查询的resolution如下:

|-  foo( 2, [1,2,3], R) ? { } 
  %% SELECT -- 1st clause, with rename
  |-  ?  { foo( H1, [H1|T1], T1) = foo( 2, [1,2,3], R) }
         **FAIL** (2 = 1)
         **BACKTRACK to the last SELECT**
  %% SELECT -- 2nd clause, with rename
  |-  foo( X1, T1, R1) ?
         { foo( X1, [H1|T1], [H1|R1]) = foo( 2, [1,2,3], R) }
         **OK**
    %% REWRITE
    |-  foo( X1, T1, R1) ?
          { X1=2, [H1|T1]=[1,2,3], [H1|R1]=R }
    %% REWRITE
    |-  foo( 2, [2,3], R1) ?  { R=[1|R1] } 
      %% SELECT -- 1st clause, with rename
      |-  ? { foo( H2, [H2|T2], T2) = foo( 2, [2,3], R1), R=[1|R1] }
             ** OK ** 
        %% REWRITE
        |-  ? { H2=2, T2=[3], T2=R1, R=[1|R1] }
        %% REWRITE
        |-  ? { R=[1,3] }
        %% DONE

|-?之间的目标是解决方案{ }内部的方程是替代。知识库(KB)整体上隐式位于|-的左侧。

在每个步骤中,选择解析器中最左边的目标,并从KB (同时重命名所有子句的变量以一致的方式进行,这样重命名的子句就不会使用解析器中的变量,因此不会意外捕获变量) ,并且在解析器中使用该子句的替换目标主体,同时将成功的统一添加到替换中。当解析程序为空时,查询已得到证明,并且我们看到的是整个and-or tree中成功的分支。


这是机器可以做到的方式。此处介绍了“重写”步骤,以便于人类理解。

所以我们可以在这里看到第一个成功的从句选择产生于方程式

     R = [1 | R1     ]

,第二个--

              R1 = [3]

,两者共同构成

     R = [1,        3]

列表的这种自顶向下的逐步实例化/充实是Prolog的一种非常有特色的处理方式。


针对悬赏挑战,关于关系foo/3(即select/3)中的功能依赖性:在foo(A,B,C)中,B和{{ 1}}唯一确定C的值(或不存在):

A

试图通过反驳反驳:

2 ?- foo( A, [0,1,2,1,3], [0,2,1,3]).
A = 1 ;
false.

3 ?- foo( A, [0,1,2,1,3], [0,1,2,3]).
A = 1 ;
false.

4 ?- foo( A, [0,1,2,1,3], [0,1,2,4]).
false.

f ?- foo( A, [0,1,1], [0,1]).
A = 1 ;
A = 1 ;
false.

序言未能找到反驳。

希望通过迭代加深来更仔细地了解正在发生的事情:

10 ?- dif(A1,A2), foo(A1,B,C), foo(A2,B,C).
Action (h for help) ? abort
% Execution Aborted

28 ?- length(BB,NN), foo(AA,BB,CC), XX=[AA,BB,CC], numbervars(XX), writeln(XX), (NN>3, !, fail). [A,[A],[]] [A,[A,B],[B]] [A,[B,A],[B]] [A,[A,B,C],[B,C]] [A,[B,A,C],[B,C]] [A,[B,C,A],[B,C]] [A,[A,B,C,D],[B,C,D]] false. 29 ?- length(BB,NN), foo(AA,BB,CC), foo(AA2,BB,CC), XX=[AA,AA2,BB,CC], numbervars(XX), writeln(XX), (NN>3, !, fail). [A,A,[A],[]] [A,A,[A,B],[B]] [A,A,[A,A],[A]] [A,A,[A,A],[A]] [A,A,[B,A],[B]] [A,A,[A,B,C],[B,C]] [A,A,[A,A,B],[A,B]] [A,A,[A,A,A],[A,A]] [A,A,[A,A,B],[A,B]] [A,A,[B,A,C],[B,C]] [A,A,[B,A,A],[B,A]] [A,A,[A,A,A],[A,A]] [A,A,[B,A,A],[B,A]] [A,A,[B,C,A],[B,C]] [A,A,[A,B,C,D],[B,C,D]] false. AA总是实例化为相同的变量。

数字3没什么特别的,因此可以概观地推测,无论尝试多少长度,它总是如此。


以Prolog方式进行证明的另一种尝试:

AA2

这会将成功的ground_list(LEN,L):- findall(N, between(1,LEN,N), NS), member(N,NS), length(L,N), maplist( \A^member(A,NS), L). bcs(N, BCS):- bagof(B-C, A^(ground_list(N,B),ground_list(N,C),foo(A,B,C)), BCS). as(N, AS):- bagof(A, B^C^(ground_list(N,B),ground_list(N,C),foo(A,B,C)), AS). proof(N):- as(N,AS), bcs(N,BCS), length(AS,N1), length(BCS, N2), N1 =:= N2. 组合的总数与其产生的B-C的数目进行比较。平等意味着一一对应。

所以我们有

A

因此,对于它保存的任何2 ?- proof(2). true. 3 ?- proof(3). true. 4 ?- proof(4). true. 5 ?- proof(5). true. 。越来越慢。一般的,无限制的查询编写起来很简单,但是速度下降似乎是指数级的。