记忆和& Haskell中的项目欧拉问题15

时间:2010-07-31 21:11:26

标签: haskell memoization

我一直在学习一些Haskell,并且在我去的时候做项目Euler问题。对于欧拉问题的答案(我可以高兴地蛮力,或者用其他语言做),我并不是真的很烦恼,但这种方法。

我在合理的时间内坚持做问题15。它要求从20x20网格的左上角到右下角的非回溯路线的数量。 Link here

我会说这个想法很容易记住(sp?)这个功能,但我并不确定如何去做。我用谷歌搜索,并在Haskell Cafe上遇到this,我天真地试图适应,但结果是:

memRoute :: (Int,Int) -> Int
memRoute (x,y) = fromJust $ lookup (x,y) $ map (\t -> (t,route t)) [(x,y) | x <- [0..20], y <- [0..20]]

route (0,0) = 0
route (_,0) = 1
route (0,_) = 1
route (x,y) = memRoute (x-1,y) + memRoute (x,y-1)

哪个看起来很糟糕,并且表现得非常糟糕(比天真版本慢很多)。问题是我真的不明白Haskell的memoization是如何工作的。

以我的代码为例,任何人都可以解释一下为什么我的代码太慢了  b)应该怎么做(不使用mutables,这是我遇到的解决方案)
c)在这种情况下,memoization如何工作?

3 个答案:

答案 0 :(得分:10)

在我看来,“备忘录”被高估了。没有 一种通用的记忆技术(在任何编程中 语言)将每个单例计算转换为a 一般计算。你必须了解每个问题, 并组织事物来控制你需要的内存量 使用。

在这种情况下,要获取n x m矩形的路径数, 你不需要记住所有较小矩形的总数, 只是对于在任一方向上都小一步的矩形。 所以你可以逐行建立,忘记所有的总计 你去的那个小矩形。

在哈斯克尔,懒惰对于这种情况是完美的。它松了一口气 你所做的一切工作都是为了记住要记住的东西 什么忘记 - 只需计算总数的无限网格 所有可能的矩形,让Haskell完成其余的工作。

对于零行,您只有底线。无论多久, 到最后只有一条路径,所以路径的数量是 repeat 1

要计算上一行网格中的行,请从1开始 (无论你有多高,只有一条路径向下), 然后在每个步骤中将相应的条目添加到 当前行中的上一行和上一步。总而言之,我们有:

iterate (scanl (+) 1 . tail) (repeat 1) !! 20 !! 20

在GHCi提示下,我立刻为我计算答案。

答案 1 :(得分:5)

抛出几个trace

memRoute :: (Int,Int) -> Int
memRoute (x,y) = trace ("mem: " ++ show (x,y))
                 fromJust $
                 lookup (x,y) $
                 map (\t -> (t,route t))
                 [(x,y) | x <- [0..20], y <- [0..20]]

route (0,0) = 0
route (_,0) = 1
route (0,_) = 1
route (x,y) = trace ("route: " ++ show (x,y))
              memRoute (x-1,y) + memRoute (x,y-1)

看到你根本没有记忆。

ghci> memRoute (2,2)
mem: (2,2)
route: (2,2)
mem: (1,2)
route: (1,2)
mem: (0,2)
mem: (1,1)
route: (1,1)
mem: (0,1)
mem: (1,0)
mem: (2,1)
route: (2,1)
mem: (1,1)
route: (1,1)
mem: (0,1)
mem: (1,0)
mem: (2,0)
6

重复使用子计算是dynamic programming

import Data.Array

routes :: (Int,Int) -> Int
routes = (rte !)
  where rte = array ((1,1), (n,n))
                    [ ((x,y), route (x,y)) | x <- [1 .. n],
                                             y <- [1 .. n] ]
        route (1,1) = 0
        route (_,1) = 1
        route (1,_) = 1
        route (x,y) = rte ! (x-1,y) + rte ! (x,y-1)
        n = 20

请注意,该算法不正确,但至少很快就会得到错误的答案!

答案 2 :(得分:0)

每次调用memRoute时都会重新计算您的memoization表。不幸的是(幸运的是!)但至少在一次通话中完成的工作与其他人没有共享。我能想到的最简单的重写是:

memRoute = let table = map (\t -> (t,route t)) [(x,y) | x <- [0..20], y <- [0..20]]
           in  \(x,y) -> fromJust $ lookup (x,y) $ table

这与你表达的意图非常接近,但我认为有更好的方法可以通过使用Data.Map并让实际的调用模式确定记忆的值来进行记忆。