Here are two similar algorithms for last/2
. Why does last_element0/2
perform better than last_element1/2
? With the extra code last_element0/2
performs almost as well as last/2
, but it is not as elegant as last_element1/2
. Also, last_element1/2
throws a stack overflow error for lists that are too big, but last_element0/2
doesn't, why? Finally, last_element1/2
has an extra choice point so a cut is necessary, not sure this is a huge concern here.
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).
Here are the test results:
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.