Perl Weekly Challenge 102.
My solutions (task 1, task 1a, task 1b, task 1c and task 2) to the The Weekly Challenge - 102.
Task 1: Rare Numbers
Submitted by: Mohammad S Anwar 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
A rare number x=x1x2x3…xN (with decimal digits x1, x2,…) is defined
such that x+y and x-y are perfect squares, where y=y1y2…=xN…x3x2x1. A
straightforward solution would be to generate
and test all $N
digit numbers for rareness. Afterwards I try other alternatives.
# Perl weekly challenge 102
# Task 1: Rare numbers
# Slowest, simplest version
#
# See https://wlmb.github.io/2021/03/01/PWC102/#task-1-rare-numbers
use strict;
use warnings;
use v5.12;
use POSIX qw(floor);
foreach my $N(@ARGV){
foreach my $x(10**($N-1)..10**$N-1){
# next if $x%10==0; #uncomment to disallow reversed numbers starting in 0
my $y=join '', reverse split '', $x;
next if $x<=$y; #use < to allow palindromes
my $s=$x+$y;
next unless $s==floor(sqrt($s))**2; # test squareness of sum
my $d=$x-$y;
next unless $d==floor(sqrt($d))**2; # test squareness of diff
say "N=$N\tx=$x\ty=$y\tx+y=$s=", sqrt($s),"**2\tx-y=$d=",sqrt($d),"**2";
}
}
Example:
(time ./ch-1.pl 2 3 4 5 6 7 8) 2>&1
Results:
N=2 x=65 y=56 x+y=121=11**2 x-y=9=3**2
N=6 x=621770 y=077126 x+y=698896=836**2 x-y=544644=738**2
real 1m45.232s
user 1m45.207s
sys 0m0.004s
The time grows very fast with the number of digits. For example:
(time ./ch-1.pl 9) 2>&1
Results:
N=9 x=281089082 y=280980182 x+y=562069264=23708**2 x-y=108900=330**2
real 17m9.807s
user 17m9.389s
sys 0m0.032s
To make the calculation slightly faster I use some of the stated properties of rare numbers, so that I don’t make an exhaustive search..
# Perl weekly challenge 102
# Task 1: Rare numbers
# Slightly faster through a reduction of the search space, but more elaborate.
#
# See https://wlmb.github.io/2021/03/01/PWC102/#task-2-rare-numbers
use strict;
use warnings;
use v5.12;
use POSIX qw(floor);
my %shown; # to avoid showing repetitions
for my $N(@ARGV){
my($A, $B, $P, $Q); #first, second, next to last and last digits
($A, $Q)=(2,2); #explore possible combinations
for $B(0..9){
search($N, $A, $B, $B, $Q);
}
($A, $Q)=(4,0);
for $B(0..9){
for $P(map {($B+2*$_)%10} (0..4)){
search($N, $A, $B, $P, $Q);
}
}
$A=6;
for $Q(0,5){
for $B(0..9){
for $P(map {($B+1+2*$_)%10} (0..4)){
search($N, $A, $B, $P, $Q);
}
}
}
($A, $Q)=(8, 2);
for $B(0..9){
$P=9-$B;
search($N, $A, $B, $P, $Q);
}
($A, $Q)=(8, 3);
for $B(0..9){
$P=($B-7)%10;
search($N, $A, $B, $P, $Q);
}
($A, $Q)=(8, 7);
for $B(0..9){
$P=(1-$B)%10;
search($N, $A, $B, $P, $Q);
}
($A, $Q)=(8, 8);
for $B(0..9){
$P=$B;
search($N, $A, $B, $P, $Q);
}
Next I write routines to build and test strings of length $N starting
with the digits "$A$B"
and ending in the digits "$P$Q"
.
sub search{ #search $N digit numbers given first and last digits
my ($N, $A, $B, $P, $Q)=@_;
# return if $N<=3;
test(join '', $A, $Q) if $N==2;
test(join '', $A, $B, $Q) if $N==3;
test(join '', $A, $P, $Q) if $N==3 && $P!=$B;
test(join '', $A, $B, $P, $Q) if $N==4;
return unless $N>4;
my $format="%0".($N-4)."d";
for my $m(map {sprintf($format, $_)} (0..10**($N-4)-1)){ #generate middle digits
test(join '',$A, $B, $m, $P, $Q);
}
}
sub test { #test and report the single number
my $x=shift;
return if $shown{$x};
# return if $x%10==0; #uncomment to disallow reversed numbers starting in 0
my $y=join '', reverse split '', $x;
return if $x<=$y; #use < to allow palindromes
my $s=$x+$y;
return unless $s==floor(sqrt($s))**2; # test squareness of sum
my $d=$x-$y;
return unless $d==floor(sqrt($d))**2; # test squareness of diff
++$shown{$x};
my $N=length $x;
say "N=$N\tx=$x\ty=$y\tx+y=$s=", sqrt($s),"**2\tx-y=$d=",sqrt($d),"**2";
}
}
Examples:
(time ./ch-1a.pl 2 3 4 5 6 7 8 9) 2>&1
Results:
N=2 x=65 y=56 x+y=121=11**2 x-y=9=3**2
N=6 x=621770 y=077126 x+y=698896=836**2 x-y=544644=738**2
N=9 x=281089082 y=280980182 x+y=562069264=23708**2 x-y=108900=330**2
real 0m42.192s
user 0m42.117s
sys 0m0.020s
Much faster, but time would explode anyway for slightly larger N
.
I guess it would be
better to look for numbers whose sum and difference yields squares
and afterwards test their digit for mirrorness.
If x+y=a2 and
x-y=b2, then x=(a2+b2)/2 and y=(a2-b2)/2.
# Perl weekly challenge 102
# Task 1: Rare numbers
# Slightly faster through a reduction of the search space, but more elaborate.
#
# See https://wlmb.github.io/2021/03/01/PWC102/#task-2-rare-numbers
use strict;
use warnings;
use v5.12;
use integer;
use POSIX qw(floor);
for my $N(@ARGV){
my ($min, $max)=(10**($N-1), 10**$N-1);
A:
for my $a(floor(sqrt($min))..floor(sqrt(2*$max))){
B:
for my $b(1..$a){
my $x=($a**2+$b**2);
next B unless $x%2==0;
$x/=2;
next B if $x<$min;
next A if $x>$max;
my $y=join '', reverse split '', $x;
next B unless $y==($a**2-$b**2)/2;
my ($s, $d)=($x+$y, $x-$y);
say "N=$N\tx=$x\ty=$y\tx+y=$s=", sqrt($s),"**2\tx-y=$d=",sqrt($d),"**2";
}
}
}
Examples:
(time ./ch-1b.pl 2 3 4 5 6 7 8) 2>&1
Results:
N=2 x=65 y=56 x+y=121=11**2 x-y=9=3**2
N=6 x=621770 y=077126 x+y=698896=836**2 x-y=544644=738**2
real 0m58.977s
user 0m58.950s
sys 0m0.008s
Another example:
(time ./ch-1b.pl 9) 2>&1
Results:
N=9 x=281089082 y=280980182 x+y=562069264=23708**2 x-y=108900=330**2
real 9m46.437s
user 9m45.803s
sys 0m0.036s
So it seems that this method is faster than the first one, but slower than the second one. The loops are shorter due to the square root, but there are two nested loops, so the number of iterations is of the same order.
I make a new attempt. Consider N-digit numbers x=xlxr and y=x’rx’l, where I assume N is even, I write x and y in terms of N/2-digit numbers, and the primes indicate inverted digits. If x+y and x-y are squares, then so are their right halves xr+x’l and xr-x’l, where I all operations are done modulo 10N/2. Thus I can find candidates, hopefully not too many, for x and y by exploring the much smaller space of N/2-digit numbers. The logic of the resulting program is not too complex. First I make a list of squares modulo 10N/2. Then I use it to obtain possible numbers xr and x’l. With them I build x and y and finally test them. If N is not even, I add a single digit in the middle.
# Perl weekly challenge 102
# Task 1: Rare numbers
# Faster by breaking the number at the middle.
use strict;
use warnings;
use v5.12;
use POSIX qw(floor);
foreach my $N(@ARGV){
my $min=10**($N-1);
my $N2=floor($N/2);
my $M=10**$N2;
my %seen; # disctint squares mod $M
foreach my $a(0..10**$N2){
$seen{($a**2)%$M}=1;
}
my @squares=sort {$a<=>$b} keys %seen;
foreach my $a2(@squares){
foreach my $b2(@squares){
my $xr=($a2+$b2);
next unless $xr%2==0;
$xr=sprintf("%0${N2}d",($xr/2)%$M);
my $xl1=sprintf("%0${N2}d",(($a2-$b2)/2)%$M);
foreach my $mid($N%2==0?(''):(0..9)){
my $x=join '', reverse(split '', $xl1), $mid, $xr;
next unless $x>=$min;
my $y=join '', reverse(split '', $x);
next unless $x>$y;
my $s=$x+$y;
my $d=$x-$y;
next unless floor(sqrt($s))**2==$s;
next unless floor(sqrt($d))**2==$d;
say "N=$N\tx=$x\ty=$y\tx+y=$s=", sqrt($s),"**2\tx-y=$d=",sqrt($d),"**2";
}
}
}
}
Examples:
(time ./ch-1c.pl 2 3 4 5 6 7 8 9) 2>&1
Results:
N=2 x=65 y=56 x+y=121=11**2 x-y=9=3**2
N=6 x=621770 y=077126 x+y=698896=836**2 x-y=544644=738**2
N=9 x=281089082 y=280980182 x+y=562069264=23708**2 x-y=108900=330**2
real 0m8.915s
user 0m8.911s
sys 0m0.004s
Another example:
(time ./ch-1c.pl 10) 2>&1
Results:
N=10 x=2042832002 y=2002382402 x+y=4045214404=63602**2 x-y=40449600=6360**2
N=10 x=2022652202 y=2022562202 x+y=4045214404=63602**2 x-y=90000=300**2
real 1m38.120s
user 1m38.111s
sys 0m0.000s
This seems to be a big improvement compared to the previous codes. I even got the fourth and fifth numbers in the series. The time still grows exponentially but with smaller factors, and the code seems somewhat cleaner, but I didn’t have patience to wait for the sixth number.
Task 2: Hash-Counting String
Submitted by: Stuart Little 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 seems to be very straightforward. I build the pieces of the string from the end towards the beginning by appending a hash mark to its position and updating the position for the successive pieces.
# Perl weekly challenge 102
# Task 2: Hash-counting string
#
# See https://wlmb.github.io/2021/03/01/PWC102/#task-2-hash-counting-string
use strict;
use warnings;
use v5.12;
foreach my $length(@ARGV){
my $remaining=$length;
my @pieces;
while($remaining>0){
unshift @pieces, $remaining==1?"#":"$remaining#";
$remaining-=length $pieces[0];
}
say "Length: $length\tString: ", join '', @pieces;
}
Examples:
./ch-2.pl 1 2 3 10 14
Results:
Length: 1 String: #
Length: 2 String: 2#
Length: 3 String: #3#
Length: 10 String: #3#5#7#10#
Length: 14 String: 2#4#6#8#11#14#