【发布时间】:2023-03-22 17:23:01
【问题描述】:
我是 Haskell 的新手,我正在尝试实现一个基本的记忆功能,它使用 Data.Map 来存储计算值。我的示例是针对 Project Euler 问题 15,其中涉及计算 20x20 网格中从一个角到另一个角的可能路径数。
这是我目前所拥有的。我还没有尝试编译,因为我知道它不会编译。我会在下面解释。
import qualified Data.Map as Map
main = print getProblem15Value
getProblem15Value :: Integer
getProblem15Value = getNumberOfPaths 20 20
getNumberOfPaths :: Integer -> Integer -> Integer
getNumberOfPaths x y = memoize getNumberOfPaths' (x,y)
where getNumberOfPaths' mem (0,_) = 1
getNumberOfPaths' mem (_,0) = 1
getNumberOfPaths' mem (x,y) = (mem (x-1,y)) + (mem (x,y-1))
memoize :: ((a -> b) -> a -> b) -> a -> b
memoize func x = fst $ memoize' Map.Empty func x
where memoize' map func' x' = case (Map.lookup x' map) of (Just y) -> (y, map)
Nothing -> (y', map'')
where y' = func' mem x'
mem x'' = y''
(y'', map') = memoize' map func' x''
map'' = Map.insert x' y' map'
所以基本上,我的这种结构方式是memoize 是一个组合器(据我所知)。 memoization 之所以有效,是因为memoize 提供了一个函数(在本例中为getNumberOfPaths'),该函数具有一个调用(mem)进行递归的函数,而不是让getNumberOfPaths' 调用自身,这将在第一次迭代后删除memoization。
我的memoize 实现采用一个函数(在本例中为getNumberOfPaths')和一个初始值(在本例中为一个元组(x,y),表示距网格另一个角的网格单元距离的数量)。它调用具有相同结构的memoize',但包含一个空的Map 来保存值,并返回一个包含返回值的元组和一个新计算的Map。 memoize' 进行地图查找,如果存在值,则返回该值和原始地图。如果不存在值,则返回计算值和新映射。
这就是我的算法崩溃的地方。为了计算新值,我用mem 和x' 调用func' (getNumberOfPaths')。 mem 只返回y'',其中y'' 包含在再次调用memoize' 的结果中。 memoize' 还返回一个新映射,然后我们将新值添加到该映射并用作 memoize' 的返回值。
这里的问题是(y'', map') = memoize' map func' x'' 行应该在mem 之下,因为它依赖于x'',这是mem 的一个参数。我当然可以这样做,但是我会丢失 map' 值,这是我需要的,因为它包含来自中间计算的记忆值。但是,我不想将Map 引入mem 的返回值,因为传递给memoize 的函数将不得不处理Map。
对不起,如果这听起来令人困惑。很多这种超高阶函数的东西让我感到困惑。
我确信有办法做到这一点。我想要的是一个通用的memoize 函数,它允许递归调用与getNumberOfPaths 的定义完全相同,其中计算逻辑不必关心记忆是如何完成的。
【问题讨论】:
-
我猜你可以考虑使用map based memo monad。
-
虽然这是一个很好的解决方案,但对于 Haskell 和函数式编程的新手来说,如果没有太多解释,它不太可能有帮助。
-
顺便说一句,有一个非常更有效的方法可以解决这个问题,这将完全消除对记忆的需要。如此高效,您可能只需几分钟就可以用铅笔和纸手工完成。
-
@Daniel 我确定这是真的,但我仍然希望将来有一个通用的记忆功能。
标签: haskell memoization