【问题标题】:Dynamic programming with Data.Map in Haskell?在 Haskell 中使用 Data.Map 进行动态编程?
【发布时间】:2012-04-10 05:51:50
【问题描述】:

我正在尝试在 Haskell 中实现一个简单的 dp 算法(这是针对 Project Euler 的 Collat​​z 猜想问题);这是等效的c++:

map<int,int> a;
int solve(int x) {
  if (a.find(x) != a.end()) return a[x];
  return a[x] = 1 + /* recursive call */;
}

所以我在 Haskell 中编写的代码最终看起来像这样:

solve :: (Memo, Int) -> (Memo, Int)
solve (mem, x) =
 case Map.lookup x mem of
  Just l -> (mem, l)
  Nothing -> let (mem', l') = {- recursive call -}
                 mem'' = Map.insert x (1+l') mem'
             in (mem'', 1+l')

(我认为我只是在这里重新实现一个状态单子,但暂时不要介意。)调用solve的代码试图找到它可以为参数提供的最大值K=1e6:

foldl'
 (\(mem,ss) k ->
   let (mem',x') = solve (mem, k)
   in (mem', (x', k):ss))
 (Map.singleton 1 1, [(1,1)]) [2..100000]

上面编写的代码因堆栈溢出而失败。这是意料之中的,我理解,因为它建立了一个非常大的未评估的重击。所以我尝试使用

x' `seq` (mem', (x',k):ss)

在 foldl' 内,它计算 K=1e5 的正确答案。但这对于 K=1e6 失败(12 秒内堆栈溢出)。然后我尝试使用

mem'' `seq` l' `seq` (mem'', 1+l')

在求解的最后一行,但这没有任何区别(堆栈仍然溢出)。然后我尝试使用

mem'' `deepseq` l' `seq` (mem'', 1+l')

这非常慢,大概是因为 deepseq 遍历整个 map mem'',使得算法的时间复杂度是二次的,而不是 n*log(n)。

实现这一点的正确方法是什么?我被卡住了,因为我无法弄清楚如何使整个计算变得严格,而且我不太确定计算的哪一部分导致堆栈溢出,但我怀疑它是地图。我可以使用,例如,数组,但我想了解我在这里做错了什么。

【问题讨论】:

  • “正确”是什么意思?
  • 我的意思是大致相当于我心目中的 (c++) 版本,但不会因堆栈溢出而失败。

标签: haskell dynamic-programming collatz


【解决方案1】:

当您使用 32 位有符号整数类型时,堆栈溢出不会消失。对于某些起始值,链离开 32 位范围并进入负数的无限循环。使用IntegerInt64Word64

【讨论】:

  • 啊,我的错。它比使用 Integer/Int64 的 c++ 慢五倍,但不会失败。没关系。谢谢。
  • 即使您使用的是foldl',您的元组看起来也不够严格,请尝试添加一些! 严格标签。实际上,对于 Haskell 来说,慢五倍听起来相当不错。
  • @JeffBurdges 使用seqs 就足够严格了。但是,maximum 可能太懒了,如果它用于查找最长的链。但是没有严格性有助于防止无限循环。
猜你喜欢
  • 1970-01-01
  • 2014-01-01
  • 2023-03-20
  • 2011-08-25
  • 2017-02-09
  • 2018-06-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多