【问题标题】:Implementing "last" in Prolog在 Prolog 中实现“last”
【发布时间】:2015-10-11 23:05:43
【问题描述】:

我正在尝试通过 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[] 匹配。无论如何,我无法解释为什么切换声明顺序应该有任何效果。

问题一:last1行为的正确解释是什么?

问题 2:如何实现与内置 last 无法区分的谓词 last1

【问题讨论】:

    标签: list prolog prolog-toplevel


    【解决方案1】:

    问题一:

    Prolog 系统并不总是能够在执行之前决定一个子句是否适用。具体情况取决于实现。也就是说,您一般不能依赖该决定。系统确实从一个版本到另一个版本有所改进。考虑为最简单的情况:

    ?- X = 1 ; 1 = 2。 X = 1; 错误的。

    一个非常聪明的 Prolog 可以检测到 1 = 2 总是失败,因此只需回答 X = 1.。另一方面,这种“聪明”的实现成本非常高,最好将时间花在优化更频繁的情况上。

    那么为什么 Prologs 会显示这个呢?主要原因是避免温顺地询问另一个答案,如果 Prolog 已经知道没有进一步的答案。因此,在此改进之前,系统会提示您为所有包含变量的查询提供另一个答案,并在每个查询中得到 false 或“否”,并且只有一个答案。这曾经非常麻烦,以至于许多程序员从不要求下一个答案,因此不会收到有关意外答案的警报。

    第二个原因是让您了解实现的局限性:如果 Prolog 要求对这个通用查询提供另一个答案,这意味着它仍然使用一些空间,这些空间可能会积累并耗尽您的所有计算资源。

    在您使用last1/2 的示例中,您遇到了这种情况。顺便说一句,您已经做了一些非常聪明的事情:您尝试最小化查询以查看意外行为的第一次出现。

    在您的示例查询 last1([1,2],X) 中,Prolog 系统不会查看整个列表 [1,2],而只会查看主函子。因此,对于 Prolog 系统,当它决定应用哪些子句时,查询看起来与 last1([_|_],X) 相同。这个目标现在适用于这两个子句,这就是 Prolog 会记住第二个子句作为尝试替代的原因。

    但是,想一想:这个选择现在可以对所有元素,但最后一个!这意味着您为每个元素支付一些内存!您实际上可以通过使用很长的列表来观察这一点。这是我在我的小型 32 位笔记本电脑上得到的——您可能需要在更大的系统上再添加零或两个:

    ?- 长度(L,10000000), last1(L,E). 错误:超出本地堆栈

    另一方面,预定义的last/2 工作顺利:

    ?- 长度(L,10000000),最后(L,E)。 L = [_G343,_G346,_G349,_G352,_G355,_G358,_G361,_G364,_G367|...]。

    实际上,它使用常量空间!

    现在有两种方法:

    1. 尝试优化您的定义。是的,你可以做到这一点,但你需要非常聪明!例如,@back_dragon 的定义是不正确的。经常发生的情况是,初学者试图优化程序,而实际上他们正在破坏程序的语义。

    2. 问问自己是否真的定义了与last/2 相同的谓词。事实上,你不是。

    问题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). ** 循环 **

    因此,在这种情况下,您的定义与 SWI 的定义不同。交换子句的顺序。

    ?- 长度(L,10000000), last2(L,E)。 L = [_G343,_G346,_G349,_G352,_G355,_G358,_G361,_G364,_G367|...]; 错误的。

    又是这个false!但这一次,大名单奏效了。而这一次,最小的查询是:

    ?- last2([1],E). E = 1 ; 错误的。

    情况也很相似:同样,Prolog 将以与last2([_|_],E) 相同的方式查看查询,并得出两个子句都适用的结论。至少,我们现在有恒定的开销而不是线性开销。

    有几种方法可以以干净的方式克服这种开销 - 但它们都非常依赖于实现的内部结构。

    【讨论】:

    • last 的实现肯定比我想象的要微妙。我选择讲义主要是因为它是对 Prolog 的简要介绍,但现在我认为 Prolog 的艺术 可能是一个更好的选择。后者对Prolog的执行模型有更好的解释。
    • 大多数 Prolog 程序员已经对last2/2 感到满意了。
    【解决方案2】:

    当 SWI-Prolog 可以确定没有解决方案时,它会尝试避免提示更多解决方案。我认为解释器检查内存寻找一些choice point,如果找不到,只需声明终止。否则它会等待让用户选择移动。

    我会尝试以这种方式使 last1 具有确定性:

    last1([_,H|Rest], Last) :- !, last1([H|Rest], Last).
    last1([Last], Last).
    

    但我认为它不是来自lastindistinguishable。潜伏在库的源代码(简单如?- edit(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).
    

    我们可以欣赏一个深思熟虑的实现。

    【讨论】:

    • last1(Xs,X) 没有给出答案
    • 我不知道edit。一定会派上用场的。
    • 在您的示例中出现在右侧的列表拆分运算符的目的是什么?我的版本无法正常工作:last1([_,T], Last) :- !, last1(T, Last)。最后一个([最后一个],最后一个)。但是当我像你一样用 H|Rest 替换 T 时,它会起作用.. 为什么?
    • @DeanRadcliffe: last1([_,T], Last) 匹配 2 个元素的列表。相反,管道运算符引入了尾部,即列表,因此允许任意数量的元素或为空。
    【解决方案3】:

    这段代码可以工作:

    last1([Last], Last).
    last1([_ | Rest], Last) :- last1(Rest, Last), !.
    

    这是因为prolog的东西可能有更多的组合,但是,有了这个符号:!,prolog到达这一点后不会返回

    【讨论】:

    • last1(L,N), L = [_,_,_]. 应该成功,但您的版本失败。
    猜你喜欢
    • 2014-08-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-11
    • 1970-01-01
    • 2018-09-05
    • 1970-01-01
    • 2012-11-30
    相关资源
    最近更新 更多