Even Clock
with Raku and Perl

by Arne Sommer

Even Clock with Raku and Perl

[136] Published 11. July 2021.

This is my response to the Perl Weekly Challenge #120.

Challenge #120.1: Swap Odd/Even bits

You are given a positive integer $N less than or equal to 255.

Write a script to swap the odd positioned bit with even positioned bit and print the decimal equivalent of the new binary representation.

Example:
Input: $N = 101
Output: 154

Binary representation of the given number is 01 10 01 01.
The new binary representation after the odd/even swap is 10 01 10 10.
The decimal equivalent of 10011010 is 154.

Input: $N = 18
Output: 33

Binary representation of the given number is 00 01 00 10.
The new binary representation after the odd/even swap is 00 10 00 01.
The decimal equivalent of 100001 is 33.

This resembles lask week's 1st challenge: Swap Nibbles, so the code is mostly identical (except the line marked [3]).

We are swapping the individual bits of neighbouring pairs. As an illustration, with the general idea to the left and the first example given in the challenge to the right:

File: swap-bits
#! /usr/bin/env raku

unit sub MAIN (Int $N where $N > 0 && $N <= 255, :v(:$verbose));  # [1]

my $binary  = $N.fmt('%08b');                                     # [2]
my $swapped = $binary.comb(2)>>.flip.join;                        # [3]
  ################### # 3a ## # 3b # # 3c
      
if $verbose
{
  say ": Binary:  $binary";
  say ": Swapped: $swapped (binary)";
}

say $swapped.parse-base(2);                                       # [4]

[1] Ensure an integer in the range 1..255.

[2] Convert it to binary, zero padded (i.e. «00110011» instead of «110011»).

[3] Swap two and two bits at a time. We start by splitting the number (a string with length 8) into (4) strings of length 2; each containing the two bits that we are tasked to switch (with comb(2)) [3a]. Then we flip all the strings, thus reversing the order of the two bits (with >>.flip, which does it on each and every element in the list) [3b], and finally glue them back into a single string (with join) [3c].

[4] Convert back to decimal, and print it.

See docs.raku.org/routine/comb for more information about comb.

Running it:

$ ./swap-bits 101
154

$ ./swap-bits 18
33

$ ./swap-bits -v 101
: Binary:  01100101
: Swapped: 10011010 (binary)
154

$ ./swap-bits -v 18
: Binary:  00010010
: Swapped: 00100001 (binary)
33

A Perl Version

This is straight forward translation of the Raku version, except for the «comb» part, that has been replaced by (a lot of) substrings:

File: swap-bits-perl
#! /usr/bin/env perl

use strict;
use warnings;
use feature 'say';
use Getopt::Long;

my $verbose = 0;

GetOptions("verbose" => \$verbose);

my $N = $ARGV[0] // "";

die "Please specify an integer in the range 1..255"
  if $N !~ /^[1-9]\d*$/ || $N > 255;

my $binary  = sprintf('%08b', $N);
my $swapped = substr($binary, 1, 1) .  # [1]
              substr($binary, 0, 1) .  # [1a]
              substr($binary, 3, 1) .
              substr($binary, 2, 1) .
              substr($binary, 5, 1) .
              substr($binary, 4, 1) .
              substr($binary, 7, 1) .
              substr($binary, 6, 1);

if ($verbose)
{
  say ": Binary:  $binary";
  say ": Swapped: $swapped (binary)";
}

say oct("0b" . $swapped);

[1] Start with the second bit (index 1), then add the first one (index 0) [1a]. And so on.

Running it gives the same result as the Raku version:

$ ./swap-bits-perl 101
154

$ ./swap-bits-perl 18
33

$ ./swap-bits-perl -v 101
: Binary:  01100101
: Swapped: 10011010 (binary)
154

$./swap-bits-perl -v 18
: Binary:  00010010
: Swapped: 00100001 (binary)
33

Challenge #120.2: Clock Angle

You are given time $T in the format hh:mm.

Write a script to find the smaller angle formed by the hands of an analog clock at a given time.

HINT: A analog clock is divided up into 12 sectors. One sector represents 30 degree (360/12 = 30).

Example:
Input: $T = '03:10'
Output: 35 degree

The distance between the 2 and the 3 on the clock is 30 degree.
For the 10 minutes i.e. 1/6 of an hour that have passed.
The hour hand has also moved 1/6 of the distance between the 3 and the 4,
  which adds 5 degree (1/6 of 30).
The total measure of the angle is 35 degree.

Input: $T = '04:00'
Output: 120 degree

An illustration may help::

