我有我爲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