基于密钥的记忆
最近我一直在使用MemoCombinators和MemoTrie软件包,并试图记录一个正在采用树的函数(这是一个真正的DAG,因为几个节点被共享) 。在形式:基于密钥的记忆
data Tree k a = Branch (Tree k a) (k, a) (Tree k a) | Leaf (k, a)
所以我想memoise类型的函数(基于它的键):
Tree k a -> b
现在我有一个模糊的认识,这些memoisation组合子是用来把你的函数f :: a -> a
转化为a
的惰性(未评估)值的结构,以便在您将其拉出时已经对其进行了评估。所以这对我的树不会有什么问题 - 不知何故,我需要把它变成一个由k
索引的值的结构。
我想不出如何用combinator库做到这一点。一个简单的方法是制作一个函数k -> a
,它索引一张地图,该地图很适合,但看起来有点笨拙。
我误入了这个目标,还是错过了一些明显的东西?
我可以很容易地看到如何写这个功能了与这种风格,通过所有的计算线程明确我的“表”:
f :: Tree Int Int -> Map Int Int -> (Int, Map Int Int)
f (Branch l (k, x) r) m | (Just r) <- lookup k m = r
| otherwise = (max rl rr, m'')
where
(rl, m') = (f l m)
(rr, m'') = (f r m')
但事实并非如此漂亮。
因此,大多数memoization技术使用状态。记忆版本的功能 保持集合映射输入到记忆输出。当它得到一个输入时,它检查集合 返回memoized值(如果可用)。否则,它使用函数的原始版本 来计算输出,将输出保存在集合中,并返回新记忆的输出。 因此,memoized收集会在函数的使用期限内增长。
的Haskell memoizers等你提到避开状态,而是预先计算保持memoized输出的收集的数据结构 ,使用懒惰,以确保特定 输出的值,不计算直到需要的那些。这与有状态方法有很多共同之处,除了几个关键点:
- 由于集合是不可变的,它永远不会增长。未记录的输出每次重新计算。
- 由于集合是在使用函数之前创建的,因此它不知道将使用哪些输入 。所以记忆器必须提供一组输入,通过这些输入 来记忆。
这是相当简单的手工来实现:
module Temp where
import Prelude hiding (lookup)
import Control.Arrow ((&&&))
import Data.Map (fromList, lookup)
data Tree k a = Branch (Tree k a) (k, a) (Tree k a) | Leaf (k, a)
key :: Tree k a -> k
key (Leaf (k, _)) = k
key (Branch _ (k,_) _) = k
-- memoize a given function over the given trees
memoFor :: Ord k => [Tree k a] -> (Tree k a -> b) -> Tree k a -> b
memoFor ts f = f'
where f' t = maybe (f t) id $ lookup (key t) m
m = fromList $ map (key &&& f) ts
什么MemoCombinators和MemoTrie包尝试做的是使输入隐含的集合(使用功能 和类型类,repsectively)。如果所有可能的输入都可以枚举,那么我们可以使用该枚举来构建我们的数据结构。
在你的情况,因为你想memoize的上你的树刚key
,最简单的方法可能 是使用wrap
函数从MemoCombinators包:
包装::(一 - > b) - >(b - > a) - >备忘录a - >备忘录b
给定a和b之间的同构记忆器,为b构建记忆器。
因此,如果您key
值有相应Memo
值(比如说,type Key = Int
), ,你必须从Key
s到Tree Key Val
双射,那么你可以使用 是双射做出memoizer您Tree Key Val
功能:
memoize :: (Tree Key Val -> b) -> (Tree Key Val -> b)
memoize = wrap keyToTree treeToKey memoForKey
更新:如果你不能创造这样的映射提前,或许解决方案是使用状态monad,这样你就可以随时记忆:
{-# LANGUAGE FlexibleContexts #-}
-- ...
import Control.Monad.State (MonadState, gets, modify)
import Data.Map (Map, insert)
-- ...
memoM :: (Ord k, MonadState (Map k b) m) => (Tree k a -> m b) -> (Tree k a -> m b)
memoM f = f'
where f' t = do
let k = key t
v <- gets $ lookup k
case v of
Just b -> return b
Nothing -> do
b <- f t
modify $ insert k b
return b
-- example of use
sumM :: (Ord k, MonadState (Map k Int) m) => Tree k Int -> m Int
sumM = memoM $ \t -> case t of
Leaf (_,b) -> return b
Branch l (_,b) r -> do
lsum <- sumM l
rsum <- sumM r
return $ lsum + b + rsum
请参阅 - 这是麻烦。除非我创建外部映射(从键 - >树),否则没有简单的方法来编写keyToTree。 – Oliver 2012-02-02 05:35:21
看看你如何写memoFor - 问题是树的领域不容易找到,他们不能用空气制造。当构建表结构时,无论它是一个列表还是一个数组,它都需要是可以构建的东西的函数(或者作为memoFor的列表提供)。嗯。 – Oliver 2012-02-02 07:36:52
@Oliver:那么,您如何更新代码来使用monad?然后,你可以使用状态和缓存,因为你得到的东西。我会用一个例子来更新。 – rampion 2012-02-02 08:02:58