我有我为f#中morris seq编写的“学习代码”,该代码遭遇堆栈溢出,我不知道如何避免。 “morris”返回无限序列的“see and say”序列(即{{1},{1,1},{2,1},{1,2,1,1},{1,1,1 ,2,2,1},{3,1,2,2,1,1},...})。避免堆栈溢出(使用F#无限序列的序列)
let printList l =
Seq.iter (fun n -> printf "%i" n) l
printfn ""
let rec morris s =
let next str = seq {
let cnt = ref 1 // Stack overflow is below when enumerating
for cur in [|0|] |> Seq.append str |> Seq.windowed 2 do
if cur.[0] <> cur.[1] then
yield!([!cnt ; cur.[0]])
cnt := 0
incr cnt
}
seq {
yield s
yield! morris (next s) // tail recursion, no stack overflow
}
// "main"
// Print the nth iteration
let _ = [1] |> morris |> Seq.nth 3125 |> printList
可以摘掉使用Seq.nth第n次迭代,但你只能得到到目前为止,你打一个堆栈溢出之前。递归的一点是尾递归,它实质上构建了一组链接的枚举器。这不是问题所在。它是在第4000个序列中调用“枚举”的时候。请注意,使用F#1.9.6.16,以前的版本超过14000)。这是因为链接序列被解析的方式。序列是懒惰的,所以“递归”是懒惰的。也就是说,seq n调用seq n-1调用seq n-2等等来获得第一个项目(第一个#是最差的情况)。
据我所知,[|0|] |> Seq.append str |> Seq.windowed 2
正在让我的问题变得更糟,如果我消除了这个问题,我可以将#产生的三倍。实际上,代码工作得很好。 morris的第3125次迭代的长度将超过10^359个字符。
我真的想解决的问题是如何留住懒惰EVAL和有根据的迭代,我可以摘掉堆栈大小没有限制。我正在寻找适当的F#成语,以根据内存大小制定限制。
消息:十月'10
学习F#好一点后,哈斯克尔的一点点,思索&调查这个问题已有一年,我终于可以回答我的问题。但是,一如既往地遇到困难问题,问题始于它是一个错误的问题。问题不是序列的序列 - 这是因为递归定义的序列。我的函数式编程技能现在好一点,所以它更容易看到发生了什么事情与下面的版本,它仍然得到一个计算器
let next str =
Seq.append str [0]
|> Seq.pairwise
|> Seq.scan (fun (n,_) (c,v) ->
if (c = v) then (n+1,Seq.empty)
else (1,Seq.ofList [n;c])) (1,Seq.empty)
|> Seq.collect snd
let morris = Seq.unfold(fun sq -> Some(sq,next sq))
这basicially创建了一个很长的序列处理函数的调用链生成sequnces。 F#附带的Seq模块是不能在不使用堆栈的情况下跟随链的。有一个优化用于追加和递归定义的序列,但只有当递归实现附加时,该优化才有效。
那么这将工作
let rec ints n = seq { yield n; yield! ints (n+1) }
printf "%A" (ints 0 |> Seq.nth 100000);;
而这一次会得到一个计算器。
let rec ints n = seq { yield n; yield! (ints (n+1)|> Seq.map id) }
printf "%A" (ints 0 |> Seq.nth 100000);;
为了证明F#libary是问题,我写了实施追加,成对,扫描和使用continutions收集,现在我可以开始没有问题产生,并打印出50000 SEQ(我自己的序列模块因为长度超过10^5697位,所以永远不会完成)。
一些其他注意事项:
- 延续是我一直在寻找的成语,但在这种情况下,他们不得不进入F#库,而不是我的代码。我了解到的,Tomas Petricek's现实世界的函数式编程书在F#的延续。
- 我接受的懒惰列表答案持有其他成语;懒惰的评价。在我重写的库中,我还必须利用惰性类型来避免堆栈溢出。
- 靠运气作品的懒列表版本八九不离十(也许设计,但已经超出了我目前的判断能力) - 主动模式匹配其使用,而它的建设和迭代导致列出了所需的递归过于之前计算值深,所以它很懒,但不是很懒,它需要延续,以避免stackoverflow。例如,当第二个序列需要第一个序列中的一个数字时,它已经被计算出来了。换句话说,LL版本并非严格的JIT懒序列生成,只有列表管理。
您的算法需要多长时间来计算第60个morris元素? – Dario 2009-05-23 13:06:16
我不知道确切的时间。这大概是4分钟加。我的一个同事所做的C++版本是次要的。功能越多,它越慢。这是所有对象创作。上面的版本开始创建输出,即使在14000. – 2009-05-23 13:57:48
这个版本是不是很正常的功能。我在Haskell中以纯功能的方式编写了这个代码,它更加简洁(只有列表+模式匹配)和b)更快;-) – Dario 2009-05-23 17:29:28