Recursion 带有递归调用的F#System.OutOfMemoryException
这实际上是在F#中投影Euler的一个解决方案。但是,在尝试计算较大数字的迭代序列时,遇到System.OutOfMemory异常。如您所见,我正在编写带有尾部调用的递归函数 我遇到了StackOverFlowException的问题,因为我正在visual studio中调试(这会禁用尾部调用)。我已经记录下来了。在这里,我是在发布模式下运行的——但当我作为控制台应用程序(在内存为4gb的windows xp上)运行时,内存会出现异常 我真的无法理解我是如何将自己编码到这个内存溢出中的&希望有人能用我的方式告诉我这个错误Recursion 带有递归调用的F#System.OutOfMemoryException,recursion,f#,out-of-memory,tail-recursion,Recursion,F#,Out Of Memory,Tail Recursion,这实际上是在F#中投影Euler的一个解决方案。但是,在尝试计算较大数字的迭代序列时,遇到System.OutOfMemory异常。如您所见,我正在编写带有尾部调用的递归函数 我遇到了StackOverFlowException的问题,因为我正在visual studio中调试(这会禁用尾部调用)。我已经记录下来了。在这里,我是在发布模式下运行的——但当我作为控制台应用程序(在内存为4gb的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
#r“FSharp.PowerPack.dll”
让E14_交互活动序列=
让rec calc acc startNum重新计算=
将startNum与
|当d=1L->List.rev(d::acc)|>List.toSeq时的d
|e%2L=0L时的e->计算(e::acc)(e/2L)
|>计算(起点::acc)(起点*3L+1L)
let maxNum(懒散对:懒散列表)=
let rec maxPairInternal acc(成对:序号)=
配对
| :? 懒散列表为p->
配p
|Cons(x,xs)->如果(sndx)>(sndacc)那么maxPairInternal x xs
else maxPairInternal acc xs
|->acc
|->failwith(“不是懒散的成对列表”)
最大内部(0升,0升)懒散对
|>fst
{2L..999999 L}
|>序列图(乐趣n->(n,(计算[]n)))
|>Seq.map(有趣的配对->((fst配对),(转换为64(Seq.length(snd配对'))))
|>懒散的
|>最大值
这就解决了问题。不过,我还要看看尹朱的解决方案,它更好。如果您将List.map更改为Seq.map(并重新使用maxPairInternal对Seq进行迭代),这可能会有所帮助。现在,在处理整个结构以获得单个数字结果之前,您将在一个巨大的结构中同时显示所有数据。通过Seq惰性地执行此操作要好得多,只需创建一行,并将其与下一行进行比较,然后一次创建一行,然后丢弃它
我现在没有时间编写我的建议,但是如果您仍然有问题,请告诉我,我将重新讨论。正如Brian所提到的,
列表。*
操作在这里不合适。它们占用了太多内存
stackoverflow问题来自另一个地方。堆栈溢出有两种可能:calc
和maxPairInternal
。它必须是第一个,因为第二个深度与第一个深度相同。然后问题就出现在数字上,3n+1
问题中的数字很容易变得非常大。首先是int32溢出,然后是stackoverflow。这就是原因。将数字更改为64位后,程序开始工作
,在那里你可以看到一个记忆技巧
open System
let E14_interativeSequence x =
let rec calc acc startNum =
match startNum with
| d when d = 1L -> List.rev (d::acc)
| e when e%2L = 0L -> calc (e::acc) (e/2L)
| _ -> calc (startNum::acc) (startNum * 3L + 1L)
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 (0L,0) pl
|> fst
// if I lower this to like [2..99999] it will work.
[2L..1000000L]
|> Seq.map (fun n -> (n,(calc [] n)))
|> Seq.maxBy (fun (n, lst) -> List.length lst)
|> (fun x-> Console.WriteLine(x))
不要到处使用列表,这不是Haskell!停止到处写
fst-pair
和snd-pair
,这不是Lisp
如果您想在F#中找到一个简单的解决方案,您可以直接这样做,而无需创建任何中间数据结构:
let rec f = function
| 1L -> 0
| n when n % 2L = 0L -> 1 + f(n / 2L)
| n -> 1 + f(3L * n + 1L)
let rec g (li, i) = function
| 1L -> i
| n -> g (max (li, i) (f n, n)) (n - 1L)
let euler14 n = g (0, 1L) n
我的上网本大约需要15秒。如果您想要更省时的结果,请通过数组重用以前的结果:
let rec inside (a : _ array) n =
if n <= 1L || a.[int n] > 0s then a.[int n] else
let p =
if n &&& 1L = 0L then inside a (n >>> 1) else
let n = 3L*n + 1L
if n < int64 a.Length then inside a n else outside a n
a.[int n] <- 1s + p
1s + p
and outside (a : _ array) n =
let n = if n &&& 1L = 0L then n >>> 1 else 3L*n + 1L
1s + if n < int64 a.Length then inside a n else outside a n
let euler14 n =
let a = Array.create (n+1) 0s
let a = Array.Parallel.init (n+1) (fun n -> inside a (int64 n))
let i = Array.findIndex (Array.reduce max a |> (=)) a
i, a.[i]
让rec进入(a:uu数组)n=
如果为n0s,则为[int n]else
让p=
如果n&&1L=0L,则在a(n>>>1)内,否则
设n=3L*n+1L
如果n>1个其他3L*n+1L
1s+如果na内部(int64n))
设i=Array.findIndex(Array.reduce max a |>(=)a
i、 a[我]
这在我的上网本上大约需要0.2秒。在查找Microsoft.FSharp.Core.Operators.Checked时发现了这个。 我只是在学习F#,所以我想我应该接受Euler 14项目的挑战 它使用递归,但不使用尾部递归。 对我来说大约需要3.1秒,但有一个优势,我几乎可以理解它
let Collatz (n:int64) = if n % 2L = 0L then n / 2L else n * 3L + 1L
let rec CollatzLength (current:int64) (acc:int) =
match current with
| 1L -> acc
| _ -> CollatzLength (Collatz current) (acc + 1)
let collatzSeq (max:int64) =
seq{
for i in 1L..max do
yield i, CollatzLength i 0
}
let collatz = Seq.toList(collatzSeq 1000000L)
let result, steps = List.maxBy snd collatz
正如@Brian指出的,Seq更适合这个问题。事实上,通过研究ProjectEuler中的前45个问题,我发现几乎所有问题都最适合基于Seq的解决方案。如果您感兴趣,下面是我对问题14的解决方案:(当然,您可能希望等到您的计算结果令人满意后再进行比较,或者您可能对算法感到满意,但希望看到基于Seq的实现的外观)。警告,除了OutOfMemoryException之外,你的解决方案至少还有一个问题,如果你看我的解决方案,这个问题可能会被破坏。我注意到OP代码中存在int32溢出,但没有连接到内存不足异常;当我在自己的解决方案中遇到同样的缺陷时,它导致了不终止,因为我的策略不涉及实际构建collatz链,而只是计算它们的长度。是的。我不会想到的@Kevin Won:如果您怀疑或想测试代码中是否发生整数溢出,请将
打开Microsoft.FSharp.Core.Operators.Checked添加到脚本中。这将用溢出时抛出的整数运算符替换整数运算符。它会使你的计算(稍微)变慢,所以当不再需要它时,别忘了删除它。@cfern:很好。我不知道。@jon:除了样式prefs之外,您反对列表和对函数的原因是什么?我试图理解你的观点&为什么你认为我的解决方案是一种滥用。虽然F#肯定不是Haskell或Lisp,但它肯定有一个双亲血统