The problem with recursion is that you need to be careful about the exponential branch or have exponential memory-printing; and also a record of a tail recursive function is usually less expressive.
You can get around the whole load of recursion by dynamically programming; which has a very efficient implementation in Haskell using the right fold:
count :: (Num a, Foldable t) => t Int -> Int -> a count coins total = foldr go (1: repeat 0) coins !! total where go coin acc = out where out = zipWith (+) acc $ replicate coin 0 ++ out
then
\> count [1, 5, 10, 25, 50] 5000 432699251
or as in the 31st problem of Project Euler (1) :
\> count [1, 2, 5, 10, 20, 50, 100, 200] 200 73682
A less effective alternative would be to use immutable non-strict (boxed) arrays:
import Data.Array (listArray, (!)) count :: (Num a, Foldable t) => t Int -> Int -> a count coins total = foldr go init coins ! total where init = listArray (0, total) $ 1: repeat 0 go coin arr = out where out = listArray (0, total) $ map inc [0..total] inc i = arr ! i + if i < coin then 0 else out ! (i - coin)
(1) The problem has already been sent elsewhere in stackoverflow; see Using dynamic programming in Haskell? [Warning: ProjectEuler 31 solution inside]
source share