以下是last/2
的两种类似算法。为什么last_element0/2
的效果优于last_element1/2
?额外代码last_element0/2
的效果几乎与last/2
相同,但不如last_element1/2
那么优雅。此外,last_element1/2
会为太大的列表引发堆栈溢出错误,但last_element0/2
不会,为什么?最后,last_element1/2
有一个额外的选择点,所以需要削减,不确定这是一个巨大的问题。
last_element0(X,Last) :-
last_element0(X,_,Last).
last_element0([X|Xs],_,Last) :-
last_element0(Xs,X,Last).
last_element0([],Last,Last).
last_element1([_|Xs],Last) :-
last_element1(Xs,Last),
!.
last_element1([Last],Last).
以下是测试结果:
last_element0/2
1 ?- length(Cs, 1000000), maplist(=(c), Cs),time(last_element0(Cs,X)).
% 1,000,003 inferences, 0.031 CPU in 0.023 seconds (136% CPU, 32000096 Lips)
Cs = [c, c, c, c, c, c, c, c, c|...],
X = c.
last_element1/2
2 ?- length(Cs, 1000000), maplist(=(c), Cs),time(last_element1(Cs,X)).
% 1,000,001 inferences, 0.250 CPU in 0.452 seconds (55% CPU, 4000004 Lips)
Cs = [c, c, c, c, c, c, c, c, c|...],
X = c.
last/2
3 ?- length(Cs, 1000000), maplist(=(c), Cs),time(last(Cs,X)).
% 1,000,001 inferences, 0.031 CPU in 0.022 seconds (142% CPU, 32000032 Lips)
Cs = [c, c, c, c, c, c, c, c, c|...],
X = c.
答案 0 :(得分:2)
您的last_element0/2
几乎与library implementation of last/2
相同,至少对于SWI-Prolog而言。它(更快)更快,因为由于底层实现,last_element0/3
(辅助谓词)的两个子句被识别为互斥,因此不会创建任何选择点,也不需要丢弃任何选择点。
但是,看看这个:
?- last_element0(Xs, X). ERROR: Out of local stack ?- last_element1(Xs, X). ERROR: Out of local stack ?- last(Xs, X). Xs = [X] ; Xs = [_G1570, X] ; Xs = [_G1570, _G1573, X] ; % ... and so on
罪魁祸首:辅助谓词的子句的顺序!
顺便说一句,还有其他方法来定义last/2
:
last_reverse(List, Last) :- reverse(List, [Last|_]). last_append(List, Last) :- append(_, [Last], List).
(无耻的自我推销)请参阅this question以及两个答案,以获得有关终止行为,选择点和效率的更多讨论(还有更多链接可供使用)。在回答a question on programmers.stackexchange并在此过程中意识到我一无所知后,我问了这个问题。