带有递归调用的F#System.OutOfMemoryException

Kev*_*Won 2 recursion f# tail-recursion out-of-memory

这实际上是F#中Project Euler Problem 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))
Run Code Online (Sandbox Code Playgroud)

编辑

根据答案的建议,我重写了使用惰性列表,并使用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
Run Code Online (Sandbox Code Playgroud)

这解决了这个问题.不过,我还会看看尹竺的解决方案.

Yin*_*Zhu 6

正如Brian所说,List.*这里的操作并不合适.它们耗费了太多内存.

stackoverflow问题来自另一个地方.stackoverflow有两种可能:calcmaxPairInternal.它必须是第一个,因为第二个与第一个具有相同的深度.然后问题来自数字,问题中的数字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))
Run Code Online (Sandbox Code Playgroud)

  • @Kevin Won:如果您怀疑或想要测试代码中发生整数溢出,请将`open Microsoft.FSharp.Core.Operators.Checked`添加到您的脚本中.这会将整数运算符替换为溢出时抛出的运算符.它会使您的计算(稍微)变慢,所以不要忘记在不再需要时将其删除. (3认同)