如何在惯用的Haskell中实现动态编程算法?

时间:2011-02-12 07:25:00

标签: haskell functional-programming dynamic-programming

Haskell和其他函数式编程语言是在不维护状态的前提下构建的。我还不熟悉函数式编程的工作原理和概念,所以我想知道是否有可能以FP方式实现DP算法。

可以使用哪些函数式编程结构来做到这一点?

5 个答案:

答案 0 :(得分:17)

执行此操作的常见方法是通过延迟记忆。在某种意义上,递归的斐波纳契函数可以被认为是动态编程,因为它计算重叠子问题的结果。我意识到这是一个疲惫的例子,但这是一种品味。它使用data-memocombinators库进行延迟记忆。

import qualified Data.MemoCombinators as Memo

fib = Memo.integral fib'
    where
    fib' 0 = 0
    fib' 1 = 1
    fib' n = fib (n-1) + fib (n-2)

fib是记忆版本,而fib'只是“强力”解决问题,但使用记忆的fib计算其子问题。其他DP算法使用不同的备忘录结构以相同的风格编写,但同样的想法是以简单的功能方式计算结果并进行记忆。

编辑:我终于放弃并决定提供一个可记忆的类型类。这意味着现在便于记忆:

import Data.MemoCombinators.Class (memoize)

fib = memoize fib'
    where
    fib' :: Integer -> Integer  -- but type sig now required 
    ...

需要遵循类型的Instaead,你可以memoize任何东西。如果你愿意,你仍然可以使用旧方法。

答案 1 :(得分:16)

答案 2 :(得分:10)

如果要使用带有2个或3个参数的DP(例如,处理字符串时),可以使用不可变数组:

import Data.Array.IArray

answer :: String -> Int
answer s = table ! (1, l)
  where
    l = length s

    --signatyres are needed, because GHC doesn't know what kind of Array we need
    --string is stored in Array because we need quick access to individual chars
    a :: Array Int Char
    a = listArray (1, l) s

    table :: Array (Int, Int) Int
    table = listArray ((1, 1), (l, l)) [f i j | i <- [1..l], j <- [1..l]]

    f i j |    i    >     j    = 0
          |    i    ==    j    = 1
          | (a ! i) == (a ! j) = 2 + table ! (i+1, j-1)
          | otherwise          = maximum [table ! (i+1, j), table ! (i, j-1)]

此代码解决了以下任务:给定一个字符串S,找到最大长度为S的子序列,这将是一个palyndrome(子序列不需要连续)。

基本上,'f'是resursive函数,array'table'是所有可能值的矩阵。因为Haskell是惰性的,所以只需要计算“f”的答案值。换句话说,这是带有memoization的递归。所以使用Data.Memocombinators,它们是相同的,但已经由其他人编写:)

答案 3 :(得分:7)

由于懒惰,haskell中的动态编程可以优雅地表达,请参阅this page上的第一个示例

答案 4 :(得分:1)

动态编程算法通常利用将问题简化为更简单的问题的想法。它的问题可以表述为一些基本事实(比如,从方形单元到自身的最短路径长度为0)加上一组循环规则,它们准确地显示了如何减少问题“从单元格中找到最短路径{{1 }} (i,j) to problem ”查找从单元格(0,0)(i-1,j)(i,j-1)的最短路径;选择最佳“ 。 AFAIK这可以很容易地在功能风格的程序中表达出来;没有涉及的州。