Haskell中的Euler项目#14个技巧?

Haskell中的Euler项目#14个技巧?,haskell,math,optimization,Haskell,Math,Optimization,我正在努力。我想知道我是否可以在haskell中快速计算它。我尝试过这种天真的方法。 导入数据。列表 导入数据。函数 collatz n |偶数n=nquot2 |否则=3*n+1 colSeq=takeWhile(/=1)。(重复科拉茨) main=print$maximumBy(比较上的(length.colSeq))[1..999999] 但这花了太长时间 λ <*Main System.Timeout>: timeout (10^6*60) main Nothing λ

我正在努力。我想知道我是否可以在haskell中快速计算它。我尝试过这种天真的方法。

导入数据。列表 导入数据。函数 collatz n |偶数n=n
quot
2 |否则=3*n+1 colSeq=takeWhile(/=1)。(重复科拉茨) main=print$maximumBy(比较
上的
(length.colSeq))[1..999999]

但这花了太长时间

λ <*Main System.Timeout>: timeout (10^6*60) main
Nothing
λ:超时(10^6*60)主
没有什么

我还尝试使用反向collatz关系,并将长度保留在地图中以消除冗余计算,但这也不起作用。我不想要这个解决方案,但是有没有人有一些数学文献,或者编程技术可以让这个过程更快,或者我只是需要把它留到晚上呢?

缓存已经命中的整数的值可以节省很多时间。如果您输入数字1234,发现需要273个步骤才能得到1,请关联这些值。1234->273

现在,如果你在一个序列中点击1234,你不需要再采取273个步骤来找到答案,只需在你当前的数字上加273,你就知道序列的长度

对于你计算的每一个数字,都要这样做,即使是在序列的中间。例如,如果您在1234,但还没有值,请执行步骤(除以2),计算并缓存617的值。通过这种方式,您可以快速缓存几乎所有的重要值。有一些很长的链条,你会一次又一次地被锁住

在运行时缓存所有值的最简单方法是创建一个递归函数。如下所示(在伪代码中):


希望Haskell在递归的深度方面不会有问题,你最终会遇到这样的问题。但是,haskell不喜欢它,你可以用一个堆栈实现同样的东西,只是不那么直观

确保使用整数而不是Int,因为Int32溢出会导致递归问题

collatz :: Integer -> Integer
你的程序没有你想象的那么慢… 首先,如果使用
-O2
编译并增加堆栈大小(我使用了
+RTS-K100m
,但您的系统可能会有所不同),您的程序运行良好,在不到两分钟的时间内完成:

…但这仍然很慢 约50%的生产率意味着GC占用了我们盯着屏幕等待结果的一半时间。在我们的例子中,我们通过迭代每个值的序列来创建大量垃圾

改进 Collatz序列是一个递归序列。因此,我们应该将其定义为一个递归序列,而不是一个迭代序列,并看看会发生什么

colSeq 1 = [1]
colSeq n 
  | even n    = n : colSeq (n `div` 2) 
  | otherwise = n : colSeq (3 * n + 1)
Haskell中的列表是一种基本类型,因此GHC应该有一些漂亮的优化(
-O2
)。让我们试试这个:

结果 请注意,我们现在在大约80%的MUT时间内达到99%的生产率(与原始版本相比)。仅仅通过这个小小的改变,我们就极大地减少了运行时间

等等,还有更多! 有一件事很奇怪。为什么我们要计算1024和512的长度?毕竟,后者无法创建更长的Collatz序列

改进 然而,在这种情况下,我们必须将问题视为一项重大任务,而不是一张地图。我们需要跟踪我们已经计算过的值,我们希望清除我们已经访问过的值

我们使用
数据。为此设置

