[Haskell-beginners] Dynamic Programming in Haskell
Daniel Fischer
daniel.is.fischer at web.de
Wed Jul 7 12:46:40 EDT 2010
On Wednesday 07 July 2010 18:40:07, Ali Razavi wrote:
> Thanks a lot for your through and informative response. Your solution is
> well-structured and pellucid. The only part I don't understand is in the
> definition of Parens:
>
> data Parens = Mul !Cost Dimension Parens Parens
>
> | Matrix Dimension deriving (Show)
>
> What is the exclamation mark in !Cost, and what does it signify?
It's a strictness annotation and signifies that a Mul-value must always be
constructed with an evaluated Int and not with a reference to a
computation.
>
>
> Ali
>
> > Date: Wed, 07 Jul 2010 11:30:28 +0200
> > From: Heinrich Apfelmus <apfelmus at quantentunnel.de>
> > Subject: [Haskell-beginners] Re: Dynamic Programming in Haskell
> > To: beginners at haskell.org
> > Message-ID: <i11hfk$828$1 at dough.gmane.org>
> > Content-Type: text/plain; charset=UTF-8; format=flowed
> >
> > Ali Razavi wrote:
> > > In order to practice Haskell, I decided to program some algorithms
> > > from
> >
> > the
> >
> > > CLRS book. Particularly, I tried to implement the Matrix Chain Order
> > > from Chapter 15 on Dynamic Programming.
> > > Here is my code. It seems to work, however, it looks ugly and it was
> > > a nightmare to debug. I appreciate comments about a more elegant
> > > solution,
> >
> > and
> >
> > > generally the best way to implement these kinds of algorithms in
> > > Haskell. Style improvement suggestions are also welcome.
> >
> > Dynamic programming algorithms follow a common pattern:
> >
> > * Find a suitably small collection of subproblems that can be used to
> > solve the original problem
> > * Tabulate the solutions to the subproblems, also called *memoization*
> >
> > These are two separate concerns and, unlike the prototype imperative
> > solutions, are best implemented separately.
> >
> > Thanks to lazy evaluation, memoization can be implemented very
> > elegantly in Haskell. First, it should be a higher-order functions and
> > second, you don't need to implement a particular order by which the
> > memo table is filled, lazy evaluation will figure that out for you.
> > You already know the latter trick, but here is another example:
> >
> > http://article.gmane.org/gmane.comp.lang.haskell.beginners/554
> >
> >
> > But it doesn't stop here: there are very systemic ways to tackle the
> > first part of dynamic programming, i.e. to *derive* dynamic
> > programming algorithms from just the problem specification! An example
> > and further references are given here
> >
> >
> > http://thread.gmane.org/gmane.comp.lang.haskell.cafe/42316/focus=42320
> >
> >
> > Concerning matrix chain multiplication, here is my implementation.
> > Note the use of telling names and algebraic data types; there is no
> > need to get lost in a maze of twisty little indexes, all alike.
> >
> > import Data.List
> > import Data.Array
> > import Data.Ord
> >
> > type Dimension = (Int,Int)
> > type Cost = Int
> > -- data type representing a parenthesization,
> > -- caches cost to calculate and dimension of the result matrix
> > data Parens = Mul !Cost Dimension Parens Parens
> >
> > | Matrix Dimension deriving (Show)
> >
> > -- retrieve cached vallues
> > cost :: Parens -> Cost
> > cost (Mul c _ _ _) = c
> > cost (Matrix _) = 0
> >
> > dimension :: Parens -> Dimension
> > dimension (Mul _ d _ _) = d
> > dimension (Matrix d) = d
> >
> > -- smart constructor
> > mul :: Parens -> Parens -> Parens
> > mul x y = Mul (cost x + cost y + n*m*p) (n,p) x y
> > where
> > (n,m,p) = (fst $ dimension x, snd $ dimension x,
> > snd $ dimension y)
> >
> > -- dynamic programming algorithm
> > solve :: [Int] -> Parens
> > solve matrices = chain 1 n
> > where
> > n = length matrices - 1
> > dimensions = array (1,n) . zip [1..] $
> > zip (init matrices) (tail matrices)
> >
> > chain = memoize n chain'
> > chain' i j
> >
> > | i == j = Matrix (dimensions ! i)
> > | otherwise = best [mul (chain i k) (chain (k+1) j)
> > |
> > | k <- [i..j-1] ]
> >
> > best = minimumBy (comparing cost)
> >
> > -- memoize a function on a "square" [1..n] x [1..n]
> > memoize :: Int -> (Int -> Int -> a) -> (Int -> Int -> a)
> > memoize n f = \i j -> table ! (i,j)
> > where
> > table = array ((1,1),(n,n)) $
> > [((i,j), f i j) | i <- [1..n], j <- [1..n]]
> >
> > Example output:
> >
> > *Main> cost $ solve [10,100,5,50,1]
> > 1750
> >
> > I didn't need to debug this code, because it's obviously correct. Put
> > differently, instead of spending my effort on debugging, I have spent
> > it on making the solution elegant.
> >
> >
> > Regards,
> > Heinrich Apfelmus
More information about the Beginners
mailing list