我试图通过浏览Ulle Endriss lecture notes来了解Prolog编程。当我对练习的解决方案没有达到预期的效果时,我发现很难给出一个很好的解释。我认为这与我对Prolog评估表达式的方式的不明确理解有关。
第20页的练习2.6要求递归实现谓词last1
,其行为类似于内置谓词last
。我的尝试如下:
last1([_ | Rest], Last) :- last1(Rest, Last). last1([Last], Last).
它给出了正确的答案,但对于具有多个元素的列表,我必须键入分号才能终止查询。这使last1
与内置last
不同。
?- last1([1], Last).
Last = 1.
?- last1([1, 2], Last).
Last = 2 ;
false.
如果我切换我声明规则和事实的顺序,那么在这两种情况下我都需要键入分号。
我想我知道为什么Prolog认为last1
可能还有一个解决方案(因此是分号)。我想它遵循评估序列
last1([1, 2], Last).
==> last1([2], Last).
==> last1([], Last). OR Last = 2.
==> false OR Last = 2.
这似乎表明我应该寻找一种避免将Rest
与[]
匹配的方法。无论如何,我没有解释为什么切换声明的顺序应该有任何影响。
问题1: last1
行为的正确解释是什么?
问题2:如何实现与内置last1
无法区分的谓词last
?
答案 0 :(得分:13)
Prolog系统并不总是能够在执行之前决定是否适用该条款。具体情况取决于实施。也就是说,一般来说,你不能依赖这个决定。从发布到发布,系统在这里都有所改进。考虑最简单的情况:
?- X = 1 ; 1 = 2. X = 1 ; false.
一个非常聪明的Prolog可以检测到1 = 2
总是失败,因此只需回答X = 1.
。另一方面,这种“聪明”的实施成本非常高,并且可以更好地花时间来优化更频繁的案例。
那么为什么Prologs会这么说呢?如果Prolog已经知道没有进一步的答案,主要原因是避免温顺地问另一个答案。因此,在此改进之前,系统会提示您为包含变量的所有查询提供另一个答案,并在每个查询上获得false
或“否”,只有一个答案。这曾经是如此繁琐,许多程序员从未要求下一个答案,因此没有得到关于意外答案的警报。
第二个原因是让你意识到实现的局限性:如果Prolog要求对这个一般查询提出另一个答案,这意味着它仍然会占用一些空间,这些空间可能会积累并占用你所有的计算资源。 / p>
在您使用last1/2
的示例中遇到这种情况。并且你已经做了一些非常聪明的事情,BTW:你试图最小化查询以查看第一次出现的意外行为。
在您的示例查询last1([1,2],X)
中,Prolog系统不会查看整个列表[1,2]
,而只会查看主要的仿函数。因此,对于Prolog系统,查询在决定应用哪些子句时看起来与last1([_|_],X)
相同。这个目标现在适用于这两个条款,这就是为什么Prolog会记住第二个条款作为替代尝试的原因。
但是,想一想:现在可以选择所有元素,但最后一个!这意味着你为每个元素支付一些内存!你可以通过使用很长的列表来实现这一点。这是我的小型32位笔记本电脑 - 您可能需要在更大的系统上再添加一两个:
?- length(L,10000000), last1(L,E). ERROR: Out of local stack
另一方面,预定义的last/2
可以顺利运行:
?- length(L,10000000), last(L,E). L = [_G343, _G346, _G349, _G352, _G355, _G358, _G361, _G364, _G367|...].
实际上,它使用常量空间!
现在有两种方法:
尝试优化您的定义。是的,你可以做到这一点,但你需要非常聪明!例如@back_dragon的定义是不正确的。通常情况下,初学者会尝试优化程序,而实际上它们正在破坏它的语义。
问问自己是否实际上定义了与last/2
相同的谓词。事实上,你不是。
考虑:
?- last(Xs, X). Xs = [X] ; Xs = [_G299, X] ; Xs = [_G299, _G302, X] ; Xs = [_G299, _G302, _G305, X] ; Xs = [_G299, _G302, _G305, _G308, X] ...
和
?- last1(Xs, X). ** loops **
因此,您的定义在这种情况下与SWI的定义不同。交换条款的顺序。
?- length(L,10000000), last2(L,E). L = [_G343, _G346, _G349, _G352, _G355, _G358, _G361, _G364, _G367|...] ; false.
再次,这false
!但这次,大名单有效。而这一次,最小的查询是:
?- last2([1],E). E = 1 ; false.
情况非常相似:再次,Prolog将以与last2([_|_],E)
相同的方式查看查询,并得出结论两个条款都适用。至少,我们现在有不变的开销而不是线性开销。
有几种方法可以以干净的方式克服这种开销 - 但它们都非常依赖于实现的内部。
答案 1 :(得分:5)
SWI-Prolog尝试避免在确定没有解决方案时提示更多解决方案。我认为解释器检查内存,寻找剩余的choice point
,如果找不到,只需说明终止。否则它等待用户选择移动。
我会尝试以这种方式使last1确定性:
last1([_,H|Rest], Last) :- !, last1([H|Rest], Last).
last1([Last], Last).
但我不认为indistinguishable
?- edit(last).
来自last。潜伏在图书馆的源代码(它简单为%% last(?List, ?Last)
%
% Succeeds when Last is the last element of List. This
% predicate is =semidet= if List is a list and =multi= if List is
% a partial list.
%
% @compat There is no de-facto standard for the argument order of
% last/2. Be careful when porting code or use
% append(_, [Last], List) as a portable alternative.
last([X|Xs], Last) :-
last_(Xs, X, Last).
last_([], Last, Last).
last_([X|Xs], _, Last) :-
last_(Xs, X, Last).
)
{{1}}
我们可以欣赏一个经过深思熟虑的实施。
答案 2 :(得分:0)
这段代码可行:
last1([Last], Last).
last1([_ | Rest], Last) :- last1(Rest, Last), !.
这是因为prolog事情可能有更多的组合,但是,用这个符号:!,prolog在达到这一点后不会回头