problem_14 :: S.Set Integer -> [(Integer, Integer)]
problem_14 s
  | S.null s  = []
  | otherwise = (c, fromIntegral $ length csq) : problem_14 rest
  where (c, rest') = S.deleteFindMin s
        csq        = colSeq c
        rest       = rest' `S.difference` S.fromList csq
我们使用
问题14
如下:

main = print $ maximumBy (compare `on` snd) $ problem_14 $ S.fromList [1..999999]
结果 我们降低了一些生产率,但这是合理的。毕竟,我们现在使用的是
Set
而不是列表,使用的是79MB而不是1MB。然而,我们的程序现在以17秒而不是34秒的速度运行,这仅是原始时间的25%

使用
ST
灵感(C++)
intmain(){
标准:向量Q(1000000,真);
无符号长-长最大值l=0,最大值c=1;
for(无符号长i=1;i最大值){
最大值=1;
max_c=i;
}
}

std::cout时间和内存问题的主要来源是您构建了整个Collatz序列,而对于任务,您只需要它们的长度,不幸的是,懒惰并不能节省时间。只计算长度的简单解决方案只需几秒钟:

simpleCol :: Integer -> Int
simpleCol 1 = 1
simpleCol x | even x = 1 + simpleCol (x `quot` 2)
            | otherwise = 1 + simpleCol (3 * x + 1)

problem14 = maximum $ map simpleCol [1 .. 999999]
它还占用更少的内存,不需要扩大堆栈:

$> ./simpleCollatz +RTS -s
simpleCollatz +RTS -s
   2,517,321,124 bytes allocated in the heap
         217,468 bytes copied during GC
          41,860 bytes maximum residency (2 sample(s))
          19,580 bytes maximum slop
               1 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0      4804 colls,     0 par    0.00s    0.02s     0.0000s    0.0046s
  Gen  1         2 colls,     0 par    0.00s    0.00s     0.0001s    0.0001s

  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time    4.47s  (  4.49s elapsed)
  GC      time    0.00s  (  0.02s elapsed)
  EXIT    time    0.00s  (  0.00s elapsed)
  Total   time    4.47s  (  4.52s elapsed)

  %GC     time       0.0%  (0.5% elapsed)

  Alloc rate    563,316,615 bytes per MUT second

  Productivity 100.0% of total user, 98.9% of total elapsed
为了说明建议的缓存解决方案,有一种漂亮的技术叫做。可以说最简单的方法是安装
memoize
软件包:

import Data.Function.Memoize

memoCol :: Integer -> Int
memoCol = memoFix mc where
    mc _ 1 = 1
    mc f x | even x = 1 + f (x `quot` 2)
           | otherwise = 1 + f (3 * x + 1)
这减少了运行时和内存使用,但也大量使用GC来维护缓存值:

$> ./memoCollatz +RTS -s
memoCollatz +RTS -s
   1,577,954,668 bytes allocated in the heap
   1,056,591,780 bytes copied during GC
     303,942,300 bytes maximum residency (12 sample(s))
         341,468 bytes maximum slop
             616 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0      3003 colls,     0 par    1.11s    1.19s     0.0004s    0.0010s
  Gen  1        12 colls,     0 par    3.48s    3.65s     0.3043s    1.7065s

  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time    7.55s  (  7.50s elapsed)
  GC      time    4.59s  (  4.84s elapsed)
  EXIT    time    0.00s  (  0.05s elapsed)
  Total   time   12.14s  ( 12.39s elapsed)

  %GC     time      37.8%  (39.1% elapsed)

  Alloc rate    209,087,160 bytes per MUT second

  Productivity  62.2% of total user, 60.9% of total elapsed

您可以归纳地定义Collatz。因此,每个Collatz编号最多提供两个新的Collatz编号:一种可能是
div(n-1)3
,另一个是
n*2
。你也知道这些数字的链的长度增加了1。你不必让它过夜。相反,用
-make-O2-rtsopts
编译它,并将堆栈大小设置得足够高。在我的系统上,你的程序在85秒后退出。只是不要在GHCi中运行程序。@Zeta,这是有效的。谢谢。你应该回答“确保优化打开并增加堆栈大小。”我已经试过了。
problem_14_vector_st :: Int -> (Int, Int)
problem_14_vector_st limit = 
  runST $ do
    q <- V.replicate (limit+1) True    
    best <- newSTRef (1,1)
    forM_ [1..limit] $ \i -> do
      b <- V.read q i
      when b $ do
        let csq = colSeq $ fromIntegral i
        let l   = fromIntegral $ length csq
        forM_ (map fromIntegral csq) $ \j-> 
          when (j<= limit && j>= 0) $  V.write q j False
        m <- fmap snd $ readSTRef best
        when (l > m) $ writeSTRef best (i,l)
    readSTRef best
$ collatz_vector_st.exe +RTS -s
   2,762,282,216 bytes allocated in the heap
      10,021,016 bytes copied during GC
       1,026,580 bytes maximum residency (2 sample(s))
          21,684 bytes maximum slop
               2 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0      5286 colls,     0 par    0.02s    0.02s     0.0000s    0.0000s
  Gen  1         2 colls,     0 par    0.00s    0.00s     0.0001s    0.0001s

  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time    3.09s  (  3.08s elapsed)
  GC      time    0.02s  (  0.02s elapsed)
  EXIT    time    0.00s  (  0.00s elapsed)
  Total   time    3.11s  (  3.11s elapsed)

  %GC     time       0.5%  (0.7% elapsed)

  Alloc rate    892,858,898 bytes per MUT second

  Productivity  99.5% of total user, 99.6% of total elapsed
simpleCol :: Integer -> Int
simpleCol 1 = 1
simpleCol x | even x = 1 + simpleCol (x `quot` 2)
            | otherwise = 1 + simpleCol (3 * x + 1)

problem14 = maximum $ map simpleCol [1 .. 999999]
$> ./simpleCollatz +RTS -s
simpleCollatz +RTS -s
   2,517,321,124 bytes allocated in the heap
         217,468 bytes copied during GC
          41,860 bytes maximum residency (2 sample(s))
          19,580 bytes maximum slop
               1 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0      4804 colls,     0 par    0.00s    0.02s     0.0000s    0.0046s
  Gen  1         2 colls,     0 par    0.00s    0.00s     0.0001s    0.0001s

  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time    4.47s  (  4.49s elapsed)
  GC      time    0.00s  (  0.02s elapsed)
  EXIT    time    0.00s  (  0.00s elapsed)
  Total   time    4.47s  (  4.52s elapsed)

  %GC     time       0.0%  (0.5% elapsed)

  Alloc rate    563,316,615 bytes per MUT second

  Productivity 100.0% of total user, 98.9% of total elapsed
import Data.Function.Memoize

memoCol :: Integer -> Int
memoCol = memoFix mc where
    mc _ 1 = 1
    mc f x | even x = 1 + f (x `quot` 2)
           | otherwise = 1 + f (3 * x + 1)
$> ./memoCollatz +RTS -s
memoCollatz +RTS -s
   1,577,954,668 bytes allocated in the heap
   1,056,591,780 bytes copied during GC
     303,942,300 bytes maximum residency (12 sample(s))
         341,468 bytes maximum slop
             616 MB total memory in use (0 MB lost due to fragmentation)

                                    Tot time (elapsed)  Avg pause  Max pause
  Gen  0      3003 colls,     0 par    1.11s    1.19s     0.0004s    0.0010s
  Gen  1        12 colls,     0 par    3.48s    3.65s     0.3043s    1.7065s

  INIT    time    0.00s  (  0.00s elapsed)
  MUT     time    7.55s  (  7.50s elapsed)
  GC      time    4.59s  (  4.84s elapsed)
  EXIT    time    0.00s  (  0.05s elapsed)
  Total   time   12.14s  ( 12.39s elapsed)

  %GC     time      37.8%  (39.1% elapsed)

  Alloc rate    209,087,160 bytes per MUT second

  Productivity  62.2% of total user, 60.9% of total elapsed