I'm not interested in the actual solution or other methods solving the problem, it's the memoization i need help with :)
I need help doing a pascals triangle problem with memoization. I want to get the middle number in the base of the triangle. (Project Euler 15)
The first example is not memoized (although the name suggests so) "20 20" not solvable
Second attempt is an attempt on doing something similar to: http://www.haskell.org/haskellwiki/Memoization
Third is hlints suggestion on no2 if that is more readable for someone.
I get this error, but i'm not sure its right even if it would compile... (run from ghci with 2 2 as params
no instance for (Num [a0])
arising from a use of `zmemopascals'
Possible fix: add an instance declaration for (Num [a0])
In the expression: zmemopascals 2 2
In an equation for `it': it = zmemopascals 2 2
.
Code:
--1
memopascals r c = [[pascals a b | b<-[1..c]] | a<-[1..r]] !! (r-1) !! (c-1)
where pascals 1 _ = 1
pascals _ 1 = 1
pascals x y = memopascals (x-1) y + memopascals x (y-1)
--2
--xmemopascals :: Int -> Int -> Int
xmemopascals r c = map (uncurry pascals) (zip [1..] [1..]) !! (r-1) !! (c-1)
where pascals 1 _ = 1
pascals _ 1 = 1
pascals x y = memopascals (x-1) y + memopascals x (y-1)
--3
zmemopascals r c = zipWith pascals [1 ..] [1 ..] !! (r-1) !! (c-1)
where pascals 1 _ = 1
pascals _ 1 = 1
pascals x y = memopascals (x-1) y + memopascals x (y-1)
Memoization doesn't work in your functions because a call like
memopascals 5 5
builds internally a part of the triangle and returns a single value from it. A different callmempascals 6 6
doesn't have any connection to that partial triangle internal tomemopascals 5 5
.For efficient memoization you want the common part (like the list of lists that represents the calculated numbers in the triangle) in a separate function, which is then used by the functions that access specific indexes. This way you use the same list of lists to lookup different indexes.
Usually the easiest way to do this is to write one function like
fullpascals :: [[Int]]
that produces the complete (infinite) data structure, and than another function to access that data structure, likeIn your case
fullpascals
would be the same as one of your current functions, but without the parameters for a specific index. It can even still usememopascals
internally for the recursion.Sidenote: In the
memoized_fib
example in the wiki, the "common part" that is memoized isn't directly the list of all fibs, but a function that accesses the list of all fibs. The effect is the same, since the compiler is smart enough to optimize this.Not that it matters for the error, but in the last line, you want to call
zmemopascals
rather thanmemopascals
.In the first line, you have two list index operators. So
zipWith pascals [1 .. ] [1 .. ]
must have type[[a]]
for somea
. The definition ofpascals
saysthus
zipWith pascals [1 .. ] [1 .. ] :: [t]
. Hence the type inference finds thatt = [a]
, but the compiler doesn't find an instanceNum [a]
.For memoisation, you have to give a name to the full triangle, like @sth suggested, to avoid recomputation (the Fibonacci memoisation only works because the compiler is smart enough, it's not guaranteed by its form).
Another option would be to construct the triangle using
iterate
,There are several guidelines to achieving memoization (take a look here for some recent discussion).
First, use -O2 optimization flag with GHC compiler. Second, use monomorphic type signature. Name your intermediate list(s), that you want to achieve sharing of.
Then, pay attention to your nested definitions. If a nested definition depends on value of an argument in its enclosing ("outer") scope, that means each call to that outer function will have to create all its nested definitions anew, so there won't be any one list to be shared, but many separate independent ones.
Here in your function, separating out and naming the list expression that you want shared, we get
Your
xs
definition is dependent on values ofr
andc
, but you call your "outer" function,memopascals
, from within a nested one,pascals
. Each invocation ofmemopascals
has to create its own copy ofxs
because it depends onmemopascals
's arguments,r
andc
. No sharing possible.If you need to have that dependent definition, you must arrange not to call "out of scope", but rather stay inside that scope, so that all the internal ("nested") definitions can be reused.
Now each invocation of
memopascals
creates its internal definitions (from its nested scope) which then call each other, never calling out of scope - so the listxs
gets reused, achieving sharing.But another call to
memopascals
will create its own new copy of its internal definition of the listxs
, and will use it. We can call this a "local" sharing, not a "global" sharing (i.e. memoization). That means it runs fast, but second call with same parameters takes the same time as the first - not the 0 time as with full memoization.There's only one way to achieve that, and that is to make your
xs
definition independent. Then the compiler can smash all the nested scope frames together, perform lambda lifting turning nested closures into plain lambdas, etc. etc.:With -O2 switch, GHC performs full memoization even for this version. As long as we don't forget the monomorphic type signature (or it's local sharing again).