我一直在学习一些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如何工作?
答案 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
并让实际的调用模式确定记忆的值来进行记忆。