动态编程(Haskell,Hofstader M / F序列)

时间:2018-08-30 00:16:08

标签: haskell recursion memoization

这有效:

f :: Int -> Int
f n = gof n where
      gof 0 = 1
      gof i = i - ms!! ( fs!! (i-1) )
      gom 0 = 0
      gom i = i - fs!! ( ms!! (i-1) )
      fs = [gof j | j <- [0..n]]
      ms = [gom j | j <- [0..n]]

m n = gom n where
      gof 0 = 1
      gof i = i - ms!! ( fs!! (i-1) )
      gom 0 = 0
      gom i = i - fs!! ( ms!! (i-1) )
      fs = [gof j | j <- [0..n]]
      ms = [gom j | j <- [0..n]]

但是它确实是重复的。有没有办法避免只重复那些代码?一些参考,这是对:

的改编

http://jelv.is/blog/Lazy-Dynamic-Programming/

序列号:

https://en.wikipedia.org/wiki/Hofstadter_sequence

我对照数字进行了检查:

https://oeis.org/A005378 https://oeis.org/A005379

它生成正确的数字,并且比基本代码要快得多,而基本代码在开始出现递归深度问题之前根本不会很高。

2 个答案:

答案 0 :(得分:3)

首先,您可以在顶级绑定中进行模式匹配。通常,这并不意味着会发生很多有趣的事情,但是如果您想在两个顶级绑定之间共享本地帮助程序,它会有所帮助。

m2 :: Int -> Int
f2 :: Int -> Int
(m2, f2) = (gom, gof)
  where
    gof 0 = 1
    gof i = i - ms !! ( fs !! (i-1) )
    gom 0 = 0
    gom i = i - fs !! ( ms !! (i-1) )
    fs = map gof [0..]
    ms = map gom [0..]

您会注意到那里还有另一个技巧。不用将列表fsms限制到最大大小,我只是让懒惰处理它们。这些列表不会在需要记住这些结果的地方创建。

但是列表索引是O(n)。摆脱其中的一些可能会大大提高速度。如果您沿着相同的函数查看递归的模式,您会发现gom i总是调用gom (i-1),而gof总是这样。您可以通过仅传递前一个值来删除这些查找的列表索引。不幸的是,对相反函数的调用不适用相同的方法,因为它们不那么容易遵循。但是它仍然在减少大量工作。可以通过进一步利用惰性来实现:

m3, f3 :: Int -> Int
(m3, f3) = ((ms !!), (fs !!))
  where
    (ms, fs) = unzip pairs
    pairs = (0, 1) : zipWith iter [1..] pairs
    iter i (mp, fp) = (i - fs !! mp, i - ms !! fp)

递归帮助器功能已被同时延迟创建两个结果列表所取代。这种模式与标准递归不同,它不需要达到基本情况,并且需要采取某种防范措施,以防止在提供完整答案之前立即找到基本情况。这种模式称为共同递归。 (或者如果我懒惰地键入,则为corecursion。)相同的想法,但是它会产生相反的答案。

答案 1 :(得分:3)

或者,您可以仅使用支持相互递归功能的许多memoization软件包之一。以下是使用monad-memo的实现,该实现确实要求以单声道形式定义记忆功能,否则,这仅仅是原始实现的直接翻译。

import Control.Monad.Memo
import Control.Monad.ST

-- Same function in monadic form
gof 0 = return 1
gof i = do
  -- gof is memoized on level 0
  fs <- memol0 gof (i-1)
  -- gom is on level 1
  ms <- memol1 gom fs
  return (i - ms)

-- Same here
gom 0 = return 0
gom i = do
  ms <- memol1 gom (i-1)
  fs <- memol0 gof ms
  return (i - fs)

-- Eval monadic form into normal Int -> Int function
fm :: Int -> Int
-- Data.Map-based memoization cache
fm = startEvalMemo . startEvalMemoT . gof

mm :: Int -> Int
mm = startEvalMemo . startEvalMemoT . gom   

-- Or much faster vector-based memoization cashe
fmv :: Int -> Int
-- We use two separate caches: mutable unboxed vectors of `(n+1)` length
fmv n = runST $ (`evalUVectorMemo`(n+1)) . (`evalUVectorMemo`(n+1)) . gof $ n

mmv :: Int -> Int
mmv n = runST $ (`evalUVectorMemo`(n+1)) . (`evalUVectorMemo`(n+1)) . gom $ n

-- This is quite fast in comparison to the original solution
-- but compile it with -O2 to be able to compute `f 1000000`
main :: IO ()
main =
    print ((fm 100000, mm 100000),(fmv 1000000, mmv 1000000))