ETOOBUSY 🚀 minimal blogging for the impatient
PWC175 - Perfect Totient Numbers
TL;DR
On with TASK #2 from The Weekly Challenge #175. Enjoy!
The challenge
Write a script to generate first
20 Perfect Totient Numbers
. Please checkout wikipedia page for more informations.Output
3, 9, 15, 27, 39, 81, 111, 183, 243, 255, 327, 363, 471, 729, 2187, 2199, 3063, 4359, 4375, 5571
The questions
I guess the reference to the Wikipedia page says it all. Although… I can’t help noting that both $0$ and $1$ might somehow qualify. Anyway.
The solution
This is finally the time that I complicate bread. The gist of this
challenge is to code some is_perfect_totient_number
, which in turn
begs for implementing some totient_supersum
that calculates the
iterative sum of totient values.
use ntheory 'euler_phi';
sub is_perfect_totient_number ($n) { $n == totient_supersum($n) }
sub totient_supersum ($n) {
state $cache = {0 => 0, 1 => 1, 2 => 1};
# first "recurse" up to the point where we have something
# in cache
my @stack = $n;
push @stack, $n = euler_phi($n) while ! exists $cache->{$n};
# then go back down to calculate all new needed values
$n = pop @stack;
my $pred = $cache->{$n};
while (@stack) {
($n, my $phi) = (pop(@stack), $n);
$pred = $cache->{$n} = $phi + $pred;
}
# whatever is left is what we were after in the first place
return $pred;
}
And now we have our usual brute forcing the way up to the needed number of items.
Well, this is it. This must end now.
So, instead of this:
# WARNING: UNTESTED CODE
my $n = shift // 20;
my $candidate = 2;
while ($n > 0) {
if (is_perfect_totient_number($candidate)) {
say $candidate;
--$n;
}
++$candidate;
}
I decided to go for this:
my $n_items = shift // 20;
say
for BruteCheck::brutechecker(
iterator => BruteCheck::int_iterator('2..'),
ender => BruteCheck::max_size($n_items),
checker => sub ($n) { return totient_supersum($n) == $n },
);
# ...
package BruteCheck;
sub max_size ($n) { sub ($aref) { $aref->@* >= $n } }
sub int_iterator ($spec) {
my ($start, $stop, $step) = $spec =~ m{
\A
([1-9]\d*|0|)
\.\.
([1-9]\d*|0|)
(?: / (-?[1-9]\d*))?
\z
}mxs;
$start ||= 0;
$step ||= 1;
my $i = $start;
return sub {
return
if length($stop)
&& (($step > 0 && $i > $stop) || ($step < 0 && $i < $stop));
my $retval = $i;
$i += $step;
return $retval;
};
} ## end sub int_iterator ($spec)
sub brutechecker (%args) {
my ($checker, $iterator, $ender) = @args{qw< checker iterator ender >};
$iterator //= int_iterator('..');
$ender //= sub { 0 };
my @retval;
while (!$ender->(\@retval) && defined(my $candidate = $iterator->())) {
push @retval, $candidate if $checker->($candidate);
}
return @retval;
} ## end sub brutechecker (%args)
So we get:
- a wonderfully overkill iterator generator which parses strings like
..
(from 0 to infinity),2..
(from 2 up to infinity),2..10
(from 2 to 10),2..10/2
(two by two), … - a definitely overkill wrapper for checking when an array has a specific size, or more;
- a brute force generalization that will surely fail me the next time I need it.
Anyway, the Raku alternative made me come to compromises. I gave up on the parsing of a string and went for an explicit expression for the start/stop/step values.
#!/usr/bin/env raku
use v6;
class IntIterator { ... }
class BruteCheck { ... }
sub MAIN (Int:D $n where * > 0 = 20) {
$*OUT.out-buffer = False;
.put for BruteCheck.new(
iterator => IntIterator.new(start => 2),
ender => -> @x { @x.elems == $n },
checker => sub ($n) { $n == totient-supersum($n) },
).run();
}
sub totient-supersum ($n is copy) {
state %cache = <0 0 1 1 2 1>;
my @stack = $n,;
@stack.push($n = euler-phi($n)) while %cache{$n}:!exists;
$n = @stack.pop;
my $pred = %cache{$n};
while @stack {
($n, my $phi) = @stack.pop, $n;
$pred = %cache{$n} = $phi + $pred;
}
return $pred;
}
sub euler-phi ($n) {
state %cache = <0 0 1 1 2 1>;
return %cache{$n} //= (1 ..^ $n).grep({($_ gcd $n) == 1}).elems;
}
class IntIterator {
has $.start is readonly is built = 0;
has $.stop is readonly is built = Inf;
has $.step is readonly is built = 1;
has $!current;
submethod TWEAK() {
$!current = $!start;
$!stop = -Inf if $!stop == Inf && $!step < 0;
}
method pull-one {
return Nil
if ($!step > 0 && $!current > $!stop)
|| ($!step < 0 && $!current < $!stop);
my $retval = $!current;
$!current += $!step;
return $retval;
}
};
class BruteCheck {
has &.checker;
has &.ender is built = sub (@r) { False };
has $.iterator is built = IntIterator.new();
method run {
my @rval;
while (! &!ender(@rval) && defined(my $c = $!iterator.pull-one())) {
@rval.push: $c if &!checker($c);
}
return @rval;
}
}
And with all these unneeded lines of code… it’s time to say goodbye until next time!