我有这个“学习代码”我为f#中的morris seq写的,它遇到了堆栈溢出,我不知道如何避免。 “morris”返回无限序列的“看见和说出”序列(即{{1},{1,1},{2,1},{1,2,1,1},{1,1,1 ,2,2,1},{3,1,2,2,1,1},...})。
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
正在使我的问题变得更糟,如果我消除了这个问题,我可能会产生三倍的问题。实际上,代码运行良好。莫里斯的第3125次迭代将超过10 ^ 359个字符。
我真正想要解决的问题是如何保留惰性eval并且根据我可以选择的迭代的堆栈大小进行无限制。我正在寻找适当的F#成语来根据内存大小制定限制。
2010年10月更新
在学习F#之后好一点,一点点Haskell,思考&amp;调查这个问题多年,我终于可以回答我自己的问题了。但是,与困难问题一样,问题始于错误的问题。问题不在于序列序列 - 它实际上是因为递归定义的序列。我的函数式编程技能现在好一点,因此更容易看到下面的版本发生了什么,它仍然得到一个stackoverflow
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))
这基本上创建了一个真正长链的Seq处理函数调用来生成sequnces。 F#附带的Seq模块是在不使用堆栈的情况下不能跟随链的。它有一个用于追加和递归定义序列的优化,但该优化仅在递归实现追加时才有效。
所以这会起作用
let rec ints n = seq { yield n; yield! ints (n+1) }
printf "%A" (ints 0 |> Seq.nth 100000);;
这个会得到一个stackoverflow。
let rec ints n = seq { yield n; yield! (ints (n+1)|> Seq.map id) }
printf "%A" (ints 0 |> Seq.nth 100000);;
为了证明F#libary是问题,我编写了自己的Seq模块,使用continuation实现了append,pairwise,scan和collect,现在我可以开始生成并打印出50,000 seq而没有问题(它永远不会完成因为它超过10 ^ 5697位数。)
一些补充说明:
答案 0 :(得分:12)
你一定要看看
但我稍后会尝试发布更全面的答案。
<强>更新强>
好的,下面是一个解决方案。它将Morris序列表示为int的LazyLists的LazyList,因为我认为你希望它在'双向'中是懒惰的。
F#LazyList(在FSharp.PowerPack.dll中)有三个有用的属性:
第一个属性与seq(IEnumerable)相同,但其他两个属性对LazyList是唯一的,对计算问题非常有用,例如本问题中提出的问题。
不用多说,代码:
// print a lazy list up to some max depth
let rec PrintList n ll =
match n with
| 0 -> printfn ""
| _ -> match ll with
| LazyList.Nil -> printfn ""
| LazyList.Cons(x,xs) ->
printf "%d" x
PrintList (n-1) xs
// NextMorris : LazyList<int> -> LazyList<int>
let rec NextMorris (LazyList.Cons(cur,rest)) =
let count = ref 1
let ll = ref rest
while LazyList.nonempty !ll && (LazyList.hd !ll) = cur do
ll := LazyList.tl !ll
incr count
LazyList.cons !count
(LazyList.consf cur (fun() ->
if LazyList.nonempty !ll then
NextMorris !ll
else
LazyList.empty()))
// Morris : LazyList<int> -> LazyList<LazyList<int>>
let Morris s =
let rec MakeMorris ll =
LazyList.consf ll (fun () ->
let next = NextMorris ll
MakeMorris next
)
MakeMorris s
// "main"
// Print the nth iteration, up to a certain depth
[1] |> LazyList.of_list |> Morris |> Seq.nth 3125 |> PrintList 10
[1] |> LazyList.of_list |> Morris |> Seq.nth 3126 |> PrintList 10
[1] |> LazyList.of_list |> Morris |> Seq.nth 100000 |> PrintList 35
[1] |> LazyList.of_list |> Morris |> Seq.nth 100001 |> PrintList 35
<强> UPDATE2 强>
如果你只想数数,那也没关系:
let LLLength ll =
let rec Loop ll acc =
match ll with
| LazyList.Cons(_,rest) -> Loop rest (acc+1N)
| _ -> acc
Loop ll 0N
let Main() =
// don't do line below, it leaks
//let hundredth = [1] |> LazyList.of_list |> Morris |> Seq.nth 100
// if we only want to count length, make sure we throw away the only
// copy as we traverse it to count
[1] |> LazyList.of_list |> Morris |> Seq.nth 100
|> LLLength |> printfn "%A"
Main()
内存使用率保持不变(我的盒子上的电量低于16M)......尚未完成运行,但我计算了第55个长度,即使在我的慢速盒子上,所以我认为这应该可行。另请注意,我使用'bignum'作为长度,因为我认为这会溢出'int'。
答案 1 :(得分:3)
我认为这里有两个主要问题:
懒惰是非常低效的,所以你可以期待一个懒惰的功能实现运行速度慢几个数量级。例如,描述here的Haskell实现比下面给出的F#慢2400倍。如果你想要一个解决方法,你最好的选择可能是通过将它们聚集成热切的批次来分摊计算,批量生产是按需生产的。
Seq.append
函数实际上是从IEnumerable
调用C#代码,因此,它的尾调用不会被消除,并且每次通过时都会泄漏更多的堆栈空间它。当您对序列进行枚举时,会显示此信息。
以下比计算第50个子序列长度的实现速度快80倍以上但也许对你来说不够懒惰:
let next (xs: ResizeArray<_>) =
let ys = ResizeArray()
let add n x =
if n > 0 then
ys.Add n
ys.Add x
let mutable n = 0
let mutable x = 0
for i=0 to xs.Count-1 do
let x' = xs.[i]
if x=x' then
n <- n + 1
else
add n x
n <- 1
x <- x'
add n x
ys
let morris =
Seq.unfold (fun xs -> Some(xs, next xs)) (ResizeArray [1])
此函数的核心是ResizeArray
的折叠,如果使用结构作为累加器,则可以将其考虑在内并在功能上使用而不会导致性能降低太多。
答案 2 :(得分:0)
只需保存您查找的上一个元素即可。
let morris2 data = seq {
let cnt = ref 0
let prev = ref (data |> Seq.nth 0)
for cur in data do
if cur <> !prev then
yield! [!cnt; !prev]
cnt := 1
prev := cur
else
cnt := !cnt + 1
yield! [!cnt; !prev]
}
let rec morrisSeq2 cur = seq {
yield cur
yield! morrisSeq2 (morris2 cur)
}