[Haskell-cafe] Memoization in Haskell?
Edward Kmett
ekmett at gmail.com
Fri Jul 9 04:39:23 EDT 2010
On Thu, Jul 8, 2010 at 5:30 PM, Angel de Vicente <angelv at iac.es> wrote:
> Hi,
>
> I'm going through the first chapters of the Real World Haskell book,
> so I'm still a complete newbie, but today I was hoping I could solve
> the following function in Haskell, for large numbers (n > 108)
>
> f(n) = max(n,f(n/2)+f(n/3)+f(n/4))
>
> I've seen examples of memoization in Haskell to solve fibonacci
> numbers, which involved computing (lazily) all the fibonacci numbers
> up to the required n. But in this case, for a given n, we only need to
> compute very few intermediate results.
>
> How could one go about solving this efficiently with Haskell?
>
> We can do this very efficiently by making a structure that we can index in
sub-linear time.
But first,
> {-# LANGUAGE BangPatterns #-}
> import Data.Function (fix)
Lets define f, but make it use 'open recursion' rather than call itself
directly.
> f :: (Int -> Int) -> Int -> Int
> f mf 0 = 0
> f mf n = max n $ mf (div n 2) +
> mf (div n 3) +
> mf (div n 4)
You can get an unmemoized f by using `fix f`
This will let you test that f does what you mean for small values of f by
calling, for example: `fix f 123` = 144
We could memoize this by defining:
> f_list :: [Int]
> f_list = map (f faster_f) [0..]
> faster_f :: Int -> Int
> faster_f n = f_list !! n
That performs passably well, and replaces what was going to take O(n^3) time
with something that memoizes the intermediate results.
But it still takes linear time just to index to find the memoized answer for
`mf`. This means that results like:
*Main Data.List> faster_f 123801
248604
are tolerable, but the result doesn't scale much better than that. We can do
better!
First lets define an infinite tree:
> data Tree a = Tree (Tree a) a (Tree a)
> instance Functor Tree where
> fmap f (Tree l m r) = Tree (fmap f l) (f m) (fmap f r)
And then we'll define a way to index into it, so we can find a node with
index n in O(log n) time instead:
> index :: Tree a -> Int -> a
> index (Tree _ m _) 0 = m
> index (Tree l _ r) n = case (n - 1) `divMod` 2 of
> (q,0) -> index l q
> (q,1) -> index r q
... and we may find a tree full of natural numbers to be convenient so we
don't have to fiddle around with those indices:
> nats :: Tree Int
> nats = go 0 1
> where
> go !n !s = Tree (go l s') n (go r s')
> where
> l = n + s
> r = l + s
> s' = s * 2
Since we can index, you can just convert a tree into a list:
> toList :: Tree a -> [a]
> toList as = map (index as) [0..]
You can check the work so far by verifying that `toList nats` gives you
[0..]
Now,
> f_tree :: Tree Int
> f_tree = fmap (f fastest_f) nats
> fastest_f :: Int -> Int
> fastest_f = index f_tree
works just like with list above, but instead of taking linear time to find
each node, can chase it down in logarithmic time.
The result is considerably faster:
*Main> fastest_f 12380192300
67652175206
*Main> fastest_f 12793129379123
120695231674999
In fact it is so much faster that you can go through and replace Int with
Integer above and get ridiculously large answers almost instantaneously
*Main> fastest_f' 1230891823091823018203123
93721573993600178112200489
*Main> fastest_f' 12308918230918230182031231231293810923
11097012733777002208302545289166620866358
-Edward Kmett
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://www.haskell.org/pipermail/haskell-cafe/attachments/20100709/5910bd29/attachment.html
More information about the Haskell-Cafe
mailing list