这实际上是F#中Project Euler Problem 14的解决方案。但是,在尝试计算较大数字的迭代序列时,我遇到了System.OutOfMemory异常。正如您所看到的,我正在使用尾调用编写递归函数。
我遇到了StackOverFlowException的问题,因为我在visual studio中调试(禁用尾调用)。我记录了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
解决了这个问题。不过,我也会看看尹竺的解决方案,这个方案更好。
答案 0 :(得分:6)
如Brian所述,此处List.*
操作不合适。它们耗费了太多内存。
stackoverflow问题来自另一个地方。您有两种可能的堆栈溢出:calc
和maxPairInternal
。它必须是第一个,因为第二个与第一个具有相同的深度。然后问题出现在数字上,3n+1
问题中的数字很容易变得非常大。所以你首先得到一个int32溢出,然后你得到一个stackoverflow。这就是原因。将数字更改为64位后,程序可以正常工作。
Here is my solution page,你可以在那里看到一个记忆技巧。
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))
答案 1 :(得分:4)
如果将List.map更改为Seq.map(并重新设置maxPairInternal以迭代seq),这可能会有助于吨。现在,在处理整个结构以获得单个数字结果之前,您将在巨型结构中一次性显示所有数据。通过Seq懒得这样做更好,只需创建一行,然后将它与下一行进行比较,一次创建一行然后丢弃它。
我现在没有时间对我的建议进行编码,但如果您仍然遇到问题请告诉我,我会重温这一点。
答案 2 :(得分:2)
停止尝试在任何地方使用列表,这不是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]
我的上网本需要大约0.2秒。
答案 3 :(得分:0)
发现这正在寻找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