Kir*_*ill 5 haskell dynamic-programming collatz
我试图在Haskell中实现一个简单的dp算法(这是来自Project Euler的Collatz猜想问题); 这是等效的c ++:
map<int,int> a;
int solve(int x) {
if (a.find(x) != a.end()) return a[x];
return a[x] = 1 + /* recursive call */;
}
Run Code Online (Sandbox Code Playgroud)
所以我在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')
Run Code Online (Sandbox Code Playgroud)
(我认为我只是在这里重新实现状态monad,但暂时不介意.)调用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]
Run Code Online (Sandbox Code Playgroud)
上面写的代码因堆栈溢出而失败.我理解这是可以预期的,因为它构建了一个非常大的未评估的thunk.所以我尝试使用
x' `seq` (mem', (x',k):ss)
Run Code Online (Sandbox Code Playgroud)
在foldl'里面,它计算出K = 1e5的正确答案.但这对K = 1e6失败(堆栈在12秒内溢出).然后我尝试使用
mem'' `seq` l' `seq` (mem'', 1+l')
Run Code Online (Sandbox Code Playgroud)
在最后一行解决,但这没有区别(堆栈溢出仍然).然后我尝试使用
mem'' `deepseq` l' `seq` (mem'', 1+l')
Run Code Online (Sandbox Code Playgroud)
这非常慢,大概是因为deepseq遍历整个地图mem'',使得算法的时间复杂度呈二次方而不是n*log(n).
实现这个的正确方法是什么?我被困了,因为我无法弄清楚如何使整个计算严格,我不太确定计算的哪一部分给堆栈溢出,但我怀疑它是地图.我可以使用,例如,数组,但我想知道我在这里做错了什么.