BLOG: The Weekly Challenge #069

Sunday, Jul 19, 2020| Tags: Perl, Raku The Task #1 of Perl Weekly Challenge - 069 raised a very interesting question i.e. is 1 strobogrammatic number? So far, I got mixed response some says yes and some not. My first thought was Yes, it is but later changed my mind. It is controversial and I don’t want loose the focus on the task itself. Please remember the objective is to have fun and not to get into controversial domain. I am also very flexible and not tied to any thing. Some even discussed that “upside down” is not same as “180 degree rotation”. I am staying away from it. I like the open culture of Perl Weekly Challenge as you are free to take the route you are comfortable with. There are no compulsion.

The Task #2 posed another challenge where getting the 0/1 string S1000 generating seems nearly impossible running on regalar machine. Midweek, it was dropped down to S30 to make it possible. Even then it took a long time and generated a very very large 0/1 string.

I have made videos of Live Coding for this week tasks in Perl.

Let me share my solutions to the Perl Weekly Challenge - 069.

A strobogrammatic number is a number that looks the same when looked at upside down.

You are given two positive numbers \$A and \$B such that 1 <= \$A <= \$B <= 10^15.

Write a script to print all strobogrammatic numbers between the given two numbers.

For the Task #1, I came up with sub strobogrammatic_numbers(), which takes 2-parameters \$start and \$stop. It assumes, 6, 8 and 9 are the only digits that can be part of any strobogrammatic_numbers. After submitting my solutions, I came to know about 0 being one more such digit. I didn’t bother changing the code. However it would be one-line change if I wanted to include 0 like below.

Replace

my %digits = (6 => 9, 8 => 8, 9 => 6);

with

my %digits = (0 => 0, 6 => 9, 8 => 8, 9 => 6);

and job done.

In the sub below, I loop through \$start and \$stop. In each loop, I split the number and compare against the keys in the %digits. If I don’t find the split digit in the %digits then I move on the next number in the list. Once all split digits matched with the keys in the %digits, then I compare against the reverse of the mapped digits. If they are same then it is my strobogrammatic number I am after.

sub strobogrammatic_numbers {
my (\$start, \$stop) = @_;

die "ERROR: Missing start number.\n"
unless defined \$start;
die "ERROR: Missing stop  number.\n"
unless defined \$stop;
die "ERROR: Invalid start number [\$start].\n"
unless (\$start >= 1);
die "ERROR: Invalid end number [\$stop].\n"
unless (\$stop >= 1);
die "ERROR: Invalid start number [\$start].\n"
unless (\$start < \$stop);

my %digits = (6 => 9, 8 => 8, 9 => 6);

my @strobogrammatic = ();
foreach my \$n (\$start .. \$stop) {
next if (\$n < 10);

my \$found = 1;
my @match = ();
foreach my \$i (split //, \$n) {
if (exists \$digits{\$i}) {
push @match, \$digits{\$i};
}
else {
\$found = 0;
last;
}
}

if (\$found) {
push @strobogrammatic, \$n
if (\$n == join('', reverse @match));
}
}

return @strobogrammatic;
}

I made controversial assumption in the subroutine above by excluding single digit number. I now realise it is being very rigid. To include single digit should not be too difficult. I can acheive this by dropping the line below:

next if (\$n < 10);

Time to get some Raku magic. Thanks to the weekly challenge, I get to practice what I learn in Raku. So what did I learn new? Honestly speaking nothing new but got to practice what I knew already, which is fun as well. I always try my best to make my code not look like Perl.

