【问题标题】:Why is this "last_element" prolog predicate more optimal?为什么这个“last_element”序言谓词更优化?
【发布时间】:2019-01-28 19:41:35
【问题描述】:

我们想检查一个元素是否出现在列表的最后,我们有这个 prolog 程序,这对我来说很明显:

last(X,[X]).
last(X,[_|L]):-
    last(X,L).

但是我们的老师给了我们另一个解决方案,他说它更优化,有它:

last(X,[X]).
last(X,[_,Y|L]):-
    last(X,[Y|L]).

为什么第二种解决方案更优化?

【问题讨论】:

标签: prolog


【解决方案1】:

这完全取决于您测量的内容。显然,您的老师做了@Armatorix 在他的回答中所做的事情,并且只是计算了 last/2 的调用。但在实践中,您的教师版本可能在所有 Prolog 系统上都会变慢,甚至明显变慢:

% Simple version:
?- length(Xs,10000000), time(last(X,Xs)).
% 10,000,000 inferences, 0.852 CPU in 0.852 seconds (100% CPU, 11735202 Lips)

% "Optimized" version
?- length(Xs,10000000), time(last(X,Xs)).
% 9,999,999 inferences, 4.436 CPU in 4.437 seconds (100% CPU, 2254074 Lips)

要了解原因,您必须意识到“优化”版本是语法糖

last(X,[X]).
last(X,[_,Y|L]):-
   YL = [Y|L],    % this takes time!
   last(X,YL).

【讨论】:

    【解决方案2】:

    好吧,理论上他是对的。 使用他的方法,我们不会检查总是包含在列表中的最后一个空元素。 用 2 个东西很容易检查,第一个是 time 函数,另一个是 trace。以下是列表(lastX 是您的教师版本,lastY 是您的版本):

    ?- time(lastY(5,[1,2,3,4,5])).
    % 5 inferences, 0.000 CPU in 0.000 seconds (86% CPU, 1743983 Lips)
    true
    
    ?- time(lastX(5,[1,2,3,4,5])).
    % 4 inferences, 0.000 CPU in 0.000 seconds (86% CPU, 194203 Lips)
    true .
    
    ?- time(lastX(6,[1,2,3,4,5])).
    % 6 inferences, 0.000 CPU in 0.000 seconds (87% CPU, 279382 Lips)
    false.
    
    ?- time(lastY(6,[1,2,3,4,5])).
    % 7 inferences, 0.000 CPU in 0.000 seconds (85% CPU, 335008 Lips)
    false.
    
    [trace]  ?- lastY(6,[1,2,3,4,5]).
       Call: (7) lastY(6, [1, 2, 3, 4, 5]) ? Unknown option (h for help)
       Call: (7) lastY(6, [1, 2, 3, 4, 5]) ? creep
       Call: (8) lastY(6, [2, 3, 4, 5]) ? creep
       Call: (9) lastY(6, [3, 4, 5]) ? creep
       Call: (10) lastY(6, [4, 5]) ? creep
       Call: (11) lastY(6, [5]) ? creep
       Call: (12) lastY(6, []) ? creep
       Fail: (12) lastY(6, []) ? creep
       Fail: (11) lastY(6, [5]) ? creep
       Fail: (10) lastY(6, [4, 5]) ? creep
       Fail: (9) lastY(6, [3, 4, 5]) ? creep
       Fail: (8) lastY(6, [2, 3, 4, 5]) ? creep
       Fail: (7) lastY(6, [1, 2, 3, 4, 5]) ? creep
    false.
    
    [trace]  ?- lastX(6,[1,2,3,4,5]).
       Call: (7) lastX(6, [1, 2, 3, 4, 5]) ? creep
       Call: (8) lastX(6, [2, 3, 4, 5]) ? creep
       Call: (9) lastX(6, [3, 4, 5]) ? creep
       Call: (10) lastX(6, [4, 5]) ? creep
       Call: (11) lastX(6, [5]) ? creep
       Fail: (11) lastX(6, [5]) ? creep
       Fail: (10) lastX(6, [4, 5]) ? creep
       Fail: (9) lastX(6, [3, 4, 5]) ? creep
       Fail: (8) lastX(6, [2, 3, 4, 5]) ? creep
       Fail: (7) lastX(6, [1, 2, 3, 4, 5]) ? creep
    false.
    

    【讨论】:

    • 现在更清楚了,谢谢。但我不明白为什么使用lastY 版本,有一个空列表的调用,而lastX 没有这样的调用。我认为线索是找出当只有一个元素时调用哪个谓词,是具有一个参数的谓词,还是具有递归性的谓词。
    • 首先看功能,第一个版本发生了什么?两种情况下的第一个谓词都会检查是否只有一个元素以及它是否与您正在寻找的元素相似。第二个谓词切头递归进入。第一个示例:第二个谓词在至少有 2 个元素、任何元素和列表符号结尾 ([]) 时执行。 (现在看trace)第二个例子:类似于第一个例子,它在至少有3个元素时执行,所以至少它会用[lastElement,[]]递归执行。 (再看trace)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多