动态规划(Haskell,Hofstader M/F序列)

动态规划(Haskell,Hofstader M/F序列),haskell,recursion,memoization,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 go

这项工作:

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]]
f::Int->Int
fn=gofn,其中
gof 0=1
gof i=i-ms!!(fs!!(i-1))
gom 0=0
gomi=i-fs!!(ms!!(i-1))

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

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..]
你会注意到还有另外一个技巧。我没有将列表
fs
ms
限制到它们的最大大小,而是让懒惰来限制它们。列表不会创建到需要记忆早期结果的地方

但是列表索引是O(n)。即使去掉其中的一部分,也会大大加快速度。如果你观察同一函数的递归模式,你会发现
gomi
总是调用
gom(i-1)
,gof也一样。您可以通过传递上一个值来删除这些查找上的列表索引。不幸的是,对相反函数的调用不适用,因为它们不容易遵循。但这仍然会删除大量工作。可以通过utiliz这样的方式来完成懒惰更进一步:

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)

递归助手函数已被同时延迟创建两个结果列表所取代。此模式不同于标准递归,因为它不需要基本情况即可到达,并且需要某种防护措施,以防止在提供完整答案之前立即找到基本情况。此模式称为协同递归(如果我懒洋洋地键入,则为协同递归)相同的想法,但它产生了相反的结果。

或者您可以使用支持交互递归函数的多个包中的一个。下面是使用的实现,它确实需要以一元形式定义记忆函数,但除此之外,它只是原始实现的直接翻译

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))
import Control.Monad.Memo
进口管制站
--一元形式的相同函数
gof 0=返回1
gof i=do
--gof在级别0上被记忆
fs Int
--我们使用两个独立的缓存:长度为`(n+1)`的可变未固定向量
fmv n=runST$(`evaluvectormo`(n+1))。(`evaluvectormo`(n+1)).gof$n
mmv::Int->Int
mmv n=runST$(`evaluvectormo`(n+1))。(`evaluvectormo`(n+1)).gom$n
--与原始解决方案相比,这相当快
--但是用-O2编译它,就可以计算出'f 1000000`
main::IO()
主要=
打印((调频100000,毫米100000),(调频1000000,毫米1000000))

<代码> >你当然可以共享<代码> M>代码>和<代码> F 之间的所有子定义。但是这对序列看起来可以做得更好,并且可以生成它们。请注意,列表上的<代码>!<代码>是无效的,因为它具有O(n)成本。如果无法避免索引,请考虑使用O(1)。数据结构,例如数组。递归深度也不应该是一个问题,除非它真的很大——你为什么提到它?(你没有使用古老的拥抱,对吗?)