使用非确定性列表monad来查找长的Collat​​z序列

时间:2015-07-08 17:09:33

标签: haskell functional-programming

我编写了以下代码来解决Project Euler的No. 14

为正整数集定义了以下迭代(Collat​​z)序列:

n → n/2 (n is even)
n → 3n + 1 (n is odd)

问:哪个起始编号低于一百万,产生最长链?

我的代码:

collatz :: Integer -> [Integer]
collatz 1 = [1] 
collatz n = 
    filter (< 1000000) prev >>= poss
    where prev = collatz (n - 1)

poss :: Integer -> [Integer]
poss prev
    | even prev && prev `mod` 3 == 1 && (prev - 1) `div` 3 > 1 = [2 * prev, (prev - 1) `div` 3]
    | otherwise = [2 * prev]

其中collatz n返回将生成长度为n的Collat​​z链的数字列表。问题是,我只能不限制结果或限制整个链,而不仅仅是种子数,要低于1000,000。是否可以使用此模型来解决问题?

2 个答案:

答案 0 :(得分:3)

我认为这种方法 - 虽然有趣 - 从根本上注定要失败。假设我发现导致长度为500的链的所有种子都超过2,000,000。我怎么知道我不会发现在三个以上的步骤中有一个低于1,000,000的种子让我在那里?我看不出你什么时候知道的。

我看到这个问题唯一可行的方法是计算从1到999,999的每个数字的collat​​z长度,然后做类似的事情:

main :: IO ()
main = do
  let collatzMax = maximumBy (compare `on` collatzLength) [1..999999]
  print collatzMax

另一方面,这提供了一个了解CAFs的绝佳机会,因为函数collatzLength可以被天真地定义为:

collatzLength 1 = 1
collatzLength n | n `mod` 2 == 0 = 1 + collatzLength (n `div` 2)
collatzLength n = 1 + collatzLength (3 * n + 1)

这种递归让CAF感到尖叫。

当然,有备忘录模块可以为你建立CAF,但是自己构建一个是一个很有用的练习。这是懒惰的无限递归数据结构中的一个小小的迷你课程。

如果这让你失望,你可以浏览this spoiler如何使用CAF,然后使用不同的数据结构重写它。 (那么10路树而不是二叉树怎么样?怎么样以不同的顺序遍历树?你可以删除对showIntAtBase的调用吗?)

答案 1 :(得分:0)

你的想法很有意思,虽然不是最有效的。它可能值得尝试,虽然它可能是内存密集型的。一些想法:

  • 由于某些链条可以超过1000000,因此您无法在collatz中过滤掉少量链条。你需要保留每次通过的所有数字。
  • 以这种方式调用collatz是低效的,因为它会再次计算集合。使其成为共享值的无限列表会更有效:

    collatz :: [[Integer]]
    collatz = [1] : map (>>= poss) collatz
    
  • 你需要弄明白你何时完成。为此,您需要浏览collatz生成的数字列表,并计算其中有多少数量低于1000000.当您看到所有数字低于限制时,最后一个列表将包含数字最长的链条。

那就是说,我担心这种方法在计算上是不可行的。特别是,您将生成指数级数和指数级大数。例如,如果最长链为500,则该步骤中collatz的结果将包含最多2 ^ 500的数字。如上所述,没有办法分辨哪些大数字可能是导致解决方案的那个,所以你不能放弃它们。