if %digits{\$i}:exists {

For my fellow Perl hacker, the above line checks if \$i exists in the hash %digits.

@match.push: %digits{\$i};

The line above pushed the mapped digit %digits{\$i} to the list @match.

if \$n == @match.join('').flip;

Now this compares the number \$n with the reverse of the joined digits of the list @match.

I am not sure if you noticed the boolean data True and False used with the variable \$found. I love it. I wish Perl had the same.

Please find below the complete definition of sub strobogrammatic-numbers():

sub strobogrammatic-numbers(\$start, \$stop) {
die "ERROR: Invalid start number [\$start].\n"
unless \$start < \$stop;

my %digits = (6 => 9, 8 => 8, 9 => 6);

my @strobogrammatic = ();
for \$start .. \$stop -> \$n {
next if \$n < 10;

my \$found = True;
my @match = ();
for \$n.comb -> \$i {
if %digits{\$i}:exists {
@match.push: %digits{\$i};
}
else {
\$found = False;
last;
}
}

if \$found {
@strobogrammatic.push: \$n
if \$n == @match.join('').flip;
}
}

return @strobogrammatic;
}

Let’s solve the task in Perl first with the help of sub strobogrammatic_numbers() we created above.

my \$A = \$ARGV || 50;
my \$B = \$ARGV || 100;

print sprintf("[%s]\n", join ', ', strobogrammatic_numbers(\$A, \$B));

How about doing the same in Raku? I love the power of MAIN(), you can do so much with it. I try to put in as much parameter validation as possible in there.

use v6.d;

sub MAIN(Int :\$A where { \$A >= 1 } = 50,
Int :\$B where { \$B >= 1 } = 100) {
strobogrammatic-numbers(\$A, \$B).join(', ').say;
}

Being a fan of TDD, I try to get unit test done as well.

use Test::More;
use Test::Deep;

is_deeply( [ strobogrammatic_numbers(50, 100) ],
[ 69, 88, 96 ],
'testing A=50, B = 100' );

Raku unit test is fun as well.

use Test;

is-deeply strobogrammatic-numbers(50, 100),
(69, 88, 96),
'testing \$A=50, \$B=100';

done-testing;

A 0/1 string is a string in which every character is either 0 or 1.

Write a script to perform switch and reverse to generate S30 as described below:

switch:

Every 0 becomes 1 and every 1 becomes 0. For example, “101” becomes “010”.

reverse:

The string is reversed. For example, "001” becomes “100”.

With that, I didn’t bother getting S1000 generated.

Instead I only attempted to get S30 0/1 string.

sub string_0_1 {
my (\$string) = @_;

die "ERROR: Missing string.\n"
unless defined \$string;
die "ERROR: Invalid string [\$string].\n"
unless (\$string =~ /s\d+/i);

if (\$string =~ /(\d+)/) {
my \$limit = \$1;
die "ERROR: Invalid string [\$string]. S30 is the limit.\n"
if (\$limit > 30);

my \$string_0_1 = '';
foreach (1 .. \$limit) {
my \$_string_0_1 =  reverse \$string_0_1;
\$_string_0_1    =~ tr///;
\$_string_0_1    =  '0'. \$_string_0_1;
\$string_0_1     =  \$string_0_1 . \$_string_0_1;
}

return \$string_0_1;
}
}

As you know, I try to translate the Perl solution when it comes to do Raku. I had hard time to find the Raku version of the following line:

if (\$string =~ /(\d+)/) {

My Raku regex knowledge is limited, so I google it but couln’t find any solution. At this point, I thought of asking the Raku experts on the @PerlWChallenge twitter handle. Then I found this in the end.

my \$limit = .Int for \$string ~~ m/(\d+)/;

I am not impressed with the solution, though.

I am sure there must be a better solution out there.

sub string_0_1(\$string) {

my \$limit = .Int for \$string ~~ m/(\d+)/;
die "ERROR: Invalid string [\$string]. S30 is the limit.\n"
if \$limit > 30;

my \$string_0_1 = '';
for 1 .. \$limit {
my \$_string_0_1 =  \$string_0_1.flip;
\$_string_0_1    ~~ tr///;
\$_string_0_1    =  '0' ~ \$_string_0_1;
\$string_0_1     =  \$string_0_1 ~ \$_string_0_1;
}

return \$string_0_1;
}

Time to get the task done in Perl first.

use strict;
use warnings;

my \$string = \$ARGV;
print sprintf("%s\n%s\n", \$string, string_0_1(\$string));

and in Raku now. I love the parameter validation feature of MAIN()

use v6.d;

sub MAIN(Str :\$string where { \$string ~~ m:i/^s\d+\$/ } = 'S5') {
string_0_1(\$string).say;
}

Quick unit test in Perl, kept it simple.

use Test::More;

is(string_0_1('S2'), '001',             'testing S2');
is(string_0_1('S3'), '0010011',         'testing S3');
is(string_0_1('S4'), '001001100011011', 'testing S4');

done_testing;

Similar unit test in Raku.

use Test;

is string_0_1('S2'), '001',             'testing S2';
is string_0_1('S3'), '0010011',         'testing S3';
is string_0_1('S4'), '001001100011011', 'testing S4';

done-testing;

That’s it for this week. Speak to you soon.

SO WHAT DO YOU THINK ?

If you have any suggestions or ideas then please do share with us.