Perl Weekly Challenge: Week 102
Challenge 1:
Rare Numbers
You are given a positive integer $N
.
Write a script to generate all Rare numbers of size $N
if exists. Please checkout the page for more information about it.
Examples
(a) 2 digits: 65
(b) 6 digits: 621770
(c) 9 digits: 281089082
I'm going to show you the Raku code for my solution in a slightly different order than it appears in the actual script. The basics of what we need to do looks like this:
sub MAIN(
Int $N #= length of rare number
) {
for (10 ** ($N - 1)) ..^ (10 ** $N) -> $n {
We range through all the integers of the required length.
my $r = $n;
my $r1 = "$n".flip.Int;
$r1
is created by treating $r
(actually $n
as they are equivalent) as a string,
reversing the string, and then casting it back to an integer.
if isRare($r, $r1) {
say $r;
Then if it is a rare number we print it.
}
}
}
The isRate
function is shown below.
sub isRare(Int $r, Int $r1) {
return sqrt($r + $r1) %% 1 && sqrt($r - $r1) %% 1;
}
%% 1
means if a number is evenly divisible by 1 i.e. it has no fractional component
or in other words it is an integer. If both the square root of $r + $r1
and the
square root of $r - $r1
are integers, we have a rare number otherwise we don't.
This will work but it will be extremely slow for larger values of $N
as we will
be processing many numbers which have no chance of being rare numbers and it will also
give false positives in the case of palindromes such as 242 which are not deemed rare
numbers.
The problem of palindrames can easily be addressed with the following check just
after $r
and $r1
have been defined:
if ($r == $r1) {
next;
}
To try and weed out useless prospects, we can employ certain properties of rare numbers.
If a rare number is even, $r + $r1
will have a factor of 11 if it is less than 3 digits or 121
if it is greater. If a rare number is odd, $r - $r1
will have a factor of 11 if it is less than 4
digits and 1089 if it is greater. Expressed in code that is:
my $xfactor = $N < 3 ?? 11 !! 121;
my $yfactor = $N < 4 ?? 11 !! 1089;
Now the inside of our loop can look like this:
if $N %% 2 {
if ($r + $r1) %% $xfactor && isRare($r, $r1) {
say $r;
}
} else {
if ($r - $r1) %% $yfactor && isRare($r, $r1) {
say $r;
}
}
There is still more optimization that can be done but with only these changes, $N = 9
completed in less than 2 minutes which was good enough for me so I left it as is.
For the Perl version, I tried to follow the same course but I ended up having to make some adjustments.
sub isRare {
my ($r, $r1) = @_;
my $x = sqrt($r + $r1);
if ($r1 > $r) {
return undef;
}
my $y = sqrt($r - $r1);
return $x == int($x) && $y == int($y);
}
In particular the isRare()
function gave me problems. Perl doesn't have an equivalent
to Rakus' %%
operator that works the same way. Also Perls' sqrt()
apparently doesn't do
negative numbers like Raku does. As that would only occur for an invalid rare number anyway,
I just added a check to make sure $r
is greater than $r1
and return a false value if it is not.
my ($N) = @ARGV;
my $xfactor = $N < 3 ? 11 : 121;
my $yfactor = $N < 4 ? 11 : 1089;
for my $n ((10 ** ($N - 1)) .. ((10 ** $N) - 1)) {
my $r = $n;
my $r1 = 0 + reverse "$n";
if ($r == $r1) {
next;
}
if ($N % 2 == 0) {
if (($r + $r1) % $xfactor == 0 && isRare($r, $r1)) {
say $r;
}
} else {
if (($r - $r1) % $yfactor == 0 && isRare($r, $r1)) {
say $r;
}
}
}
Other than those minor inconveniences, the Perl version follows the same pattern as the Raku version.
Challenge 2:
Hash-Counting String
You are given a positive integer $N
.
Write a script to produce Hash-counting string of that length.
The definition of a hash-counting string is as follows:
- the string consists only of digits 0-9 and hashes, ‘#’
- there are no two consecutive hashes: ‘##’ does not appear in your string
- the last character is a hash
- the number immediately preceding each hash (if it exists) is the position of that hash in the string, with the position being counted up from 1
It can be shown that for every positive integer N there is exactly one such length-N string.
Examples
(a) "#" is the counting string of length 1
(b) "2#" is the counting string of length 2
(c) "#3#" is the string of length 3
(d) "#3#5#7#10#" is the string of length 10
(e) "2#4#6#8#11#14#" is the string of length 14
This one was a lot easier. I shall start with Raku again.
sub MAIN(
Int $N #= length of hash-counting string
) {
my $wasHash = 0;
my @hash-counting;
my $pos = $N;
while $pos > 0 {
if $wasHash {
@hash-counting.unshift("$wasHash");
$pos -= "$wasHash".chars;
$wasHash = 0;
} else {
@hash-counting.unshift('#');
$wasHash = $pos;
$pos--;
}
}
@hash-counting.join(q{}).say;
}
What I did was built up the hash-counting string backwards. I stored its' components
in an array as they are of variable length. We know from the description that the last
character (at the end of the array i.e. position $N
) is always going to be a #. The one
before that will be the value $N
with a length of the number of digits in $N
. The one
before that will be # again, and the one before that its' position and so on until we
get to the beginning of the string (the start of the array i.e. position 1.)
How do we know whether to add a # or a number? If the last element of the array added
was a #, the variable $wasHash
contains its' position. If it wasn't, $wasHash
= 0.
This is the Perl version. It is a straight copy of the Raku code.
my ($N) = @ARGV;
my $wasHash = 0;
my @hashCounting;
my $pos = $N;
while ($pos > 0) {
if ($wasHash) {
unshift @hashCounting, "$wasHash";
$pos -= length "$wasHash";
$wasHash = 0;
} else {
unshift @hashCounting, '#';
$wasHash = $pos;
$pos--;
}
}
say join q{}, @hashCounting;