Reputation: 811
Here is non tail recursive function
alg :: Int -> Int
alg n = if n<7 then n else alg(n-1) * alg(n-2) * alg(n-4) * alg(n-6)
I've been stuck on this for a while, I get the basic idea of tail recursion, and how to do it for single call recursive function, but no clue how to do it for multi call one.
Even came up with this abomination
algT :: Int -> Int
algT n = tail1 n 0 where tail1 i r = tail1(i-1) r *
tail2 n 0 where tail2 i r = tail2(i-2) r *
tail3 n 0 where tail3 i r = tail3(i-4) r *
tail4 n 0 where tail4 i r = tail4(i-6) r
It doesnt work and obviously not how recursive function should look, had few other attempts, but all of them ended in infinite 100% cpu load loop...
Upvotes: 2
Views: 202
Reputation: 19221
Have you looked into Fibonacci in Haskell? It is a similar type of function. BTW tail recursion isn't quite the right term in Haskell, as multi-recursion functions can't really be done recursively but Haskell's lazy nature makes a similar but more powerful trick possible. Here is the standard one given:
fibs = 0 : 1 : zipWith (+) fibs (tail fibs)
Using the same trick on yours gives EDIT: As a function
alg :: Int -> Int
alg n = alg' !! (n - 1)
where alg' = 1 : 2 : 3 : 4 : 5 : 6 : zipWith4 (\a b c d -> a * b * c * d) (drop 5 alg') (drop 4 alg') (drop 2 alg') alg'
Note that you shouldn't use Int
here, that isn't open ended and the 11th term will loop in an Int
.
EDIT: Actually Int
is even worse than I thought. Once you hit 32 2's in your result you will start returning 0 since every answer is 0 mod 2^32.
Upvotes: 3
Reputation: 3946
Here is a possible solution.
let f = [1..6] ++ foldr1 (zipWith (*)) [f, drop 2 f, drop 4 f, drop 5 f]
or even:
let f = [1..6] ++ foldr1 (zipWith (*)) (map (flip drop $ f) [0,2,4,5])
Upvotes: 0
Reputation:
From your question it's not entirely clear what is the purpose of making your function tail-recusrive. If you are trying to reduce cpu/memory usage, then you should use memoization (mentioned in the Guvante's answer).
Meanwhile, there is a way to make almost any function tail-recursive, known as continuation-passing style. Your example written in the CPS looks like this:
alg_cps :: Integer -> (Integer->a) -> a
alg_cps n cont =
if n < 7
then cont n
else alg_cps (n - 1)
(\x1 -> alg_cps (n - 2)
(\x2 -> alg_cps (n - 4)
(\x3 -> alg_cps (n - 6)
(\x4 -> cont (x1*x2*x3*x4)))))
And to directly get the result you can call it with id
as continuation:
alg_cps 20 id
Notice that this does not reduce algorithm complexity or memory usage compared to naive non-tail recursive implementation.
Upvotes: 2
Reputation: 1169
I think I have a solution, but it's not very elegant or pretty.
alg :: Int -> Int
alg n | n < 7 -> n
| otherwise -> alg' n (repeat 0)
alg' :: Int -> [Int] -> Int
alg' n [] = error "something has gone horribly wrong"
alg' n l@(x:y)
| n < 5 -> error "something else has gone horribly wrong"
| n == 6 -> product $ zipWith (^) [6,5..1] l
| otherwise -> alg' (n-1) $ zipWith (+) [x,x,0,x,0,x] (y ++ [0])
The idea is that you can keep track of how many times you're supposed to be multiplying each thing without actually doing any of the calculations until the very end. At any given time, you have information about how many times you've needed any of the next 6 values, and once you're below 7, you just raise 1-6 to the proper powers and take their product.
(I haven't actually tested this, but it seems right. And even if it's not I'm pretty sure the idea behind it is sound)
P.S. As @Guvante says, Int
isn't a good choice here as it will quickly overflow. As a general rule I use Integer
by default and only switch if I have a good reason.
Upvotes: 0