Turning recursive function into tail recursion
I am coding in ATS and am trying to make a function that finds the square root of a given integer. Provided here is code that properly meets my requirments other than it not being tail-recursion.
implement intsqrt(n) =
if(n >= 1)
then let
val n4 = n / 4
val res = 2 * intsqrt(n4) + 1
in
if(res * res <= n) then res else 2*intsqrt(n4)
end
else n
I'm not sure if others know this language well or not but it is my first week with it. I know the clear difference between regular and tail recursion, I just don't understand how this can be changed.
I don't even need the exact code to do it, I am just wondering how it is possible. In order for me to find the sqrt, I must calculate n4 = 1 / n and then multiply it by two. However, doing this goes into the recursion. What I want to be able to do is calculate a result, then pass it through to my next recursive call.
Does this mean I need to work backwards in a way? Hopefully this all makes sense but I will try to clarify if needed.
Thanks!
In pattern-matching "pseudocode" (Haskell, where :
is list-building cons
and []
an empty list), your function is
isqrt n | n < 1 = n
| res*res <= n = res
| otherwise = 2 * isqrt(n `div` 4)
where
res = 2 * isqrt(n `div` 4) + 1
To turn it into a tail-recursive one we'll have to maintain the relevant data by ourselves, say, in a list. Simply iterate down towards the n < 1
case first, and then iterate back up until the simulated stack is exhausted and the final result can be produced.
The transformation is straightforward:
isqrt n = go n []
where
go n [] | n < 1 = n -- initial special case
go n (x:xs) | n < 1 = up n x xs -- bottom reached - start going back up
go n xs = go (n `div` 4) (n:xs) -- no - continue still down
up recres n (n1:ns) = -- still some way to go up
let res = 2*recres + 1
in if res*res <= n
then up res n1 ns -- "return" new recursive-result
else up (res-1) n1 ns -- up the chain of previous "invocations"
up recres n [] = -- the last leg
let res = 2*recres + 1
in if res*res <= n
then res -- the final result
else (res-1)
The code can be simplified further now.
A systematic way to do this sort of thing is via CPS-transformation. What is special about the following implementation is that every byte of memory allocated during a call to intsqrt_cps is freed after the call returns. There is no GC here (unlike the above Haskell solution):
fun
intsqrt_cps
(
n: int, k: int -<lincloptr1> int
) : int =
if
(n >= 1)
then let
val n4 = n / 4
in
//
intsqrt_cps
( n4
, llam(res) =>
applin(k, if square(2*res+1) <= n then 2*res+1 else 2*res)
) (* intsqrt_cps *)
//
end // end of [then]
else applin(k, 0) // end of [else]
fun intsqrt(n:int): int = intsqrt_cps(n, llam(x) => x)
The entirety of the code can be found at:
https://github.com/githwxi/ATS-Postiats/blob/master/doc/EXAMPLE/MISC/intsqrt_cps.dats
You can use valgrind to verify absence of memory leaks:
==28857== Memcheck, a memory error detector
==28857== Copyright (C) 2002-2015, and GNU GPL'd, by Julian Seward et al.
==28857== Using Valgrind-3.11.0 and LibVEX; rerun with -h for copyright info
==28857== Command: ./intsqrt_cps.exe
==28857==
intsqrt(1023) = 31
intsqrt(1024) = 32
==28857==
==28857== HEAP SUMMARY:
==28857== in use at exit: 0 bytes in 0 blocks
==28857== total heap usage: 14 allocs, 14 frees, 1,304 bytes allocated
==28857==
==28857== All heap blocks were freed -- no leaks are possible
==28857==
==28857== For counts of detected and suppressed errors, rerun with: -v
==28857== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 0 from 0)
链接地址: http://www.djcxy.com/p/80598.html
上一篇: C ++
下一篇: 将递归函数转换为尾递归