试图预测未来的结果,伯兹,佩尔

时间:2014-05-21 20:46:49

标签: perl caching for-loop prediction collatz

我正在制作一个胶合序列。我目前有一个 for 循环。

for my $num (1..1000000) {
    my $count = 1;
    for (my $i = $num; $i != 1; $count++) {
        $i = $i % 2 ? 3 * $i + 1 : $i / 2;
    }
}

然后我有一个简单的方法来计算循环计数(完成理论需要很多次)。

if ($count > $max_length) {
    $max = $num;
    $max_length = $count;
}

我发现使用简单的理论可以更快地编写代码。

  

如果n = 3,它将具有这个序列{3,10,5,16,8,4,2,1} [8]如果n =   6,它将具有这个序列{6,3,10,5,16,8,4,2,1} [9]如果n = 12,它   会有这个序列{12,6,3,10,5,16,8,4,2,1} [10]

所以我想保存3的结果,只需在计数中加1就可以计算出6的结果。

我尝试解决这个问题,我认为这会解决问题,但实际上我的程序需要1分钟才能完成,我现在有一个程序需要1.49秒而不是之前的30秒。

这是我添加缓存的方式(可能是错误的)

以下是for循环之外的

my $cache = 0;
my $lengthcache = 0;

然后我有一些代码位于$ i行,for循环中的第4行

    $cache = $i;
    $lengthcache = $count;
    if  ($cache = $num*2) {
            $lengthcache++;
    }

我不想完全给出答案,我只需要了解如何正确缓存而不会使代码变慢。

3 个答案:

答案 0 :(得分:3)

你只想要长度,对吗?缓存序列没有多少节省,内存使用量也会非常大。

编写一个返回长度的递归函数。

sub seq_len {
   my ($n) = @_;
   return 1 if $n == 1;
   return 1 + seq_len( $n % 2 ? 3 * $n + 1 : $n / 2 );
}

缓存结果。

my %cache;

sub seq_len {
   my ($n) = @_;
   return $cache{$n} if $cache{$n};
   return $cache{$n} = 1 if $n == 1;
   return $cache{$n} = 1 + seq_len( $n % 2 ? 3 * $n + 1 : $n / 2 );
}

也可以将终止条件移动到缓存中。

my %cache = ( 1 => 1 );

sub seq_len {
   my ($n) = @_;
   return $cache{$n} ||= 1 + seq_len( $n % 2 ? 3 * $n + 1 : $n / 2 );
}

没有必要进行递归。你可以通过平整它来加快速度。这有点棘手,但你可以使用通常的技术 [1] 来做到这一点。

my %cache = ( 1 => 1 );

sub seq_len {
   my ($n) = @_;

   my @to_cache;
   while (1) {
      if (my $length = $cache{$n}) {
         $cache{pop(@to_cache)} = ++$length while @to_cache;
         return $length;
      }

      push @to_cache, $n;
      $n = $n % 2 ? 3 * $n + 1 : $n / 2;
   }
}

确保它有效:

use strict;
use warnings;
use feature qw( say );
use List::Util qw( sum );

my $calculations;

   my %cache = ( 1 => 1 );

   sub seq_len {
      my ($n) = @_;

      my @to_cache;
      while (1) {
         if (my $length = $cache{$n}) {
            $cache{pop(@to_cache)} = ++$length while @to_cache;
            return $length;
         }

         push @to_cache, $n;
++$calculations;
         $n = $n % 2 ? 3 * $n + 1 : $n / 2;
      }
   }

my @results = map { seq_len($_) } 3,6,12;
say for @results;
say "$calculations calculations instead of " . (sum(@results)-@results);

8
9
10
9 calculations instead of 24

注意:

  1. 要删除递归,

    1. 通过重新排列代码或传递有关返回时要执行的操作的信息,使函数尾递归。 (前者在这里是不可能的。)
    2. 用循环加堆栈替换递归。
    3. 尽可能消除堆栈。 (这里不可能。)
    4. 清理结果。

答案 1 :(得分:1)

更改算法以缓存结果,以便它可以提前爆发:

use strict;
use warnings;

my @steps = (0,0);
my $max_steps = 0;
my $max_num = 0;

for my $num (2..1_000_000) {
    my $count = 0;
    my $i = $num;
    while ($i >= $num) {
        $i = $i % 2 ? 3 * $i + 1 : $i / 2;
        $count++;
    }
    $count += $steps[$i];
    $steps[$num] = $count;

    if ($max_steps < $count) {
        $max_steps = $count;
        $max_num = $num;
    }
}

print "$max_num takes $max_steps steps\n";

将处理时间从37秒更改为2.5秒。

为什么2.5秒足够改进?

我在数组@steps中选择了缓存,因为从1N的所有整数的处理很容易匹配数组的索引。与在保存相同数据的哈希中使用33M vs 96M的哈希相比,这也提供了内存优势。

正如ikegami指出的那样,这确实意味着我无法缓存超过100万的所有循环值,因为这会快速耗尽所有内存。例如,数字704,511的周期最长为56,991,483,520

最后,这意味着我的方法会重新计算某些周期的某些部分,但总体而言,由于无需在每个步骤检查缓存,因此仍然可以提高速度。当我将其更改为使用散列并在每个循环中缓存时,速度会降低到9.2secs

my %steps = (1 => 0);

for my $num (2..1_000_000) {
    my @i = $num;
    while (! defined $steps{$i[-1]}) {
        push @i, $i[-1] % 2 ? 3 * $i[-1] + 1 : $i[-1] / 2;
    }
    my $count = $steps{pop @i};
    $steps{pop @i} = ++$count while (@i);
    #...

当我使用memoize所示的Oesor时,速度为23secs

答案 2 :(得分:0)

如果您将实现更改为递归函数,则可以使用Memoize(https://metacpan.org/pod/Memoize)对其进行包装,以加快已计算的响应。

use strict;
use warnings;
use feature qw/say/;
use Data::Printer;

use Memoize;

memoize('collatz');

for my $num (qw/3 6 12 1/) {
    my @series = collatz($num);
    p(@series);
    say "$num : " . scalar @series;
}

sub collatz {
    my($i) = @_;

    return $i if $i == 1;
    return ($i, collatz( $i % 2 ? 3 * $i + 1 : $i / 2 ));
}

<强>输出

[
    [0] 3,
    [1] 10,
    [2] 5,
    [3] 16,
    [4] 8,
    [5] 4,
    [6] 2,
    [7] 1
]
3 : 8
[
    [0] 6,
    [1] 3,
    [2] 10,
    [3] 5,
    [4] 16,
    [5] 8,
    [6] 4,
    [7] 2,
    [8] 1
]
6 : 9
[
    [0] 12,
    [1] 6,
    [2] 3,
    [3] 10,
    [4] 5,
    [5] 16,
    [6] 8,
    [7] 4,
    [8] 2,
    [9] 1
]
12 : 10
[
    [0] 1
]
1 : 1