F# System.OutOfMemoryException 与递归调用
这实际上是 F# 中 Project Euler 问题 14 的解决方案。但是,在尝试计算较大数字的迭代序列时,我遇到了 System.OutOfMemory 异常。如您所见,我正在使用尾部调用编写递归函数。
我遇到了 StackOverFlowException 的问题,因为我正在 Visual Studio 中进行调试(这会禁用尾部调用)。我已经在另一个问题中记录了这一点。在这里,我在发布模式下运行 - 但当我将其作为控制台应用程序运行时(在具有 4GB RAM 的 Windows XP 上),我遇到了内存不足的异常。
我真的不明白我是如何将自己编码成这种内存溢出&的。希望有人能以我的方式向我指出错误。
let E14_interativeSequence x =
let rec calc acc startNum =
match startNum with
| d when d = 1 -> List.rev (d::acc)
| e when e%2 = 0 -> calc (e::acc) (e/2)
| _ -> calc (startNum::acc) (startNum * 3 + 1)
let maxNum pl=
let rec maxPairInternal acc pairList =
match pairList with
| [] -> acc
| x::xs -> if (snd x) > (snd acc) then maxPairInternal x xs
else maxPairInternal acc xs
maxPairInternal (0,0) pl
|> fst
// if I lower this to like [2..99999] it will work.
[2..99999]
|> List.map (fun n -> (n,(calc [] n)))
|> List.map (fun pair -> ((fst pair), (List.length (snd pair))))
|> maxNum
|> (fun x-> Console.WriteLine(x))
编辑
鉴于答案中的建议,我重写了使用惰性列表并使用 Int64 的列表。
#r "FSharp.PowerPack.dll"
let E14_interativeSequence =
let rec calc acc startNum =
match startNum with
| d when d = 1L -> List.rev (d::acc) |> List.toSeq
| e when e%2L = 0L -> calc (e::acc) (e/2L)
| _ -> calc (startNum::acc) (startNum * 3L + 1L)
let maxNum (lazyPairs:LazyList<System.Int64*System.Int64>) =
let rec maxPairInternal acc (pairs:seq<System.Int64*System.Int64>) =
match pairs with
| :? LazyList<System.Int64*System.Int64> as p ->
match p with
| LazyList.Cons(x,xs)-> if (snd x) > (snd acc) then maxPairInternal x xs
else maxPairInternal acc xs
| _ -> acc
| _ -> failwith("not a lazylist of pairs")
maxPairInternal (0L,0L) lazyPairs
|> fst
{2L..999999L}
|> Seq.map (fun n -> (n,(calc [] n)))
|> Seq.map (fun pair -> ((fst pair), (Convert.ToInt64(Seq.length (snd pair)))))
|> LazyList.ofSeq
|> maxNum
这解决了问题。不过,我也会看看 Yin Zhu 的解决方案,这是更好的解决方案。
This is actually a solution to Project Euler Problem 14 in F#. However, I'm running into a System.OutOfMemory exception when attempting to calculate an iterative sequence for larger numbers. As you can see, I'm writing my recursive function with tail calls.
I was running into a problem with StackOverFlowException because I was debugging in visual studio (which disables the tail calls). I've documented that in another question. Here, I'm running in release mode--but I'm getting out of memory exceptions when I run this as a console app (on windows xp with 4gb ram).
I'm really at a loss to understand how I coded myself into this memory overflow & hoping someone can show my the error in my ways.
let E14_interativeSequence x =
let rec calc acc startNum =
match startNum with
| d when d = 1 -> List.rev (d::acc)
| e when e%2 = 0 -> calc (e::acc) (e/2)
| _ -> calc (startNum::acc) (startNum * 3 + 1)
let maxNum pl=
let rec maxPairInternal acc pairList =
match pairList with
| [] -> acc
| x::xs -> if (snd x) > (snd acc) then maxPairInternal x xs
else maxPairInternal acc xs
maxPairInternal (0,0) pl
|> fst
// if I lower this to like [2..99999] it will work.
[2..99999]
|> List.map (fun n -> (n,(calc [] n)))
|> List.map (fun pair -> ((fst pair), (List.length (snd pair))))
|> maxNum
|> (fun x-> Console.WriteLine(x))
EDIT
Given the suggestions via the answers, I rewrote to use a lazy list and also to use Int64's.
#r "FSharp.PowerPack.dll"
let E14_interativeSequence =
let rec calc acc startNum =
match startNum with
| d when d = 1L -> List.rev (d::acc) |> List.toSeq
| e when e%2L = 0L -> calc (e::acc) (e/2L)
| _ -> calc (startNum::acc) (startNum * 3L + 1L)
let maxNum (lazyPairs:LazyList<System.Int64*System.Int64>) =
let rec maxPairInternal acc (pairs:seq<System.Int64*System.Int64>) =
match pairs with
| :? LazyList<System.Int64*System.Int64> as p ->
match p with
| LazyList.Cons(x,xs)-> if (snd x) > (snd acc) then maxPairInternal x xs
else maxPairInternal acc xs
| _ -> acc
| _ -> failwith("not a lazylist of pairs")
maxPairInternal (0L,0L) lazyPairs
|> fst
{2L..999999L}
|> Seq.map (fun n -> (n,(calc [] n)))
|> Seq.map (fun pair -> ((fst pair), (Convert.ToInt64(Seq.length (snd pair)))))
|> LazyList.ofSeq
|> maxNum
which solves the problem. I'd also look at Yin Zhu's solution which is better, though.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
正如 Brian 所提到的,
List.*
操作在这里不合适。它们消耗了太多的内存。stackoverflow问题来自另一个地方。有两种可能的 stackoverflow:
calc
和maxPairInternal
。它必须是第一个,因为第二个与第一个具有相同的深度。然后问题就到了数字,3n+1
问题中的数字很容易变得非常大。所以你首先会得到一个 int32 溢出,然后你会得到一个 stackoverflow。这就是原因。将数字更改为 64 位后,程序可以运行。这是我的解决方案页面,您可以在其中看到记忆技巧。
As mentioned by Brian,
List.*
operations are not appropriate here. They cost too much memory.The stackoverflow problem comes from another place. There are two possible for you to have stackoverflow:
calc
andmaxPairInternal
. It must be the first as the second has the same depth as the first. Then the problem comes to the numbers, the number in3n+1
problem could easily go to very large. So you first get a int32 overflow, then you get a stackoverflow. That's the reason. After changing the numbers to 64bit, the program works.Here is my solution page, where you can see a memoization trick.
如果将 List.map 更改为 Seq.map (并重新工作 maxPairInternal 以迭代 seq),这可能会有所帮助。现在,您在处理整个结构以获得单个数字结果之前,首先在一个巨大的结构中显示所有数据。最好通过 Seq 惰性地执行此操作,只需创建一行,然后将其与下一行进行比较,一次创建一行然后丢弃它。
我现在没有时间编写我的建议,但如果您仍然遇到问题,请告诉我,我会重新考虑这个问题。
If you change List.map to Seq.map (and re-work maxPairInternal to iterate over a seq) that will probably help tons. Right now, you're manifesting all the data at once in a giant structure before processing the whole structure to get a single number result. It is much better to do this lazily via Seq, and just create one row, and compare it with the next row, and create a single row at a time and then discard it.
I don't have time to code my suggestion now, but let me know if you are still having trouble and I'll revisit this.
别再尝试到处使用列表了,这不是 Haskell!别再到处写
fstpair
和sndpair
,这不是 Lisp!如果您想要 F# 中的简单解决方案,您可以直接这样做,而无需创建任何中间数据结构:
在我的上网本上大约需要 15 秒。如果您想要更省时的结果,请通过数组重用以前的结果:
在我的上网本上大约需要 0.2 秒。
Stop trying to use lists everywhere, this isn't Haskell! And stop writing
fst pair
andsnd pair
everywhere, this isn't Lisp!If you want a simple solution in F# you can do it directly like this without creating any intermediate data structures:
That takes around 15s on my netbook. If you want something more time efficient, reuse previous results via an array:
That takes around 0.2s on my netbook.
发现这个寻找 Microsoft.FSharp.Core.Operators.Checked。
我刚刚学习 F#,所以我想参加 Project Euler 14 Challenge。
这使用递归但不使用尾递归。
对我来说大约需要 3.1 秒,但优点是我几乎可以理解它。
Found this looking for Microsoft.FSharp.Core.Operators.Checked.
I'm just learning F#, so I thought I'd take the Project Euler 14 Challenge.
This uses recursion but not tail-recursion.
Takes about 3.1 sec for me, but has the advantage that I can almost understand it.