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)
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
memopascals r c = xs !! (r-1) !! (c-1)
where
xs = [[pascals a b | b<-[1..c]] | a<-[1..r]]
pascals 1 _ = 1
pascals _ 1 = 1
pascals x y = memopascals (x-1) y + memopascals x (y-1)
Your xs
definition is dependent on values of r
and c
, but you call your "outer" function, memopascals
, from within a nested one, pascals
. Each invocation of memopascals
has to create its own copy of xs
because it depends on memopascals
's arguments, r
and c
. 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.
memopascals r c = f r c
where
f r c = xs !! (r-1) !! (c-1)
xs = [[pascals a b | b<-[1..c]] | a<-[1..r]]
pascals 1 _ = 1
pascals _ 1 = 1
pascals x y = f (x-1) y + f x (y-1)
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 list xs
gets reused, achieving sharing.
But another call to memopascals
will create its own new copy of its internal definition of the list xs
, 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.:
memopascals :: Int -> Int -> Integer
memopascals r c = [[pascals a b | b<-[1..]] | a<-[1..]] !! (r-1) !! (c-1)
where
pascals 1 _ = 1
pascals _ 1 = 1
pascals x y = memopascals (x-1) y + memopascals x (y-1)
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).
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 call mempascals 6 6
doesn't have any connection to that partial triangle internal to memopascals 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, like
memopascals x y = fullpascals !! (x-1) !! (y-1)
In 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 use memopascals
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.
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)
Not that it matters for the error, but in the last line, you want to call zmemopascals
rather than memopascals
.
In the first line, you have two list index operators. So zipWith pascals [1 .. ] [1 .. ]
must have type [[a]]
for some a
. The definition of pascals
says
pascals :: Num t => Int -> Int -> t
thus zipWith pascals [1 .. ] [1 .. ] :: [t]
. Hence the type inference finds that t = [a]
, but the compiler doesn't find an instance Num [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
,
pascal :: Int -> Int -> Integer
pascal n k = iterate nextRow [1] !! n !! k
where
nextRow ks = zipWith (+) (0:ks) (ks ++ repeat 0)
来源:https://stackoverflow.com/questions/11473130/memoization-pascals-triangle