Perl Weekly Challenge # 11: Fahrenheit Celsius and Identity Matrix

These are some answers to the Week 11 of the Perl Weekly Challenge organized by Mohammad S. Anwar.

Spoiler Alert: This weekly challenge deadline is due in several days from now (June 9, 2019). This blog post offers some solutions to this challenge, please don't read on if you intend to complete the challenge on your own.

Challenge # 1: Fahrenheit and Celsius Scales Intersection

Write a script that computes the equal point in the Fahrenheit and Celsius scales, knowing that the freezing point of water is 32 °F and 0 °C, and that the boiling point of water is 212 °F and 100 °C. This challenge was proposed by Laurent Rosenfeld.

While it is very easy to do the math by hand, find that $Tf = 9/5 * $Tc + 32 and then solve the linear equation x = 9/5 x + 32, it turns out that it is a little bit more complex to write a program doing that with a general purpose programming language. I guess it is probably easy with symbolic or algebraic computation software such as Maple, Mathematica, or Scilab. I'm working on a solution that might do that, but I'm not sure when it will be ready (and even whether it will work out the way I hope). Here we'll use successive numerical approximations.

Fahrenheit Celsius in Perl 5

In the code below, the $speed_diff variable measures the difference of variation between the two scales. It is the coefficient of the slope of the straight line representing one scale compared to another in a graphic representation (and its value is 9/5, or 1.8). In other word, if you move on the Celsius scale by 1 °, you have to move on the Fahrenheit scale by 1.8 °.

use strict;
use warnings;
use feature qw/say/;

my %freeze = (F => 32, C => 0);
my %boil = (F => 212, C => 100);
my $speed_diff = ($boil{F} - $freeze{F} ) /($boil{C} - $freeze{C} ) ;

my $c = $freeze{C};
my $f = $freeze{F};

while (abs($c - $f) > 0.2) {
    $f += ($c - $f)/2;
    $c +=  ( ($c - $f) / $speed_diff );
    # say "Approx: $c $f ", $c-$f; # De-comment this line to see the intermediate results 
} 
printf "Fahrenheit and Celsius scales meet at: %.0f\n", $c;

The program displays the following final result (after about 20 iterations):

$ perl Fahrenheit.pl
Fahrenheit and Celsius scales meet at: -40

Note that we use two hashes to store the input values without hard-coding them, but we could have used 4 variables.

Fahrenheit Celsius in Perl 6

Translating the same program in Perl 6 is easy:

use v6;

#`{ Write a script that computes the equal point in the Fahrenheit 
    and Celsius scales, knowing that the freezing point of water 
    is 32 °F and 0 °C, and that the boiling point of water is 212 °F 
    and 100 °C.
  }


my %freeze = (F => 32, C => 0);
my %boil = (F => 212, C => 100);
my $speed_diff = (%boil<F> - %freeze<F> ) / (%boil<C> - %freeze<C> );

my $c = %freeze<C>;
my $f = %freeze<F>;

while abs($c - $f) > 0.2 {
    $f += ($c - $f)/2;
    $c +=  ( ($c - $f) / $speed_diff );
    # say "Approx: $c $f ", $c-$f;
} 
say "Fahrenheit and Celsius scales meet at: ", $c.fmt("%.0f");

And we obtain the same value as in P5 (-40, i.e. -40 °C and -40 °F).

Note that Perl 6 has an approximately-equal operator (=~=) that could have been used in the while loop for comparing $c and $f, but using it would require to first set the tolerance value, so the benefit is limited when used only once. This part of the program could look like this:

{
    my $*TOLERANCE = .01;
    while not $c =~= $f  {
        $f += ($c - $f)/2;
        $c +=  ( ($c - $f) / $speed_diff );
        # say "Approx: $c $f ", $c-$f; 
    } 
}

The $*TOLERANCE value is relative tolerance, not absolute tolerance, so I had to use another value (1%) than the absolute tolerance hard-coded in the original while loop. In brief, using this functionality does not simplify the code for the case in point.

Challenge # 2: Identity Matrix

Write a script to create an Identity Matrix for the given size. For example, if the size is 4, then create Identity Matrix 4x4. For more information about Identity Matrix, please read the Wikipedia page.

An identity matrix is a square matrix with ones on the main diagonal (top left to bottom right) and zeroes everywhere else.

Identity Matrix in Perl 5

Let's start with a boring plain-vanilla solution using two nested loops, as I would probably do in C, Pascal, Ada, or Java:

use strict;
use warnings;
use feature qw/say/;
use Data::Dumper;

my $size = shift() - 1;
my @matrix;
for my $i (0..$size) {
    for my $j (0..$size-1) {
        if ($i == $j) {
            $matrix[$i][$j] = 1;
        } else {
            $matrix[$i][$j] = 0;
        }
    }
}
say Dumper \@matrix;

When the row index ($i) is equal to the column index ($j), we populate the item with a one. Otherwise, we fill with zeroes.

This program displays the following:

$ perl matrix.pl 3
$VAR1 = [
          [
            1,
            0,
            0
          ],
          [
            0,
            1,
            0
          ],
          [
            0,
            0,
            1
          ]
        ];

