我正在制作一个胶合序列。我目前有一个 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++;
}
我不想完全给出答案,我只需要了解如何正确缓存而不会使代码变慢。
答案 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)
更改算法以缓存结果,以便它可以提前爆发:
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
中选择了缓存,因为从1
到N
的所有整数的处理很容易匹配数组的索引。与在保存相同数据的哈希中使用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