The starting position on an analog clock (straight up) is shown as the hour «12». I have decided to allow «12:xx» (as well as «00:xx). So «12:59» is the same as «00:59». «13:xx» is not allowed.

Using a custom type (with subset) is difficult when we have a strange upper limit (as 12), so I have used a Grammar:

File: clock-angle
#! /usr/bin/env raku

unit sub MAIN (Str $T, :v(:$verbose));                # [1]

grammar HHMM                                          # [2]
{
  token TOP { <HH> \: <MM> }                          # [3]
  token HH  { <[01]> <[0..9]> <?{ $/.Int <= 12 }> }   # [4]
  token MM  { <[0..5]> <[0..9]> }                     # [5]
}

my $m = HHMM.parse($T) // die "Illegal HH:MM value";  # [6]

my $hour = $m<HH>;                                    # [7]
my $min  = $m<MM>;                                    # [7]

$hour -= 12 if $hour >= 12;                           # [8]

my $degrees_h = 360 / 12 * $hour + $min / 2;          # [9]
my $degrees_m = 360 / 60 * $min;                      # [10]

if $verbose
{
  say ": Hours: $hour - Degrees: $degrees_h";
  say ": minutes: $min - Degrees: $degrees_m";
}

my $diff = abs($degrees_h - $degrees_m);              # [11]

say "$diff degree";                                   # [12]

[1] Set it up as a string, which does not say much.

[2] The Grammar, with grammar as the keyword.

[3] The «TOP» token is the entry point. The token keyword is one of three we can use in Grammars (the others are regex and rule). The top rule is an hour value, followed by a literal colon, and a minute value.

[4] The hour value. The Regex (by itself) would allow hours from «00» to «19», so I have added a constraining code block (the <?{ $/.Int <= 12 }> part). If this block returns False, the Regex fails (as in does not match). The block simply check that the hour value (the match object, in $/ coerced to an integer) is lower or equal to 12.

[5] The minute part. We do not need custom code here.

[6] Parse the input value against the Grammar, and exit if it fails.

[7] Collect the hour and minute values, by name (as given in the Grammar, line [4] and [5]).

[8] If the hour is 12 (not higher, as the Grammar has prevented that), we set it to 0 - as the angle calculation assumes that we start at 0.

[9] Get the degree for the hour value. The full circle is 360 degrees, so we divide it by 12 to get the size of each hour. Then we multiply it with the hour value to get the angle. The minutes must be added to the hour hand (as it moves along), and we do this by dividing the minutes by 2. (One hour is 30 degrees, which is 60 minues - so 1 minute gives 0.5 degrees.)

[10] The degree for the minute value. There are 60 minutes in an hour, so «360/60» gives the degree for one minute.

[11] The difference in the angles for the two hands. Remove the sign (if any) with abs.

[12] Print the result.

See docs.raku.org/language/grammar_tutorial for an introduction to Grammars, and docs.raku.org/language/grammars for more details.

See docs.raku.org/routine/abs more information about abs.

Running it:

$ ./clock-angle 03:10
35 degree

$ ./clock-angle 04:00
120 degree

$ ./clock-angle -v 03:10
: Hours: 03 - Degrees: 95
: minutes: 10 - Degrees: 60
35 degree

$ ./clock-angle -v 04:00
: Hours: 04 - Degrees: 120
: minutes: 00 - Degrees: 0
120 degree

Looking good.

Perl

This is a straight forward translation of the Raku version. Perl does not have Grammars, but a Regex works as well:

File: clock-angle-perl
#! /usr/bin/env perl

use strict;
use warnings;
use feature 'say';
use Getopt::Long;

my $verbose = 0;

GetOptions("verbose" => \$verbose);

my $T = $ARGV[0] // "";

die "Please specify a HH:MM value" unless $T =~ /^[01]\d\:[0-5]\d$/;

my ($hour, $min) = split(":", $T);

die "Hours 00-12 only" if $hour > 12;  # [1]

$hour -= 12 if $hour >= 12;

my $degrees_h = 360 / 12 * $hour + $min / 2;
my $degrees_m = 360 / 60 * $min;

if ($verbose)
{
  say ": Degree H: $degrees_h";
  say ": Degree M: $degrees_m";

}

my $diff = abs($degrees_h - $degrees_m);

say "$diff degree";

[1] Get rid of hour values larger than 12.

Running it gives the same result as the Raku version:

$ ./clock-angle-perl -v 03:10
: Degree H: 95
: Degree M: 60
35 degree

$ ./clock-angle-perl -v 04:00
: Degree H: 120
: Degree M: 0
120 degree

And that's it.