考虑以下两个功能。已包含traceShow
以显示DP缓存命中或未命中。第一个是从MemoCombinators documentation偷猎的。我自己构建了第二个。
import Data.MemoCombinators as Memo
import Debug.Trace
fib :: Int -> Int
fib = Memo.integral fib'
where
fib' :: Int -> Int
fib' 0 = traceShow 0 $ 0
fib' 1 = traceShow 1 $ 1
fib' n = traceShow n $ fib (n-1) + fib (n-2)
brokenFib :: a -> Int -> Int
brokenFib a = Memo.integral brokenFib'
where
brokenFib' :: Int -> Int
brokenFib' 0 = traceShow 0 $ 0
brokenFib' 1 = traceShow 1 $ 1
brokenFib' n = traceShow n $ brokenFib [] (n-1) + brokenFib [] (n-2)
fib
利用了DP,但是brokenFib
没有,这意味着额外的变量必须以某种方式弄乱它。构建一个你只希望DP只有两个参数函数的一个参数的场景并不难,但是如果不知道额外的变量是如何搞乱的话就无法做到这一点。 brokenFib
。有什么建议吗?
编辑:
@ user6655594给出的第二个解决方案的实现:
brokenFib :: a -> Int -> Int
brokenFib = Memo.memoSecond Memo.integral brokenFib'
where
brokenFib' :: a -> Int -> Int
brokenFib' _ 1 = traceShow 1 $ 1
brokenFib' _ 2 = traceShow 1 $ 1
brokenFib' _ n = traceShow n $ (brokenFib [] (n-1)) + brokenFib [] (n-2)
它也没有捕获DP,尽管文档("记住函数的第二个参数")表明它应该。
答案 0 :(得分:3)
不同之处在于fib
是一个不会改变的值 - 它是一个记忆函数,并且在调用之间共享了记忆值。
另一方面,brokenFib
是一个函数,对于每个类型为a
的值的调用,都会创建一个新的memoized函数,该函数不会与其他函数共享记忆值。
你有几种选择(我没有测试过任何一种选择,而且我对包装不熟悉):
使用memoSecond
来记忆第二个参数,例如
brokenFib = memoizeSecond integral brokenFib'
...
尽管文档似乎没有描述第一个论点是如何处理的。
如果可能,请使用memo2
记住两个参数。
如果整个调用中类型a
的第一个参数相同,则可以使用
brokenFib a = go
where
go = integral go'
go' = ... -- and calls 'go', not 'brokenFib' for recursive calls!
答案 1 :(得分:1)
嗯,事实证明,三年前在#haskell上讨论了这个问题。 看看这些聊天事件:
根据int-e的评论,似乎memoSecond integral f
本身并非如此
那很有用。我认为它的意思是使用,所以你可以指定如何
第二个论点应该被记住。换句话说,你应该总是记住
在两个函数参数上都提供了memoSecond
,因此您可以指定一个
记忆第二个论点的策略。因此,int-e提出了一些建议
像:
integral (memoSecond integral f)
当然,每个integral
可以用不同的memoization替换
策略。
此外,实际发生的事情取决于你如何编写函数 并且当你使用ghci运行它或者用ghc和它编译它时 您使用的优化级别。
考虑这个程序:
import qualified
Data.MemoCombinators as Memo
import Debug.Trace
afib :: Char -> Int -> Int
afib _ = bfib
where
bfib = Memo.integral cfib
cfib n | trace msg False = undefined
where msg = "cfib at " ++ show n
cfib 1 = 1
cfib 2 = 2
cfib n = bfib (n-1) + bfib (n-2)
main = do print (afib 'x' 5); print (afib 'x' 6)
以及第afib _ = bfib
行更改为afib = \_ -> bfib
的第二个版本(所有其他行都相同。)
afib是否被记忆可能取决于你如何执行/编译它:
afib _ = bfib | afib = \_ -> bfib
---------------|-------------------
ghci NOT MEMOIZED | MEMOIZED
runhaskell NOT MEMOIZED | MEMOIZED
ghc NOT MEMOIZED | MEMOIZED
ghc -O2 MEMOIZED | MEMOIZED
所以,在这种情况下,您似乎应该使用第二种形式来保证 记忆化。