Ryan Thompson › Perl Weekly Review: Challenge - #045

Saturday, Feb 8, 2020| Tags: perl

Welcome to the Perl review for Week 045 of the Weekly Challenge! For a quick overview, go through the original tasks and recap of the weekly challenge.

Continues from previous week.

Getting in Touch

Email › Email me (Ryan) with any feedback about this review.

GitHub › Submit a pull request for any issues you may find with this page.

Twitter › Join the discussion on Twitter!

We’d greatly appreciate any feedback you’d like to give.

Table of Contents



Task #1 › Square Secret Code


Original task description

The square 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

Figure 1 › Partitioned Plaintext

The code message would be as below:

tbjrd hruto eomhg qwpe unsl ifoa covz kxey

My general observations

There seem to be two main ways people approached this task:

1. Partitioning

By first splitting the plaintext into column-width substrings, you end up with Figure 1 (above). From there, you can simply append the first character of each string to your output, then the second character, and so on.

This method is perhaps the most obvious implementation of the problem description, as it follows the wording quite closely.

2. split and Modulo Arithmetic

For this method, you first split the plaintext into a list of chars. Then, you loop over the plaintext array, appending each character into its $index % $columns string in an array of @columns. Finally, you simply join the columns together.

This method is maybe a little less obvious, but lead to some concise solutions.


If my plain English descriptions don’t make complete sense yet, don’t worry; there will be plenty of code examples of both methods below.

Adam Russell

Adam Russell’s solution uses the split and modulo arithmetic method. He iterates over @characters to push characters into the array of arrays, @buckets:

