在Haskell中记住项目Euler 116

时间:2015-02-15 22:00:59

标签: haskell dynamic-programming

以下是我对此问题的第一次尝试。它基本上是我的C ++解决方案的音译,减去用于记忆函数f的结果的全局哈希表。

f :: Int -> Int -> Int
f blocksize spaces
    | blocksize > spaces = 0
    | blocksize == spaces = 1
    | otherwise = 1 + fIter blocksize (spaces - blocksize)

fIter :: Int -> Int -> Int
fIter blocksize spaces =
    sum $ map (f blocksize) [1..spaces]

main :: IO ()
main = do
    let spaces = 50
    print $ fIter 2 spaces + fIter 3 spaces + fIter 4 spaces

我认为我理解创建一个必要时填充的惰性数据结构的基本思想,如this type of thing,但是我在这里抛出的是有两个相互递归的函数相互调用不止一个。我已经尝试将它们组合成一种形式,如下所示,但还没有任何运气直接在我的头脑中。有什么提示吗?

fIter2 :: Int -> Int -> Int
fIter2 b s = sum $ map (\i -> memos ! i) [1..s]
    where
        memos = listArray (1, s) (map (f b) [1..s])
        f blocksize spaces
          | blocksize > spaces = 0
          | blocksize == spaces = 1
          | otherwise = 1 + fIter2 blocksize (spaces - blocksize)

(我知道这个问题有一个组合解决方案,但我现在更感兴趣的是在Haskell中更好地进行动态编程。)

1 个答案:

答案 0 :(得分:4)

f是一个非常简单的功能。假设fIter易于计算,f也很容易计算。我们不需要记住f。我们唯一的目标是记住fIter

当开始在Haskell中记忆递归函数时,以开放递归形式编写函数是有用的。函数的开放递归形式删除显式递归,而是为函数需要递归调用自身时添加一个额外的参数。这将为我们提供一种方法来改变函数递归调用自身的方式。

我们会将fIter的签名从第一种类型更改为第二种

                      The type of fIter
                       v
                       Int -> Int -> Int
(Int -> Int -> Int) -> Int -> Int -> Int
 ^                     ^
 |                     Gets back something with the type of fIter
 What to do when fIter needs to call itself recursively

fIter通过f递归调用自己,因此我们首先添加(Int -> Int -> Int)参数,了解如何将fIter调用到f

f' :: (Int -> Int -> Int) -> Int -> Int -> Int
f' r blocksize spaces
    | blocksize > spaces = 0
    | blocksize == spaces = 1
    | otherwise = 1 + r blocksize (spaces - blocksize)

修改后的fIter'将传递如何递归调用自身(它来自新参数)作为f'的新参数。

fIter' :: (Int -> Int -> Int) -> Int -> Int -> Int
fIter' r blocksize spaces =
    sum $ map (f' r blocksize) [1..spaces]

我们可以通过将fIter中定义的fix :: (a -> a) -> a修改为fix f = let x = f x in x来恢复原来的慢fIter :: Int -> Int -> Int fIter = fix fIter'

fIter

现在我们处于改变Data.MemoTrie自称的方式的好地方。另一个"懒惰的数据结构,必要时填写"在Data.Function包中的trie :: HasTrie a => (a -> b) -> a :->: b中提供。它提供了两个我们感兴趣的功能。 untrie :: HasTrie a => (a :->: b) -> a -> b构建了一个由提供的函数构建的惰性数据结构。 HasTrie a返回一个从惰性数据结构中查找参数的函数。这些只适用于可以构建惰性数据结构的某些参数类型HasTrie Int。幸运的是,对于我们的问题,已经存在(HasTrie a, HasTrie b) => HasTrie (a, b)trie的实例。

来自Data.MemoTrie

fIter只接受一个参数,因此我们需要将fIter'的两个参数打包到一个参数中。我们通常使用MemoTrie执行此操作,但是,由于uncurryOr需要采用与其结果相同的类型作为参数,我们还需要uncurry递归调用解包参数。 uncurryOR :: (( a -> b -> c) -> a -> b -> c) -> ((a, b) -> c) -> (a, b) -> c uncurryOR or = uncurry . or . curry 将取消任何打开的递归函数。

fIter'

将此应用于uncurryOR fIter' :: ((Int, Int) -> Int) -> (Int, Int) -> Int会产生令人满意的结果,import Data.MemoTrie memoOR :: HasTrie a => ((a -> b) -> a -> b) -> a -> b memoOR or = f where f = untrie t t = trie (or f)

为了记住一般的开放递归函数,我们为开放递归函数构建一个trie,传递trie查找函数应该如何得到它的递归结果。

memoOR

我们可以从fix memo = untrie . trieData.MemoTrie更优雅地撰写memoOR or = fix (memo . or)

fIter

我们现在可以定义如何有效地计算fIter :: Int -> Int -> Int fIter = curry . memoOR . uncurryOR $ fIter'

{{1}}