我正在研究this problem的f#解决方案,我需要找到1,000,000以上的生成元素,生成的序列最长 我使用尾递归函数来记忆先前的结果以加速计算。这是我目前的实施。
let memoize f =
let cache = new Dictionary<_,_>(1000000)
(fun x ->
match cache.TryGetValue x with
| true, v ->
v
| _ -> let v = f x
cache.Add(x, v)
v)
let rec memSequence =
memoize (fun generator s ->
if generator = 1 then s + 1
else
let state = s+1
if even generator then memSequence(generator/2) state
else memSequence(3*generator + 1) state )
let problem14 =
Array.init 999999 (fun idx -> (idx+1, (memSequence (idx+1) 0))) |> Array.maxBy snd |> fst
它似乎运行良好,直到想要计算由前100,000个数字生成的序列的长度,但它显着减慢。事实上,对于120,000,它似乎没有终止。我有一种感觉,这可能是由于我使用的词典,但我读到这不应该是这种情况。你能指出为什么这可能效率低下吗?
答案 0 :(得分:3)
你走在正确的轨道上,但是如何实现你的记忆是非常错误的。
你的memoize函数接受一个参数的函数并返回它的memoized版本。但是当你在memSequence
中使用它时,你给它一个curried的两个参数函数。然后发生的是memoize接受函数并保存仅为第一个参数部分应用它的结果,即它存储因将函数应用于generator
而产生的闭包,然后继续调用那些闭包。 s
。
这意味着你的memoization实际上没有做任何事情 - 在你的memoize函数中添加一些print语句,你会发现你仍在进行完全递归。
答案 1 :(得分:2)
我认为潜在的问题可能是如何将记忆功能与可能需要多个参数的潜在代价的计算函数相结合?。 在这种情况下,不需要第二个参数。记忆2168612元素(计算后字典的大小)没有任何内在错误。
小心溢出,因为在113383,序列超过System.Int32.MaxValue
。因此,解决方案可能如下所示:
let memoRec f =
let d = new System.Collections.Generic.Dictionary<_,_>()
let rec g x =
match d.TryGetValue x with
| true, res -> res
| _ -> let res = f g x in d.Add(x, res); res
g
let collatzLong =
memoRec (fun f n ->
if n <= 1L then 0
else 1 + f (if n % 2L = 0L then n / 2L else n * 3L + 1L) )
{0L .. 999999L}
|> Seq.map (fun i -> i, collatzLong i)
|> Seq.maxBy snd
|> fst