use constant SQUARE_SIZE => 8;
sub encode{
    my($message) = @_;
    $message =~ tr/ //d;
    my $encoded;
    my @buckets;
    my @characters = split(//, lc($message));
    for my $i (0 .. @characters){
       $buckets[$i % SQUARE_SIZE] = [] if !$buckets[$i % SQUARE_SIZE];
       push @{$buckets[$i % SQUARE_SIZE]}, $characters[$i] if $characters[$i];
    }
    for my $bucket (@buckets){
        $encoded .= join("", @{$bucket}) . " ";
    }
    return $encoded;
}

The solution is then built up by joining each of the @buckets together, and appending the result to the $encoded output string, with the space to separate the columns.

BlogPerl Fun

Alicia Bielsa

Alicia Bielsa’s solution has an input loop that asks the user for a plaintext message. Her encodeMessage function uses the split and modulo method to encode the message:

sub encodeMessage {
    my $message = shift;
    my @aSubMessages = ();
    my $messageEncoded = '';
    $message =~ s%\s+%%g;
    my @aEncodedGroups = ();
    my @aMessage = split ('', $message);
    foreach my $indexMessage  (0..$#aMessage) {
        my $indexSubgroup = $indexMessage % $columnLength ;
        unless  (defined $aEncodedGroups[$indexSubgroup]) {
            $aEncodedGroups[$indexSubgroup] = '';
        }
        $aEncodedGroups[$indexSubgroup] .= $aMessage[$indexMessage]; #t
    }
    $messageEncoded = join(' ', @aEncodedGroups);
    return $messageEncoded ;
}

The $indexSubgroup = $indexMessage % $columnLength line is what sets up the proper index, so that the next character in @aMessage is appended to the correct column in @aEncodedGroups.

Also notable, is that Alicia is one of a few people who allows for a configurable column width, through the $columnLength variable, which is a thoughtful touch.

Andrezgz

Andrezgz’s solution first converts the input to lowercase, and then filters out any remaining non-lowercase characters (such as numerics, symbols, and spaces):

my $msg = join '',
          map {my $w = lc $_; $w =~ s/[[:^lower:]]//g; $w }
          @ARGV or die "USAGE: $0 <message>";

After that, the solution is built up in a %cols hash with modulo arithmetic, with one member for each column:

my %cols;
# each letter is appended to the corresponding column
$cols{$_ % 8} .= substr $msg, $_, 1 for (0 .. (length $msg) -1 );

# coded message is formed by printing each column string in order
print join ' ', map { $cols{$_} } sort keys %cols;

This is a really clean solution, and I particularly like the decision to filter out characters based on what is allowed, instead of what isn’t. When possible, this method typically leads to safer, less error-prone code, as the programmer doesn’t have to account for every possible thing a user might throw at them.

Arne Sommer

Arne has submitted excellent Raku solutions for all 45 weeks in the challenge so far, which is absolutely fantastic. But this is, I believe, the first week Arne has submitted a Perl solution since all the way back in Week 023. Welcome back!

Arne Sommer’s solution is straightforward:

my $string = $ARGV[0] || "The quick brown fox jumps over the lazy dog";
$string =~ tr/ //d;
my @a = split(//, lc $string);

@a.shift;
@a.pop;

for my $word (0 .. 7) {
  my $index = $word;
  while (1) {
    defined $a[$index]
      ? print $a[$index]
      : print(" ") && last;

   $index += 8;
  }
}
print "\n";

One minor issue, is that the @a.shift; and @a.pop; lines aren’t necessary, and in fact, these statements do nothing except some string concatenation in void context (which would have thrown a warning with use warnings enabled). On reading his blog, Arne was getting extra characters somehow, but that appears to no longer be the case, as this solution works perfectly. It’s also very easy to understand.

Again, I’m very glad to see Arne submitting Perl solutions, and hope to see more in the future! If you haven’t already been following his blog, please do so; it’s excellent.

BlogSquare Dumper with Raku

Athanasius

Athanasius’s solution also has an input loop, and the encoder uses the partitioning method:

sub encode {
    my  ($plain)  = @_;
    my   @rows;
    push @rows, substr($plain, 0, $COLUMNS, '') while $plain;
    my   $encoded = '';

    for my $col (0 .. $COLUMNS - 1) {
        $encoded .= ' ' if $encoded;

        for my $row (0 .. $#rows) {
            my $text  = $rows[$row];
            $encoded .= substr($text, $col, 1) if $col < length $text;
        }
    }

    return $encoded;
}

As you can see, @rows is built up by partitioning $plain into chunks of up to $COLUMN characters. (The fourth argument to substr ('') is the replacement text, so those chunks get removed during the loop). The next two nested loops build up $encoded character by character from the strings in @rows.

Athanasius wrote a decoder as well:

sub decode {
    my ($encoded) = @_;
    my  @rows     = split /\s+/, $encoded;
    my  $decoded  = '';

    for my $col (0 .. length($rows[0]) - 1) {
        $decoded .= substr($rows[$_], $col, 1) for 0 .. $#rows;
    }

    return $decoded;
}

The decoder is a little simpler as Athanasius was able to take advantage of the encoded format having spaces separating the rows, but the meat of the function is similar: the nested loops are essentially doing the inverse of what encode did, above.

Burkhard Nickels

Burkhard Nickels’ solution first splits the plaintext into a character array, @l. Then, Burkhard uses a nested loop that builds up the result ($coded_msg) by calculating the array indicies:

$msg =~ s/\s//g;
$msg = lc($msg);
my @l = split("",$msg);

my $coded_msg;
for(my $i=0; $i<=7; $i++) {
    for(my $j=$i; $j<=$#l; $j+=8) {
        $coded_msg .= $l[$j];
    }
    $coded_msg .= " ";
}
print "Coded   : $coded_msg\n";

The C-style for loops allow Burkhard to manipulate the starting index and increment of the inner loop, so $j will always be the next index to be appended to the result.

Python

Burkhard also submitted a Python solution. Here’s the encoder:

msg = re.sub('\s','',msg)
msg = msg.lower()
l = list(msg)

coded_msg = ""
for i in range(0,8):
    j = i
    e = len(l)
    while j < e:
        coded_msg += l[j]
        j += 8
    coded_msg += " "

print "Coded   : ", coded_msg

The Python code uses the same algorithm as the above Perl solution.

BlogSquare Secret Code

Cheok-Yin Fung

Cheok-Yin Fung’s solution decomposes the problem into three distinct parts. First, the plaintext is sanitized, and padded to be a multiple of 8 characters:

$msg = lc($msg);
$msg =~ s/\s//g; # &removespace($_);
$msg =~ s/\t//g;

#ADD WHITESPACE AT THE END SUCH THAT THE LINE HAS 8n CHARACTERS
my $copyoflengthofmsg = length($msg);
$msg .= " " x ($copyoflengthofmsg % 8);

Second, the plaintext is split into characters, and put into a two-dimensional array. $i and $j are maintained such that @a will have the characters in the same type of grid shown in the challenge description:

my $numberofcols = 8;

foreach my $char (split //, $msg) {
    $a[$i][$j] = $char;
        $copyoflengthofmsg++;
    $j++;
    if ($j==$numberofcols) {$j = 0; $i++;}
}

Finally, Cheok-Yin Fung iterates over the @a grid to print out the solution:

for $j (0..$numberofcols) {
    for $i (0..7) {
        $b[$p] = $a[$i][$j];
        if ($b[$p] ne " ") {print $b[$p];}
        $p++;
    }
    print " ";
}

Colin Crain

Colin Crain’s solution splits and uses modulo arithmetic:

## eliminate spaces and nonword chars, lowercase rest in one pass
$input = lc( join '', grep { /\w/ } split //, $input );

## create the output array data structure
push my @output, [] for (1..8);

## fill the output arrays
push $output[$_%8]->@*, substr $input, ($_), 1  for (0..(length $input) - 1);

## display the output arrays
say join ' ', map {join '', $_->@* } @output;

This is an example of a good way to use the post-deref syntax, in my opinion; it actually aids the comprehension in this case. Colin’s solution is compact, without being hard to follow.

Dave Cross

Dave Cross’ solution outputs the result in the loop body, rather than building up a string first, but the result is the same:

$msg =~ s/\s+//g;

my @lines = map { [ split // ] } $msg =~ /(.{1,8})/g;

for my $x (0 .. $#{$lines[0]}) {
  for my $y (0 .. $#lines) {
    print $lines[$y][$x] // '';
  }
  print ' ';
}

print "\n";

Dave partitions $msg into 8 character (or less) chunks with a /g regex in list context, so it can be sent straight to map, where it is then split into characters and stored in an array of arrays.

Dave Jacoby

Dave Jacoby’s solution first filters out any non-lowercase characters, and then starts partitioning $plaintext into chunks of 8 characters or less, using substr and regexp substitution:

sub encypher ( $plaintext ) {
    $plaintext = lc $plaintext;
    $plaintext =~ s/[^a-z]//gmx;
    my @work;

    while ( length $plaintext >= 8 ) {
        my $eight = substr $plaintext, 0, 8;
        $plaintext =~ s/\w{8}//mix;
        push @work, $eight;
    }
    push @work, $plaintext;

    my @cyphertext;

    for my $i ( 0 .. scalar @work - 1 ) {
        my $word = $work[$i];
        for my $j ( 0 .. length $word ) {
            my $letter = substr $word, $j, 1;
            next unless scalar $letter;
            $cyphertext[$j][$i] = $letter;
        }
    }

    return join ' ', map { join '', $_->@* } @cyphertext;
}

After that, @cyphertext is a two-dimensional array (array of arrays, or AoA) built up with a by now familiar-looking nested loop. The return is made by sending each top-level element of @cyphertext through map { join '', $_->@* } to turn the array into a string, and then those strings are joined by the leftmost join ' '.

BlogChallenge 45: Cyphers and Quines

Duane Powell

Duane Powell’s solution is concise and clear:

$code =~ s/ //g;
my @code = split(//,$code);

my @out;
my $m = 0;
$out[$m++ % $block] .= shift(@code) while (@code);
print "$_ " foreach (@out);
print "\n";

The line doing the heavy lifting, $out[$m++ % $block] .= shift(@code) while (@code) is another good example of how modulo arithmetic really makes light work of this problem.

Duncan C. White

Duncan C. White’s solution provides both an encoder and decoder, as well as a way to change the number of columns. The encoder uses the modulo arithmetic method, but replaces the split with repeated calls to substr $text, $pos, 1 to get one character at a time:

use Function::Parameters;

fun encode( $text )
{
    my @columns;
    $text =~ s/\s+//g;        # remove all whitespace
    my $len = length($text);
    foreach my $pos (0..$len-1) {
        my $ch = substr($text,$pos,1);
        $columns[$pos%$ncolumns] //= "";
        $columns[$pos%$ncolumns] .= $ch;
    }
    my $result = join( ' ', @columns );
    return $result;
}

The decoder splits the ciphertext on whitespace to obtain @columns. After that is a nested loop, whose body uses s/(^\w)// to trim (and capture) the first character from one of the @columns, appending it to the result:

fun decode( $text )
{
    my @columns = split( /\s+/, $text );
    my $ncols = @columns;
    die "decode: $text has $ncols columns, not $ncolumns\n"
        unless $ncols == $ncolumns;
    my $npasses = length( $columns[0] );
    my $result = "";
    foreach my $p (1..$npasses) {
        foreach (@columns) {
            if( $_ ) {
                s/(^\w)//;    # remove 1st char from column
                $result .= $1;
            }
        }
    }
    return $result;
}

E. Choroba

E. Choroba’s solution:

sub square_secret_code {
    my ($message) = @_;
    my @code = ("") x 8;
    for my $group (lc($message) =~ s/\s//gr =~ m/(.{1,8})/g) {
        $code[$_] .= (split //, $group)[$_] // "" for 0 .. 7;
    }
    return join ' ', @code
}

This solution is another example of using the /g modifier in list context. However, Choroba does one better and first removes spaces with the substitution s/\s//gr. The /r modifier is a personal favourite of mine, as instead of performing the substitution in place as usual, it returns a copy of the (modified) string instead. Without that, Choroba would have needed an extra couple of lines of code.

BlogPerl Weekly Challenge 045: Square Secret Code & Source Dumper

Jaldhar H. Vyas

Jaldhar H. Vyas’ solution chunks the plaintext into @rows of 8 characters, and then uses a nested loop to append the $ith character from each row to the $ith column. The solution is then just @cols, separated by spaces:

my $input = lc join q{ }, @ARGV;
$input =~ s/\s+//gmx;
my @rows;
while (length $input) {
    push @rows, substr $input, 0, 8, q{};
}

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

say join q{ }, @cols;

BlogPerl Weekly Challenge: Week 45

Javier Luque

Javier Luque’s solution is another concise one with modulo arithmetic:

$string =~ s/\s//g;
my @chars = split('', $string);
my @new_words;

for my $i (0..scalar(@chars)-1) {
    $new_words[$i % 8] .= $chars[$i];
}

say join ' ', @new_words;

BlogPERL WEEKLY CHALLENGE – 045 – Perl Weekly Challenge

Laurent Rosenfeld

Laurent Rosenfeld’s solution uses the partition approach, printing the output on the fly:

$msg =~ s/\s+//g;
my @letters = map { /.{1,8}/g; } $msg;
for my $i (0..7) {
    print map { substr  $_, $i, 1 if length $_ >= $i} @letters;
    print " ";
}

BlogPerl Weekly Challenge 45: Square Secret Code and Source Dumper

Maxim Kolodyazhny

Maxim Kolodyazhny’s solution is a unique one:

$_ = lc <>;
s/\s//g;

for my $i ( 0..7 ) {
    last if ( pos = $i ) == length;
    print
        $i ? ' ' : '',
        /(.).{0,7}/g;
}

Take careful note of Maxim’s assignment to pos, as that is the key to the entire solution. pos sets the offset of the regexp match, and the regexp /(.).{0,7}/g captures every 8th character from that offset. The offset is then incremented the next time through, and it repeats, and thus the columns are printed, one character at a time.

Maxim also included an external set of tests.

Nazareno Delucca

Nazareno Delucca’s solution uses partitioning, and also allows for a user-specified column width:

my $columns = shift || 8;
$message =~ s/\s+//g;
my @rows = unpack "(A$columns)*", lc $message;

foreach my $word ( @rows ){
    my @chars = split //, $word;
    push @matrix, \@chars;
}

for (0..$columns) {
    for my $row( @matrix ) {
        $code .= shift @$row || '';
    }
    $code .= " ";
}

print "$code\n";

The use of unpack is a good way to partition the string. Nazareno then splits the row strings to character arrays to simplify the following task of repeatedly peeling off the first character of each string.

Peter Scott

Peter Scott’s solution came as the following one-liner:

perl -E '$_ = lc shift; tr/a-z//cd; @x = split //; $c=7; while (@x){ for ($i = 0; $i <= $#x; $i += $c) { print splice @x, $i, 1} $c--; print " "} say ""' "The quick brown fox jumps over the lazy dog"

Adding some whitespace back in, we can see it is similar to the split-and-loop method, but is the only solution this week to use splice:

$_ = lc shift;
tr/a-z//cd;
@x = split //;
$c = 7;
while (@x) {
    for ($i = 0; $i <= $#x; $i += $c) {
        print splice @x, $i, 1
    }
    $c--;
    print " "
}

splice @x, $i, 1 is $x[$i] with the side effect of removing it from @x. Because the element is removed and the others shift positions, Peter compensates by decrementing $c after every column, since the interval between columns decreases by one.

Although splice will be slower, the effect is small (about 15% on a 60-character input string, increasing with length), and I do appreciate the alternative loop conditionals, here, compared to a purely arithmetic-indexed approach.

Rage311

Rage311’s solution uses the split/modulo method, and is beautifully concise:

my @input = split //, <<>> =~ s/\s+//gr;

my @words;
$words[$_ % 8] .= $input[$_] for 0..$#input;

say join ' ', @words;

This is, I believe, Rage311’s first Perl submission. Congrats! I hope to see a lot more like this one.

Rage311 also submitted a solution in Rust, using the same algorithm. Here it is, for all you Rust fans out there:

fn main() -> io::Result<()> {
    let mut buffer = String::new();
    io::stdin().read_to_string(&mut buffer)?;
    buffer = buffer.split_whitespace().collect();

    let mut final_words: Vec<String> = vec!["".to_string(); 8];

    for i in 0..buffer.len() {
        final_words[i % 8].push(buffer.chars().nth(i).unwrap());
    }

    println!("{}", final_words.join(" "));

    Ok(())
}

Roger Bell West

Roger Bell West’s solution uses core module Getopt::Std to accept an arbitrary column width:

use Getopt::Std;
my %o=(n => 8);
getopts('n:',\%o);

Then, after stripping spaces, his encoding looks like this:

my $l=length($in)-1;
my @out;
foreach my $c (0..$o{n}-1) {
    my $out;
    for (my $k=$c;$k<=$l;$k+=$o{n}) {
        $out.=substr($in,$k,1);
    }
    push @out,$out;
}
print join(' ',@out),"\n";

As you can see, Roger uses a C-style for loop so he can increment by the column width ($o{n}) to pull every $o{n}-th character into his output array. The outer loop shifts the offset ($c) every time through. If speed was the goal, this could be ported to a C XS module with just a few minor changes.

Ruben Westerberg

Ruben Westerberg’s solution avoids the need to deal with undefined values for strings that aren’t a multiple of 8 characters in length by padding the string with spaces:

$padded .= " " x (8+8-length($padded)%8);

Then, he partitions and splits the string into the @rows array-of-arrays, and iterates over it with a nested loop:

my @rows;
my $steps=length($padded)/ 8;
push @rows, [split "", substr $padded, $_*8,8] for (0..$steps-1);
my $out="";
for my $c (0..7) {
    for my $r (0..$steps-1) {
        $out.= join "",$rows[$r]->[$c];
    }
}
$out=~s/ +/ /g;
$out=~s/ $//;
print $out;

His padded string does require a bit of trimming at the end, but after that, all that needs be done is print $out. Nice.

Ryan Thompson

My solution uses the split/modulo method:

sub encode {
    local $_ = lc shift;
    s/\s//g;
    my ($i, @s);

    map { $s[$i++ % COLUMNS] .= $_ } split '';

    join ' ', @s;
}

The last two lines do most of the work. Had I been going for brevity, I might have combined the lc and substitution regex on the same line as split:

sub encode {
    my ($i, @s);
    $s[$i++ % COLUMNS] .= $_ for split '', lc shift =~ s/\s//gr;
    join ' ', @s
}

But I didn’t feel that helped the code, so I left it as separate statements. I would be happy with either, though.

BlogSquare Secret Code

Saif Ahmed

Saif Ahmed’s solution also supports arbitrary columns, and uses the partition method to chunk the plaintext into @splitChars before the familiar-looking nested loop builds up $result character by character:

sub pivotEncode{
   my $str=shift;
   my $cols=shift//8;
   $str=~s/\s//gm;                             # remove spaces
   @splitChars=($str=~/(.{$cols}|.+)/g);       # split into blocks
   my $result;                                 # initialise result
   foreach my $index (0..$cols-1) {            # now select character
      foreach my $row ( @splitChars ){         # in each block and
                                               # append it to result
        $result.= substr($row,$index,1) if ($index<length $row)
      }
      $result.=" ";                            #intersperse spaces
   }
   return $result;                             # return encrypted
}

Ulrich Rieke

Ulrich Rieke’s solution uses the partitioning method. I’ll take you through his encode function one step at a time. First, he partitions the string (including any remaining portion less than 8 characters long):

sub encode {
  my $str = shift ;
  my @strings ;
  my $times = int ( (length $str) / 8 ) ;
  my $pos = 0 ;
  for ( my $i = 0 ; $i < $times ; $i++ ) {
      push @strings, substr( $str, $pos , 8 ) ;
      $pos += 8 ;
  }

At this point, @strings contains one string for each row. Next, Ulrich uses a nested loop and substr($word, $i, 1) to build up $encoded character by character:

  push @strings , substr( $str, $pos ) ;
  my $encoded ;
  for ( my $i = 0 ; $i < 8 ; $i++ ) {
      for my $word ( @strings ) {
    my $len = length $word ;
    if ( $len > $i ) {
        $encoded .= substr( $word , $i , 1 ) ;
    }
      }
  }

By now, $encoded might look like Tbjrdhrutoeomhgqwpeunslifoacovzkxey, which is close, but needs spaces between the columns, which is what this next bit does, by splitting and then recombining via join ' ', @encodedStrings:

  my $stringslen = scalar @strings ;
  my $len = length $encoded ;
  $times =  length $strings[-1]  ;
  my @encodedStrings ;
  $pos = 0 ;
  for ( my $i = 0 ; $i < $times ; $i++ ) {
      push @encodedStrings, substr( $encoded , $pos , $stringslen ) ;
      $pos += $stringslen ;
  }
  my $theRest = 8 - $times ;
  for ( my $i = 0 ; $i < $theRest ; $i++ ) {
      push @encodedStrings , substr( $encoded , $pos , $stringslen - 1 ) ;
      $pos += $stringslen - 1 ;
  }
  return ( join ( ' ' , @encodedStrings ) ) ;
}

Wanderdoc

Wanderdoc’s solution uses Leon Timmermans’ Const::Fast in place of builtin constants:

use Const::Fast; # To use the constant in the regex.
const my $SECRET => 8;
const my $REGEX => qr/(.{1,${SECRET}})/;

Wanderdoc then uses the partitioning method to break up the string:

sub encoding_message {
     my $str = $_[0];
     $str =~ tr/ //ds;
     $str = lc $str;

     my @rows = map [split(//,$_)], ($str =~ /$REGEX/g);
     my @coded = map {
          my $idx = $_;
          my @slice = map $_->[$idx] // '', @rows; [@slice];
     } 0 .. $SECRET - 1;

     my $enc = join(' ', map join('',@$_), @coded);

     return $enc;
}

Notice that Wanderdoc uses a variation on the nested loop to create an array-of-arrays in @coded, that is then joined together for the solution.

Wanderdoc also provides a decoder, which looks very similar to the above portion of the encoder, but in reverse:

sub decoding_message {
     my $str = $_[0];
     my @words = map [split(//,$_)], split(' ', $str);
     my @txt = map {
          my $idx = $_;
          my @slice = map $_->[$idx] // '', @words; [@slice];
     } 0 .. $#words;

     my $dec = join('', map join('',@$_), @txt);
}


Task #2 - Source Dumper

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

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

There are two ways to interpret this problem, resulting in very different solutions. A straight reading of the challenge, with no additional constraints, means our script can simply read its own source file and print it. For example:

open my $fh, '<', __FILE__; # or $0
print <$fh>;

Most people did something similar to this, and these solutions certainly pass the challenge! However, a few of us noticed that this challenge sounded an awful lot like a quine, so we took it a step further for (in my case) the fun of it. Quines are computer programs that not only produce a copy of their own source code, but they also have the additional constraint of taking no input, meaning, reading your own source code is not allowed.

Adam Russell

Adam Russell’s solution is a proper quine, because it does not use any input (the last blank line is required):

print<<''x2,"\n"
print<<''x2,"\n"

I’ve previously seen this quine attributed to Robin Houston some years ago, and it’s long been one of my favourites, due to the clever use of heredocs.

BlogPerl Fun

Alicia Bielsa

Alicia Bielsa’s solution uses $0 to obtain the filename.

open (my $fh_file , '<', $0 ) or die "Error reading file";
while (my $line = <$fh_file>) {
     print $line;
}
close ($fh_file);

Using the three-argument open is always a good practice, so I’m glad to see Alicia use it here. It would, after all, be a terrible quine if your script was named rm -rf . | and used the two-argument open (please don’t try this.)

Andrezgz

Andrezgz’s solution is another proper quine. The whole thing is nearly 100 lines long, but it’s worth seeing, so I’ll trim it down a bit (mostly removing comments) and show what’s left, here. This should still be a quine, after my trimming:

use v5.10;

my @s = (
q&&,
q&say <<'EOT';&,
q&use v5.10;&,
q&EOT&,
q&&,
q&say 'my @s = (';&,
q&foreach my $line (@s) {&,
q&    say 'q'.chr(38).$line.chr(38).','&,
q&}&,
q&say ');';&,
q&&,
q&foreach my $line (@s) {&,
q&    say $line&,
q&}&,
q&&,
q&say <<'EOT';&,
q&__END__&,
q&&,
q&./ch-2.pl | diff - ch-2.pl&,
q&EOT&,
q&&,
);

say <<'EOT';
use v5.10;
EOT

say 'my @s = (';
foreach my $line (@s) {
    say 'q'.chr(38).$line.chr(38).','
}
say ');';

foreach my $line (@s) {
    say $line
}

say <<'EOT';
__END__

./ch-2.pl | diff - ch-2.pl
EOT

__END__

./ch-2.pl | diff - ch-2.pl

The solution works by using various quoting operators to embed a copy of the source code within the code itself, which is a common yet powerful way to generate a quine.

Arne Sommer

Arne Sommer’s solution uses $0 to get the script filename, and outputs it line by line:

my $file = $0;

if (open(my $fh, $file)) {
    while (my $row = <$fh>) {
        print $row;
    }
    close $fh;
}

Arne also submitted another solution, even more elegant than the first, which uses File::Slurper to read $0 instead:

use File::Slurper 'read_text';
print read_text($0);

By the way, if you haven’t already switched over to File::Slurper from File::Slurp, I encourage you to do so, as ::Slurper fixes a lot of the problems in ::Slurp, especially around the API and handling of encoding.

BlogSquare Dumper with Raku

Burkhard Nickels

Burkhard Nickels’ solution is a source code printer that has gone through some serious research and development! It not only prints its own source code, but it can print the source code of any file. And that’s not all: it can also highlight its own syntax with Text::VimColor, and even render HTML output if desired. It comes with full internal and external documentation, too.

I won’t quote the full source here, but I will share a few key parts. Here is the source code reader and argument processor:

if($ARGV[0] and $ARGV[0] eq "help") {
    ... # Full help text is here
}
elsif($ARGV[0] and $ARGV[0] eq "high") {
    syntax_high($0,$ARGV[1]);
}
elsif($ARGV[0]) {
    syntax_high(@ARGV);
}
else {
    open(IN,$0) or die "Cant open $0\n";
    while(<IN>) { print; }
    close IN;
}

As you can see, in all cases, the source code is read from $0, as we’ve seen with other solutions.

The syntax highlighting component (in the sub syntax_high()) shows just how easy it is to get Vim syntax highlighting in HTML using Text::VimColor:

    my $syntax = Text::VimColor->new(
        file => $file,
        filetype => 'perl',
        html_full_page => $full,
    );
    my $html = $syntax->html;

(Getting ANSI output instead would simply look like my $ansi = $syntax->ansi, according to the documentation.)

Burkhard also does a partial HTML parse with split to insert his own line numbering, because the Text::VimColor line numbering wasn’t working for him.

I always like to see solutions like this, that go far above and beyond the challenge, as there is almost guaranteed to be something interesting or unexpected.

Python

Burkhard submitted a Python solution for challenge #2 as well, which uses __file__ to get the script’s filename and then reads from that:

fh = open(__file__);
for line in fh:
    print(line),

fh.close

BlogSource Dumper

Colin Crain

Colin Crain’s solution reads the source code from $0, and prints it out, along with an observation about sections we’d not normally think of as code, such as the __DATA__ section:

local $/ = undef;
open (my $fh, "<", $0) or die "can't open this script thats running this code to read: $0 : $!";
print <$fh>;

__DATA__

even prints the data section, see?

Cristina Heredia

Cristina Heredia’s solution uses a call to system to run cat(1) to print the contents:

#Name of the script
my $program = 'ch-2.pl';
#Execute an unix command
system("cat $program");

Cristina opted to hard-code the name in $program. While this of course means the script can’t be renamed or run from a different directory, it does mean that the single-string call to system is safe from spaces and metacharacters in the filename, since $program is trusted.

The POSIX-only solution is fine by me. My only suggestion would be to switch to the system PROGRAM LIST form. The single-argument form (above) won’t work (and in fact could be vulnerable to abuse) if the script or given pathname contain spaces or shell metacharacters. This is better:

system cat => $0

You could even use exec here, as there is no need to ever regain control after cat is finished:

exec cat => $0

Dave Cross

Dave Cross’ solution exploits the fact that the DATA filehandle is already opened and pointed at the start of the __DATA__ block in the script (if the script has one), but you are still free to access the contents of the entire script, if you set the filehandle’s position with seek:

seek DATA, 0, 0;
print while <DATA>;
__END__

Dave Jacoby

Dave Jacoby’s solution uses Cwd‘s abs_path function to get the absolute path of the script first:

use Cwd qw{abs_path};

my $file = abs_path($0);
if ( -f $file && open my $fh, '<', $file ) {
    print join '', <$fh>;
}

From there, Dave does some error checking and uses the three-argument open, which is always a good idea. He prints the contents of the file with join '', <$fh>, which slurps the entire contents into a list which is then fed to join.

BlogChallenge 45: Cyphers and Quines

Duane Powell

Duane Powell’s solution also makes a system call to cat(1) to print the contents:

system("cat $0");

The same comments I made to Cristina’s use of the single-argument system apply here; system cat => $0 is preferable.

Duncan C. White

Duncan C. White’s solution uses $0 to obtain the script’s filename, and loops through the lines:

open( my $fh, '<', $0 ) || die "can't read $0\n";
while( <$fh> )
{
    chomp;
    say;
}

E. Choroba

E. Choroba’s solution is another Perl hacker who submitted a full quine:

$_=q!print'$_=q*'.$_.'*;';s/\52/\41/g;print!;print'$_=q!'.$_.'!;';s/\52/\41/g;print

This one again uses the trick of quoting operators to embed a copy of the source, and munging those quoting characters so they are escaped or output as needed. You might think tr/*/!/ would work, but it would transmute its own arguments in the quoted code, which is why the character codes in s/\52/\42/g are necessary.

According to Choroba’s blog, this is a quine he came up with, entirely of his own invention, when he started programming Perl, and has tweaked it over the years into the version you see now.

Covering all the bases, he submitted a file reading version as well:

seek *DATA, 0, 0;
print <DATA>
__DATA__

This one uses the DATA filehandle in a similar way as we saw with Dave Cross’ solution, above.

BlogPerl Weekly Challenge 045: Square Secret Code & Source Dumper

Jaldhar H. Vyas

Jaldhar H. Vyas’ solution reads its own source file in slurpy fashion, and uses English to aid comprehension:

use English qw/ -no_match_vars /;

open my $fh, '<', $PROGRAM_NAME or die "$OS_ERROR\n";
local $RS = undef;
print <$fh>;

English, a core module, adds plain English aliases for most of Perl’s punctuation variables, and manages to use three of them in just two lines of code:

  • $PROGRAM_NAME is $0, the script’s filename
  • $OS_ERROR is $!, errno error value (or description)
  • $RS is $/, the input record separator, usually newline

BlogPerl Weekly Challenge - Week 45

Javier Luque

Javier Luque’s solution reads the file in $0, with UTF-8 encoding for good measure:

open(my $fh, '<:encoding(UTF-8)', $0) || die "$@";
while (my $line = <$fh>) {
    print $line;
};

BlogPERL WEEKLY CHALLENGE – 045 – Perl Weekly Challenge

Laurent Rosenfeld

Laurent Rosenfeld’s solution is another example of reading one’s own source file via $0:

my $progr = "./$0";
open my $IN, "<", $progr or die "Unable to open $progr $!";
print while <$IN>;
close $IN;

BlogPerl Weekly Challenge 45: Square Secret Code and Source Dumper

Maxim Kolodyazhny

Maxim Kolodyazhny’s solution reads its own source file. You know those two-argument open calls I’ve been talking about? How about a one-argument open!

use strict;
use warnings;

open 0 and print <0>;

This might require a bit of explanation if you’ve never encountered a one-argument open. First, it’s important to know that there is no special magic with “0” going on, here. Normally open requires at least two arguments. The two-argument open expects a filehandle, and an expression containing the filename.

When open is given just one argument, though, Perl takes the filename from the package scalar with the same name as the filehandle. In this case that scalar is $0, which is of course the script filename. After that, the print <0> part is just a normal read from the filehandle glob named “0” created with open.

Don’t believe this works as I’ve described it? Try it with a different name:

$PROG = "$0";
open PROG and print <PROG>

Then make the first line a lexical: my $PROG = "$0" and now it doesn’t work. In fact, that’s part of the reason we avoid bareword scalars these days; they only work with package globals, so it’s not possible to give them lexical scope.

Nazareno Delucca

Nazareno Delucca’s solution uses the __FILE__ function introduced in Perl 5.16 to get the filename:

open(my $fh, "<", __FILE__) || die "Couldn't open $0 for reading because: $! ";
print <$fh>;
close($fh);

Rage311

Rage311’s solution reads the file in $0, complete with error handling and the three-argument open:

die "Unable to open file: $!" unless
  open my $fh, '<', $0;
print while <$fh>;

Roger Bell West

Roger Bell West’s solution eschews all modern Perl contrivances for the following concise solution:

open F,$0;
print <F>;

Ruben Westerberg

Ruben Westerberg’s solution shows that those modern features can look pretty, too:

open my $f,"<",$0;
print $_ for ( <$f> );

Ryan Thompson

I submitted three solutions this week, to showcase three different and progressively more difficult ways this challenge could be solved.

First up—and I can’t believe I actually committed this—an empty file works as a valid script. This arguably meets the letter of the challenge, but probably not the spirit. All serious definitions of quines regard this as cheating, too.

So the next best thing is my own version of the source file reader:

open my $fh, '<', __FILE__;
print do { undef $/; <$fh> };

I opted to slurp <$fh> into a string, although I really didn’t need to.

But my third solution, finally, is a real quine. The source code is not really human readable, but you can see it here. It was, as it turns out, human writeable, although I wouldn’t recommend replicating my methods.

Its output, when fed to a terminal, is displayed just a bit differently:

I <3 Perl

When fed to diff, diff will happily compare the output—including all of the ANSI escape codes—and report no differences.

For a more human-readable introduction to what I did, I started with the following quine:

$_=q<"ANSI ART HERE";print"\$_=q<$_>;eval\n">;eval

As discussed in my blog, I do not claim to have invented this quine, as it’s pretty difficult to come up with a completely novel quine after all these years. Similar versions are rather ubiquitious in Perl quine discussions, due to how powerful the quoting operators are in Perl.

I then got to work with my amazing ANSI art skills, at the position shown. Maybe I could get rich selling signed prints.

BlogQuine

Saif Ahmed

Saif Ahmed’s solution is another file read solution, with UTF-8 encoding:

open (my $fh, '<:encoding(UTF-8)', $0 ) ;
print while(<$fh>);

Ulrich Rieke

Ulrich Rieke’s solution reads the file via a filehandle glob:

open (FH , "< $0" ) or die "Can't open file $0!\n" ;
while ( <FH> ) {
  print ;
}
close (FH) ;

Wanderdoc

Wanderdoc’s solution reads $0:

open my $in, "<", $0 or die "$!";
for ( <$in> ) {print $_;}

And that’s it! Although a lot of these solutions were quite similar, it’s amazing how much variation in style one can achieve in just two or three lines.



SEE ALSO

Blogs this week:

Great to see more people blogging again this week! I always enjoy reading them.

Adam RussellPerl Fun

Arne SommerSquare Dumper with Raku

Burkhard NickelsSquare Secret Code | Source Dumper

Dave JacobyChallenge 45: Cyphers and Quines

E. ChorobaSquare Secret Code & Source Dumper

Jaldhar H. VyasPerl Weekly Challenge Week 45

Javier Luque045 – Perl Weekly Challenge

Laurent RosenfeldSquare Secret Code and Source Dumper

Luca FerrariEncoded messages and self-source-code-printing

Ryan ThompsonSquare Secret Code | Quine

SO WHAT DO YOU THINK ?

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

Contact with me