One problem here is that the Data::Dumper module is good to show the data structure in general, but it requires here some careful check to see that this is an identity matrix.

Let's improve our script by adding a subroutine to pretty print our matrix. While we are at it, let's also simplify the code by replacing the if ... else conditional with a ternary operator:

use strict;
use warnings;
use feature qw/say/;

sub pretty_print {
    my $matrix_ref = shift;
    for my $rows (@$matrix_ref) {
        say join " ", @$rows;
    }
}

my $size = shift() - 1;
my @matrix;
for my $i (0..$size) {
    for my $j (0..$size) {
        $matrix[$i][$j] = $i == $j ? 1 : 0;
    }
}
pretty_print \@matrix;

Now, the output really looks like an identity matrix:

$ perl matrix.pl 4
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1

We could further simplify the code of the nested loops:

for my $i (0..$size) {
    $matrix[$i][$_] = $i == $_ ? 1 : 0 for 0..$size;
}

But that simplification is only mildly interesting. Perhaps, we can do better with the functional paradigm.

Identity Matrix in Functional Perl 5

In my Perl Weekly Challenge # 9: Square Numbers and Functional Programming in Perl blog post on May 26, 2019, I showed some ways to use the functional programming style both in Perl 5 and Perl 6, especially the data stream or data pipeline model. Remember especially that map takes a list of items (e.g. an array) as parameter, applies some transformation to each item, and returns the list of modify items.

Let's use the same techniques for the identity matrix challenge:

#!/usr/bin/perl
use strict;
use warnings;
use feature qw/say/;

sub prettify {
    my $matrix = shift;
    return join "\n", map { join " ", @$_ } @$matrix;
}

my $size = shift() - 1;
my @matrix = map { my $i = $_; [map { $i == $_ ? 1 : 0 } 0..$size] }
    0..$size;
say prettify \@matrix;

Note that, since we're aiming at doing functional programming, we have replaced the pretty_print subroutine by a pure function, prettify, with no side effect: it does not print anything, but only returns a printable formatted string, which the user may then decide to print (as we did here), or may possibly do something else, such as storing it into a file for future use.

The script displays the same formatted output as before:

$ perl matrix.pl 5
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
0 0 0 0 1

Identity Matrix in Perl 6

I was initially tempted to provide only a functional implementation in Perl 6, but for the benefit of Perl 6 beginners who might read this blog post, I'll start with the plain-vanilla nested loops as in P5:

use v6;

sub pretty-print (@matrix) {
    for @matrix -> @rows {
        say join " ", @rows;
    }
}
sub MAIN (Int $size where * > 0) {
    my @matrix;
    $size--;
    for 0..$size -> $i {
        for 0..$size -> $j {
            @matrix[$i][$j] = $i == $j ?? 1 !! 0;
        }
    }
    pretty-print @matrix;
}

Here, we are using the MAIN subroutine to process the argument passed to the script (which, in this case, must be a strictly positive integer).

The output is the same as in P5:

~ perl6 matrix.p6 5
1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 1 0
0 0 0 0 1

And if the user omits to pass a parameter (or passes an invalid one), a convenient usage line is printed to the screen:

~ perl6 matrix.p6
Usage:
  matrix.p6 <size>

Also note that we don't need in Perl 6 the syntax complications associated with references and dereferencing in P5.

Identity Matrix in Functional Perl 6

A functional implementation in P6 may look like this:

use v6;

sub prettify (@matrix) {
    return join "\n", map { join " ", $_}, @matrix;
}
sub MAIN (Int $size where * > 0) {
    my @matrix = map { my $i = $_; map { $i == $_ ?? 1 !! 0 }, 
        0..$size },  0..$size;
    say prettify @matrix;
}

Note that the populating the identity matrix takes only one code line. The rest is really for prettifying the result and printing it.

We could also use data pipelines with chained method invocations:

sub prettify (@matrix) {
    @matrix.map({join(" ",$_)}).join("\n");
}
sub MAIN (Int $size where * > 0) {
    say prettify (1..$size).map( -> $i
         { (1..$size).map( { $_ == $i ?? 1 !! 0 })});
}

although I'm not really convinced this is any better, as it is a bit difficult to get these pesky closing parentheses and curly brackets right.

Wrapping up

There was a third challenge this week: Using Open Weather Map API, write a script to fetch the current weather for an arbitrary city. Note that you will need to sign up for Open Weather Map’s free tier and then wait a couple hours before your API key will be valid. This challenge was proposed by Joelle Maslak. The API challenge is optional but would love to see your solution.

As mentioned in earlier blog posts, I know next to nothing about this kind of topic, so I won't undertake anything on that subject and even less blog about it. Please try this challenge and provide answers if you know more that I do on such topic.

The next week Perl Weekly Challenge is due to start soon. If you're interested in participating in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 6 p.m. BST (British summer time) on Sunday, June 16. And, please, also spread the word about the Perl Weekly Challenge if you can.

Leave a comment

About laurent_r

user-pic I am the author of the "Think Perl 6" book (O'Reilly, 2017) and I blog about Perl (5 and 6).