Perl Weekly Challenge 120: Swap Odd/Even Bits and Clock Angle

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

Spoiler Alert: This weekly challenge deadline is due in a couple of days, on July 11, 2021). This blog post offers some solutions to this challenge, please don’t read on if you intend to complete the challenge on your own.

Task 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.

Swap Odd/Even Bits in Raku

We use the fmt method to convert the input numeral into a binary string. We could also use the base method, but the fmt method makes it possible to also specify an output format on 8 digits in one step (with leading 0’s when needed). Then, we split the binary string into groups of two digits and swap them. Finally, we use the parse-base to convert back the result into its numeric equivalent.

use v6;

sub swap-bits (UInt $n where * <=255) {
    my $bin = $n.fmt("%08b");
    $bin ~~ s:g/(\d)(\d)/$1$0/;
    return $bin.parse-base: 2; 
}
say "$_ : ", swap-bits $_ for 101, 154, 33, 18;

This program displays the following output:

$ raku ./swap_bits.raku
101 : 154
154 : 101
33 : 18
18 : 33

Swap Odd/Even Bits in Perl

The Perl program is essentially a port to Perl of the Raku program above. Since Perl doesn’t have a binary string to numeral conversion, we re-use the bin2dec subroutine implemented last week.

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

sub bin2dec {
    my $bin = shift;
    my $sum = 0;
    for my $i (split //, $bin) {
        $sum = $sum * 2 + $i;
    }
    return $sum;
}

for my $test (101, 154, 33, 18) {   
    my $b2 = sprintf "%08b", $test;
    $b2 =~ s/(\d)(\d)/$2$1/g;
    say "$test: ", bin2dec $b2;;
}

This program displays the following output:

$ perl ./swap_bits.pl
101: 154
154: 101
33: 18
18: 33

Task 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

Clock Angle in Raku

The general problem is not very difficult, but, as with anything having to do with time, there is a number of edge cases making the solution more complicated than we might initially expect.

Here, we compute the angle of each hand with the origin (00h00) measured clockwise. Then we compute the absolute value of the difference. At he end, if we find an angle larger than 180, we replace it by its complement to 360.

use v6;

sub find-angle (Str $t) {
    my ($h, $m) = split /\:/, $t;
    # We compute angles in degrees from 0h00 and clockwise
    my $m-angle = $m * 6;  # or: $m * 360/60
    my $h-angle = ($h * 360/12 + $m-angle / 12) % 360;
    my $angle = abs ($m-angle - $h-angle);
    return $angle <= 180 ?? $angle !!  360 - $angle;
}
for <03:10 04:00 07:00 15:10 20:44> -> $test {
    say "$test: ", find-angle $test;
}

This is the output displayed for the built-in test cases:

$ raku ./find-angle.raku
03:10: 35
04:00: 120
07:00: 150
15:10: 35
20:44: 2

Clock Angle in Perl

This is essentially a port to Perl of the Raku program above (although I must admit that there is one edge case that I originally missed in my Raku implementation and that I corrected after having found out about it in the Perl program).

use strict;
use warnings;
use feature "say";

sub find_angle {
    my $time = shift;
    my ($h, $m) = split /:/, $time;
    # angles counted in deg clockwise from the 0/12 position
    my $m_angle = $m * 6; # or: $m * 360/60
    # for the short hand:
    #     1 hr = 360 / 12 = 30 degrees
    #     1 min = #m_angle / (360 / 30) = #m_angle /12
    my $h_angle = ($h * 30 + $m_angle / 12) % 360; 
    my $hands_angle = abs($h_angle - $m_angle);
    return  $hands_angle <= 180 ? $hands_angle : 360 - $hands_angle;
}

for my $t (qw / 03:10 04:00 07:00 15:10 18:00 /) {
    say "$t: ", find_angle $t;
}

This is the output displayed for the built-in test cases:

$ perl ./find-angle.pl
03:10: 35
04:00: 120
07:00: 150
15:10: 35
18:00: 180

Wrapping up

The next week Perl Weekly Challenge will start soon. If you want to participate in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 23:59 BST (British summer time) on July 18, 2021. 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 the Perl 5 and Raku programming languages.