用`append / 3`或`reverse / 2`来实现`last / 2`

时间:2015-03-10 13:06:45

标签: prolog

为什么SWI-Prolog文档会将append(_, [Last], List)建议为可移植的last/2而不是reverse(List, [Last|_])(请参阅here)?是reverse/2本身没有像append/3那样广泛实施吗?或者是否还有其他我想念的东西?

无论哪种方式,如果列表是循环的,则所有三个都不会终止:

?- L = [z|L], last(L, Last).
^CAction (h for help) ? abort
% Execution Aborted
?- L = [z|L], append(_, [Last], L).
^CAction (h for help) ? abort
% Execution Aborted
?- L = [z|L], reverse(L, [Last|_]).
^CAction (h for help) ? abort
% Execution Aborted

但是,reverse/2至少不会在正确的列表上留下选择点:

?- append(_, [Last], [a]).
Last = a ;
false.

?- reverse([a], [Last|_]).
Last = a.

2 个答案:

答案 0 :(得分:2)

reverse/2的定义实际上并不常见,并且SWI的实现具有更好的终止行为,而许多其他实现仅在第一个参数是列表时才终止。我看到至少有3种不同的实现:一方面是SWI,另一方面是SICStus,还有很多其他实现,然后是XSB,它们位于两者之间。您可以将它们与以下目标区分开来:

reverse(Xs, [a]).     % terminates for SWI and XSB
reverse([a|Xs], [a]). % terminates for SWI

在性能方面,我希望传统的reverse/2(不是SWI的实现)应该更快一些,因为它完全是确定性的。另一方面,它在堆上重新创建整个列表。

在当前的实现中,append(_, [L], Xs)没有理想地实现:对于列表Xs的每个元素,创建一个选择点然后删除,使最后一个选择点保持活动状态。 有关更多信息,请参阅this question

答案 1 :(得分:1)

实际上,系统完全符合我期望的

13 ?- length(L,1000000),time(reverse(L,[X|_])),statistics.
% 1,000,002 inferences, 0.422 CPU in 0.424 seconds (100% CPU, 2367605 Lips)
% Started at Tue Mar 10 14:31:08 2015
% 523.563 seconds cpu time for 7,770,847 inferences
% 13,661 atoms, 4,540 functors, 3,987 predicates, 81 modules, 214,610 VM-codes
% 
%                        Limit    Allocated       In use
% Local  stack:    268,435,456       12,288        1,904 Bytes
% Global stack:    268,435,456  100,659,184   72,011,904 Bytes
% Trail  stack:    268,435,456      129,016        2,280 Bytes
% 
% 8 garbage collections gained 1,837,408 bytes in 1.346 seconds.
% Stack shifts: 13 local, 68 global, 47 trail in 0.034 seconds
% 2 threads, 0 finished threads used 0.000 seconds
L = [_G1238, _G1241, _G1244, _G1247, _G1250, _G1253, _G1256, _G1259, _G1262|...].

14 ?- length(L,1000000),time(append(_,[X],L)),statistics.
% 999,999 inferences, 0.572 CPU in 0.574 seconds (100% CPU, 1747727 Lips)
% Started at Tue Mar 10 14:31:08 2015
% 536.544 seconds cpu time for 8,772,339 inferences
% 13,662 atoms, 4,540 functors, 3,987 predicates, 81 modules, 214,615 VM-codes
% 
%                        Limit    Allocated       In use
% Local  stack:    268,435,456       12,288        2,960 Bytes
% Global stack:    268,435,456   50,327,536   48,011,920 Bytes
% Trail  stack:    268,435,456       30,712        2,312 Bytes
% 
% 8 garbage collections gained 1,837,408 bytes in 1.346 seconds.
% Stack shifts: 13 local, 72 global, 50 trail in 0.036 seconds
% 2 threads, 0 finished threads used 0.000 seconds
L = [_G1240, _G1243, _G1246, _G1249, _G1252, _G1255, _G1258, _G1261, _G1264|...] 
.

似乎reverse / 2正在使用append / 3分配的2倍。反向/ 2的全局和跟踪堆栈使用是双倍的,反向/ 2的结果是巧妙地编译为反向/ 4 ....