Transforming recursion into tail recursion?

妖精的绣舞 提交于 2019-12-10 19:17:53

问题


I am trying to write a predicate that recursively finds the nth power of some number [A^n = A * A^(n-1)] and uses the shortcut A^(2n) = A^n * A^n.

Here is the solution so far.

p(_,0,1):-!.
p(A,N,R):-N mod 2=0,!,N1=N/2,p(A,N1,R1),R=R1*R1.
p(A,N,R):-N1=N-1,p(A,N1,R1),R=R1*A.

Now I want to make this tail recursive. I can do tail for simple cases, such as factorials and power without the shortcut (by adding an accumulator), but this one is hard.

Any help is much appreciated!


回答1:


It seems it is sort of possible after all, just start it from the other end:

pow(A,N,R) :-
    pow(A,N,A,1,R).

pow(_,N,R,N,R) :- !.
pow(A,N,Acc,M,R) :-
    M =< N div 2, !,
    M1 is M*2,
    NewAcc is Acc * Acc,
    pow(A,N,NewAcc,M1,R).
pow(A,N,Acc,M,R) :-
    M < N,
    M1 is M+1,
    NewAcc is A * Acc,
    pow(A,N,NewAcc,M1,R).

It applies the shortcut up to the highest power of 2 smaller than N, which is admittedly not the same as what your algorithm is doing.




回答2:


Boris is right in that what his algorithm does is not the same as the original one. But here is how you can reproduce it, if you really want to:

Observe that you can determine the order of the operations from the binary representation of the number. Let N=7, then binary N=111, denoted as N=7~111.

Now you see the scheme in your original algorithm:

N      Op     N'
7~111  Mul    6~110 (= zero last bit) 
6~110  Squ    3~011 (= shift right)
3~011  Mul    2~010 
2~010  Squ    1~001
1~001  Base

Considering that due to the recursive nature of the algorithm, these steps are carried out top-to-bottom, you get Base - Squ - Mul - Squ - Mul = ((A*A)*A)*((A*A)*A))*A = A**7

Contrast this to Boris' algorithm:

N      Op     N'
1~001  Squ    2~010 (=shift left)
2~010  Squ    4~100 (=shift left)
4~100  Mul    5~101 (=add one)
5~101  Mul    6~110 (=add one)
6~110  Mul    7~111 (=add one)

So this one does all the shifting first, while the original considers each bit except for the first of N, right to left, in turn, "queuing" (because of bottom-up) Mul, Squ if the bit is set or just Squ if it is unset.

To reproduce this behavior (which is more efficient, as you will never do more simple multiplications than squares), you could start with N in binary and do the following (here in general pseudocode, easy for you to translate into prolog):

Acc=A
for i in (reverse(tail(bits(N)))):
    Acc*=Acc
    if i==1:
       Acc*=A

This is for N>=1. N=0 is a special case and must be treated separately.

I'm pretty sure this is correct. If you have doubts, then just think about your original algorithm: testing for mod 2 == 0 is the same as testing if the last bit is zero. And if it is not, then substracting one is the same as zeroing out the last bit while doubling and halving is just shifting left or right in binary.



来源:https://stackoverflow.com/questions/15283482/transforming-recursion-into-tail-recursion

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!