Perl Weekly Challenge: Week 45

Challenge 1:

Square Secret Code

The squate secret code mechanism first removes any space from the original message. Then it lays down the message in a row of 8 columns. The coded message is then obtained by reading down the columns going left to right.

For example, the message is “The quick brown fox jumps over the lazy dog”.

Then the message would be laid out as below:

thequick
brownfox
jumpsove
rthelazy
dog

The code message would be as below:

tbjrd hruto eomhg qwpe unsl ifoa covz kxey

Write a script that accepts a message from command line and prints the equivalent coded message.

Perl first. I joined up the command line arguments, made all the characters lower case and removed all the whitespace.

my $input = lc join q{ }, @ARGV;
$input =~ s/\s+//gmx;

Then I removed groups of up to 8 characters and put them into an array, @rows.

my @rows;
while (length $input) {
    push @rows, substr $input, 0, 8, q{};
}

Turning the rows into columns was a simple matter of splitting each row into an array and selecting the ith element of each array (assuming it exists; the last $row will have less than 8 elements.) and concatenating it into a string pushed into a new array, @cols.

my @cols;
for my $row (@rows) {
    my @chars = split //, $row;
    for my $i (0 .. 7) {
        if ($chars[$i]) {
            $cols[$i] .= $chars[$i];
        }
    }
}

The last step is to combine all the @cols into one string which is then printed out.

say join q{ }, @cols;

(Full code on Github.)

Initially, my Raku version was just a copy of the Perl code. But I wanted to see if I could make it more idiomatic. I even had dreams of making it a one-liner but eventually had to settle for three as we shall see.

This line does the job of concatenating the command-line args, making them lower case and removing whitespace.

my $input = @*ARGS.lc.join(q{ }).subst(/\s+/, q{}, :g);

I'm going to skip the second line for a moment and show you half of the third line.

([Z~] $input.comb.rotor(8))

This is some dense code. I think if this was a production script I'd probably split it into several steps for fear I (or the poor sap who will come after me) wouldn't remember what it does several weeks later. Anyway... .comb splits $input into an array of individual characters. .rotor(8) takes that array and breaks it up into an array of (up to) 8-element arrays. [Z~] is the zip hyperoperator. It interleaves the elements of the array of arrays I just created and applies the ~ (concatenation) operator to them. I.e. the first elements of each second level array are selected and then joined together and the result pushed into a new array, then the second elements of each second level array and so on. This new array represents the columns of our output. There's a problem though. If you look at the example message above, you'll see that the first three columns have five elements while the rest only have four. Z stops when it runs out of input so applying it to this input will give us:

'tbjr', 'hrut', 'eomh', 'qwpe', 'unsl', 'ifoa', 'covz', 'kxey'

which is not quite the right number. To get around this, I added padding to the input so its length will always be a multiple of 8. That's what line two does.

$input ~= q{ } x 8 - ($input.chars % 8); 

Now [Z~] will work properly and give us:

'tbjrd', 'hruto', 'eomhg', 'qwpe ', 'unsl ', 'ifoa ', 'covz ', 'kxey '

The second part of line three joins these groups into one string, squashes that extra padding we introduced, and prints the result.

    .join(q{ }).subst(/' '+/, q{ }, :g).say;

(Full code on Github.)

Challenge 2:

Source Dumper

Write a script that dumps its own source code. For example, say, the script name is ch-2.pl then the following command should returns nothing.

$ perl ch-2.pl | diff - ch-2.pl

In Perl, the name of the running program is stored in the system variable $0 or if you use English as I always do for clarity, $PROGRAM_NAME. We can use it to open a file handle to the program:

open my $fh, '<', $PROGRAM_NAME or die "$OS_ERROR\n";

Setting $/ (aka $RS in English) to undef lets the entire file be slurped up in one go: local $RS = undef;

...which is done here. The results are then printed out.

print <$fh>;

For completeness, we ought to close() the filehandle but in practice, the operating system will close it automatically when the script ends.

(Full Perl code on Github.)

The Raku version could almost be a one-liner. Unfortunately it seems $*PROGRAM (Raku's equivalent of $PROGRAM_NAME) is not set correctly in one-liners so I had to make this a script.

open(:r, $*PROGRAM).slurp.print;

(Full Raku code on Github.)

After I wrote the above, I discovered that $*PROGRAM actually contains the entire path to the script. For an exact equivalent to Perl, there is $*PROGRAM-NAME.