高度可约三角形数

作者:polettix

https://projecteuler.net/problem=12

三角形数序列是通过将自然数相加生成的。所以第 7 个三角形数是

1 + 2 + 3 + 4 + 5 + 6 + 7 = 28.

前十项是

1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ...

让我们列出前七个三角形数的因数

1: 1
3: 1,3
6: 1,2,3,6
10: 1,2,5,10
15: 1,3,5,15
21: 1,3,7,21
28: 1,2,4,7,14,28

我们可以看到,28 是第一个拥有超过五个因数的三角形数。

第一个拥有超过五百个因数的三角形数是多少?

源代码: prob012-polettix.pl

use v6;

# Minimum number of factors, defaults to challenge's request
my $t = @*ARGS.shift || 500;


# It's easy to tell triangle numbers: they are all of the form
# ($n * ($n + 1)) / 2. Hence, the factors are those of
# $n and ($n + 1), less one "2" because of the division.
#
# We'll iterate through $n, from 2 to... wherever it is needed.
#
# We use a factors_progressively_asked() function that is supposed
# to return factors but requests you to ask factors for numbers
# progressively (i.e. start from 2, then 3, 4, 5, ...). This allows
# an optimisation and is perfect in our case, because we need the
# factors for increasing values of $n.

my $max = 1;
my $n = 2;
my %previous_factors = factors_progressively_asked(2);

# Due to lazyness, we'll talk about ($n - 1) and $n instead of $n and
# ($n + 1).
while ($max <= $t) {
    $n++;
    my %this_factors = factors_progressively_asked($n);

    # Now, %previous_factors has the factors for ($n - 1), and
    # %this_factors has the factors for $n. We mix them all into
    # %previous_factors and then eliminate one "2" to cope with the
    # division.
    %previous_factors{$_} += %this_factors{$_} for %this_factors.keys;
    %previous_factors{2}--; # divide by 2

    # Now, we count the number of different factors
    my $p = 1;
    $p *= $_ for %previous_factors.values.map({ $_ + 1 });

    # a little feedback
    say "$n $p ($max)" unless $n % 100;

    # prepare for next iteration: update $max and save %this_factors
    $max = $p if $max < $p;
    %previous_factors = %this_factors;
}

say 'result: ', ($n * ($n - 1)) / 2;

# In rakudo, the "is copy" is needed because of a known bug
# as of Aug 24th, 2009. Otherwise, the @primes.push($x) gets
# "confused".
sub factors_progressively_asked ($x is copy) {
    state @primes;
    state %factors_for;
    my $result;

    if (%factors_for{$x}:exists) {
        $result = %factors_for{$x};
    }
    else {
        for @primes -> $p {
            if ($x % $p) == 0 { # Bingo! A divisor!
                # Now, $p is prime, and $x / $p is surely into %factors_for
                # because we're calling this function progressively, so
                # we're done.
                $result = [ $p, %factors_for{$x / $p}.list ];
            }
        }
        if (! $result) { # Ok, there's a new prime in town
            @primes.push($x);
            $result = [ $x ];
        }
        %factors_for{$x} = $result;
    }

    # Return as a hash of (factor, occurrences) pairs
    my %factors;
    %factors{$_}++ for $result.list;
    return %factors;
}