Data.Memocombinators没有在存在额外变量的情况下捕获案例

时间:2016-07-29 18:11:19

标签: haskell dynamic-programming

考虑以下两个功能。已包含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,尽管文档("记住函数的第二个参数")表明它应该。

2 个答案:

答案 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

所以,在这种情况下,您似乎应该使用第二种形式来保证 记忆化。