我试图了解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]
。
答案 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)
这意味着X
是List
的元素,而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,X
与Y
统一来成功地与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]
; ...
因此Xs
和Ys
共享一个公用列表前缀,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规则作为生产规则。因此,只要所有内容都位于规则的右侧,您就可以得出左侧的内容。因此,:-
是1970年代早期的←
稍后,您可能还需要考虑更复杂的问题。喜欢
第一个和第二个参数是否唯一地确定最后一个?
X
,Xs
→Ys
成立吗?
这是一个示例查询,要求相同的Ys
和Ys2
的{{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]
。
由于在对基本案例的调用中Xs
与R
统一,即
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.
。越来越慢。一般的,无限制的查询编写起来很简单,但是速度下降似乎是指数级的。