F# System.OutOfMemoryException 与递归调用

发布于 2024-10-02 01:13:42 字数 2597 浏览 3 评论 0原文

这实际上是 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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

花落人断肠 2024-10-09 01:13:42

正如 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))

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 and maxPairInternal. It must be the first as the second has the same depth as the first. Then the problem comes to the numbers, the number in 3n+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.

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))
故人的歌 2024-10-09 01:13:42

如果将 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.

日记撕了你也走了 2024-10-09 01:13:42

别再尝试到处使用列表了,这不是 Haskell!别再到处写 fstpairsndpair,这不是 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 秒。

Stop trying to use lists everywhere, this isn't Haskell! And stop writing fst pair and snd 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:

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

That takes around 15s on my netbook. If you want something more time efficient, reuse previous results via an array:

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]

That takes around 0.2s on my netbook.

你是我的挚爱i 2024-10-09 01:13:42

发现这个寻找 Microsoft.FSharp.Core.Operators.Checked。
我刚刚学习 F#,所以我想参加 Project Euler 14 Challenge。

这使用递归但不使用尾递归。
对我来说大约需要 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

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.

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
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文