将类Java的2D动态编程矩阵转换为Haskell

将类Java的2D动态编程矩阵转换为Haskell,java,haskell,functional-programming,Java,Haskell,Functional Programming,我目前正在使用java动态编程算法,该算法使用经典的2D矩阵进行反向计算。我正在尝试将它转换为haskell之类的函数式语言,但事实证明这是我无法比拟的。寻找有关解决方案的任何建议: int[][] dynamic = new int[size][size]; for (int outer = 0; outer < size; j++){ Arrays.fill(dynamic[outer], 0); } dynamic[0][0] = 1; for (int a = 0;

我目前正在使用java动态编程算法,该算法使用经典的2D矩阵进行反向计算。我正在尝试将它转换为haskell之类的函数式语言,但事实证明这是我无法比拟的。寻找有关解决方案的任何建议:

int[][] dynamic = new int[size][size];
for (int outer = 0; outer < size; j++){
    Arrays.fill(dynamic[outer], 0);
}

dynamic[0][0] = 1; 

for (int a = 0; a < size; a++){ 
   for (int b = a; b < size; b++){ 
      if (a == 0){
        dynamic[a+1][b+1] = dynamic[a][b] * 2;

        dynamic[a][b+1] = dynamic[a][b] * -1;
      } else{
        dynamic[a+1][b+1] = dynamic[a+1][b+1] + (dynamic[a][b] * 3);
        dynamic[a][b+1] = dynamic[a][b+1] + (dynamic[a][b] * 2);
      }
    }
}
int[][]动态=新的int[size][size];
对于(int-outer=0;outer

到目前为止,我一直在寻找创建二维阵列的不同选项,但运气不好。我猜是因为Haskell不使用可变状态,所以它不是一个超级流行的选项。这可能只是我的java大脑,但我希望一些有函数背景的人能给我一些关于如何开始思考或解决这种转换的建议?

动态编程通常依靠可变性来提高效率。但这在哈斯克尔是完全可行的。
array
vector
包都提供了可变的数组数据结构。这些API与您在Java中可能使用的API稍有不同,但通常像您这样的算法具有相当直接的翻译。例如,下面是使用
array
包和
ST
monad的算法:

import Control.Monad
import Control.Monad.ST
import Data.Array.Unboxed
import Data.Array.MArray.Safe
import Data.Array.ST.Safe

foo :: Int -> UArray (Int,Int) Int
foo size = runSTUArray $ do
  dynamic <- newArray ((0,size-1),(0,size-1)) 0
  writeArray dynamic (0,0) 1
  forM_ [0..size-1] $ \a -> do
    forM_ [a..size-1] $ \b -> do
      v <- readArray dynamic (a,b)
      if a == 0 then do
        writeArray dynamic (a+1,b+1) (v*2)
        writeArray dynamic (a, b+1) (v*(-1))
      else do
        v1 <- readArray dynamic (a+1,b+1)
        writeArray dynamic (a+1,b+1) (v1 + v * 3)
        v2 <- readArray dynamic (a,b+1)
        writeArray dynamic (a,b+1) (v2 + v * 2)
  return dynamic
import-Control.Monad
进口管制站
导入Data.Array.unbox
导入Data.Array.MArray.Safe
导入Data.Array.ST.Safe
foo::Int->UArray(Int,Int)Int
foo size=runSTUArray$do
动态do
表格[a..size-1]$\b->do

动态规划通常依赖于效率的可变性。但这在哈斯克尔是完全可行的。
array
vector
包都提供了可变的数组数据结构。这些API与您在Java中可能使用的API稍有不同,但通常像您这样的算法具有相当直接的翻译。例如,下面是使用
array
包和
ST
monad的算法:

import Control.Monad
import Control.Monad.ST
import Data.Array.Unboxed
import Data.Array.MArray.Safe
import Data.Array.ST.Safe

foo :: Int -> UArray (Int,Int) Int
foo size = runSTUArray $ do
  dynamic <- newArray ((0,size-1),(0,size-1)) 0
  writeArray dynamic (0,0) 1
  forM_ [0..size-1] $ \a -> do
    forM_ [a..size-1] $ \b -> do
      v <- readArray dynamic (a,b)
      if a == 0 then do
        writeArray dynamic (a+1,b+1) (v*2)
        writeArray dynamic (a, b+1) (v*(-1))
      else do
        v1 <- readArray dynamic (a+1,b+1)
        writeArray dynamic (a+1,b+1) (v1 + v * 3)
        v2 <- readArray dynamic (a,b+1)
        writeArray dynamic (a,b+1) (v2 + v * 2)
  return dynamic
import-Control.Monad
进口管制站
导入Data.Array.unbox
导入Data.Array.MArray.Safe
导入Data.Array.ST.Safe
foo::Int->UArray(Int,Int)Int
foo size=runSTUArray$do
动态do
表格[a..size-1]$\b->do

v通常手动进行突变是一个错误。这很尴尬,它重新引入了哈斯克尔通常没有的所有变异bug。通常你会让懒惰替你处理事情。动态编程问题的结构非常简单,可以用Haskell中的惰性来表示,但您需要从一个不太模糊的代码规范开始

要做的第一件事是将其分解为一个低效的递归表示。这对您的示例来说是一个完全的痛苦-我希望您只是或多或少地随机输入代码

以下是我得到的,用Haskell表示,尽管我不能保证它是正确的:

present :: Int -> Int -> Int
present 0 0 = 1
present _ 0 = 0
present 0 b = negate (present 0 (b - 1))
present a b = future a b + 2 * present a (b - 1)

future :: Int -> Int -> Int
future 1 b = 2 * present 0 b
future a b = 3 * present (a - 1) (b - 1)
为了避免您的变异,我必须使用相互递归的函数,
present
从概念上跟踪循环中当前行的状态,
future
跟踪下一行的状态。请注意,第一行之后的行的未来情况并不完全反映您编写的内容。我随意删掉了
dynamic[a+1][b+1]+
部分,因为
dynamic[a+1][b+1]
的逻辑布局总是为零

通常情况下,您使用递归解决方案开始动态编程,所以您不需要像我在这里所做的那样进行反向工程。另外,你通常有一些看起来不像完全随机操作的东西。我有点喜欢多个函数之间的相互递归。它使下一部分更凉爽

让我们研究一下ghci,只是为了大致了解它的性能

*Main> :set +s
*Main> present 10 10
-39366
(0.00 secs, 801,616 bytes)
*Main> present 11 11
-118098
(0.00 secs, 1,530,632 bytes)
*Main> present 20 20
-2324522934
(1.07 secs, 746,657,288 bytes)
*Main> present 21 21
-6973568802
(2.22 secs, 1,493,244,064 bytes)
*Main> present 22 22
-20920706406
(4.14 secs, 2,986,417,752 bytes)
这与运行时的预期指数增长大致相似

这是事情变酷的地方。Haskell中的值可以根据自身定义。其基本思想是建立与每个函数对应的规则装箱数组,其中每个单元格的值通过一个函数表示,该函数用数组查找替换函数调用。只要定义中没有任何循环性,懒惰就可以让一切顺利

下面是它的代码:

import Data.Array
import Data.Ix

memoPresent :: Int -> Int -> Int
memoPresent r c = pArray ! (r, c)
  where
    bounds = ((0, 0), (r, c))

    pArray = listArray bounds (map (uncurry present) (range bounds))

    present 0 0 = 1
    present _ 0 = 0
    present 0 b = negate (pArray ! (0, b - 1))
    present a b = fArray ! (a, b) + 2 * pArray ! (a, b - 1)

    fArray = listArray bounds (map (uncurry future) (range bounds))

    future 1 b = 2 * pArray ! (0, b)
    future a b = 3 * pArray ! (a - 1, b - 1)
如果您密切关注,您可能会注意到整个
fArray
的第一行没有定义。如果访问了该行中的任何内容,都会出现错误,但如果调用
future 0 whatever
,则递归代码也会出现这种情况

让我们看看它是如何执行的:

*Main> :set +s
*Main> memoPresent 10 10
-39366
(0.00 secs, 194,176 bytes)
*Main> memoPresent 11 11
-118098
(0.00 secs, 223,344 bytes)
*Main> memoPresent 20 20
-2324522934
(0.00 secs, 521,944 bytes)
*Main> memoPresent 21 21
-6973568802
(0.00 secs, 566,544 bytes)
*Main> memoPresent 22 22
-20920706406
(0.00 secs, 616,304 bytes)
*Main> memoPresent 100 100
1989748563691696842
(0.01 secs, 10,442,400 bytes)
*Main> memoPresent 200 200
7879235843265279210
(0.05 secs, 41,116,704 bytes)
*Main> memoPresent 1000 1000
-4135538464527847958
(1.26 secs, 1,064,652,488 bytes)
嗯。我没想到它会突然得到肯定的答案。哦,那可能只是
Int
underflow
Integer
可能是一个更好的类型选择,尽管它不能反映您的Java代码


无论如何,如果你利用懒惰的优势,那么在Haskell中,从递归形式到动态编程的转换是非常简单的。您只需要确保按照低效的递归执行原始公式,而不是执行模糊但高效的循环变异。

通常手动执行变异是一个错误。这很尴尬,它重新引入了哈斯克尔通常没有的所有变异bug。通常你会让懒惰替你处理事情。动态规划问题的结构非常简单,可以用Haskell中的laziness来表示,但您需要从一个不太模糊的问题开始