2010-11-08 31 views
2

这实际上是F#中的项目Euler Problem 14的解决方案。但是,当试图计算大数的迭代序列时,我遇到了System.OutOfMemory异常。正如你所看到的,我正在用tail调用写我的递归函数。带递归调用的F#System.OutOfMemoryException

由于我在Visual Studio中调试(禁用尾部调用),我遇到了StackOverFlowException的问题。我已经记录了in another question。在这里,我以发布模式运行 - 但是当我将其作为控制台应用程序运行时(在使用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 

它解决了这个问题。尽管如此,我也会看到尹's的解决方案更好。

+1

正如@Brian指出的,Seq更适合这个问题。事实上,在研究Euler项目中的前45个问题后,我发现几乎所有的问题都适用于基于Seq的解决方案。如果你有兴趣,这是我对问题14的解决方案:http://projecteulerfun.blogspot.com/2010/05/problem-14-which-starting-number-under.html(当然,你可能想等待直到你将你的工作满意为止,或者你可能已经满足于你的算法,但希望看到基于Seq的实现的外观)。 – 2010-11-09 00:50:52

+0

警告,除了OutOfMemoryException外,还有至少一个其他问题,您的解决方案可能会因查看我的解决方案而被破坏。 – 2010-11-09 00:53:24

回答

6

正如Brian所说,List.*操作在这里并不合适。他们花费太多的内存。

该stackoverflow问题来自另一个地方。有两种可能有你的计算器:calcmaxPairInternal。它必须是第一个,因为第二个与第一个深度相同。那么问题来了,这个数字在3n+1的问题很容易就会变得很大。所以你首先得到一个int32溢出,然后你得到一个stackoverflow。这就是原因。将数字更改为64位后,该程序起作用。

Here is my solution page,在那里你可以看到一个memoization技巧。

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)) 
+0

+1好抓@尹。我注意到OP代码中存在int32溢出,但没有建立与内存不足异常的连接;当我在自己的解决方案中遇到同样的缺陷时,它导致无法终止,因为我的策略不涉及实际构建collat​​z链,而只是计算它们的长度。 – 2010-11-09 02:16:49

+0

是的。我不会想出来的。 。 。 – 2010-11-09 02:36:48

+3

@Kevin Won:如果您怀疑或想要测试代码中发生的整数溢出,请在脚本中添加“打开Microsoft.FSharp.Core.Operators.Checked”。这将整数运算符替换为在溢出时抛出的运算符。它使你的计算(稍微)慢一点,所以不要忘记在不再需要时将其移除。 – cfern 2010-11-09 07:52:23

4

如果将List.map更改为Seq.map(并且重新工作maxPairInternal以迭代seq),这可能会帮助吨。现在,在处理整个结构以获得单个数字结果之前,您将在一个巨大的结构中同时显示所有数据。通过Seq懒洋洋地做这件事情要好得多,只需创建一行,并将其与下一行进行比较,然后一次创建一行,然后丢弃它。

我现在没有时间编写我的建议,但是如果您仍然遇到问题,请告诉我,我会重新审视这个问题。

2

不要试图在任何地方使用列表,这不是Haskell!并且到处停止写作fst pairsnd 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] 

在我的上网本上需要大约0.2秒。

+0

哇。呼吁任务! – 2010-11-16 21:20:52

+0

@jon:风格偏好之外,你反对列表和配对函数的原因是什么?我试图理解你的观点,以及为什么你认为我的解决方案是一种滥用。虽然F#肯定不是Haskell或Lisp,但它确实有一个父系亲属的血统。 – 2010-11-18 00:11:17

+0

当元素很少(最好是零)时,列表可以是一个很好的集合,特别是在逻辑编程的情况下,但在这里不是这种情况,所以它们在这种情况下不适用。 'fst'和'snd'函数几乎总是可以更好地避免,以支持模式匹配。 – 2010-11-18 13:32:17

0

找到了这个寻找Microsoft.FSharp.Core.Operators.Checked。 我刚刚学习F#,所以我想我会参加欧拉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