Perl Weekly Challenge: Week 120

Challenge 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 challenge is very close to challenge 1 last week. We convert the input into a binary number.

my $n = $N.base(2);

We pad that number with leading 0's if necessary to make it 8 digits long.

if $n.chars !%% 8 {
    $n = 0 x (8 - $n.chars % 8) ~ $n;
}

Whereas last week we transformed the binary number with this line:

$n.comb(4).reverse.join.parse-base(2).say;

...this time, we do it like this:

$n.comb(2).map({ $_.flip }).join.parse-base(2).say;

(Full code on Github.)

.comb() splits up the string but this time into 2 character chunks. .map() reverses each chunk individually instead of reversing the whole list of chunks as we did last week. .join() joins the list back into a string again and .parse-base() converts it back into base 10. Finally .say() prints the answer.

Perl works the same way so I'll only show the difference between last week:

say oct '0b' . join q{}, reverse (unpack '(A4)*', $n);

...and this week:

say oct '0b' . join q{}, map {  $_ = reverse; } (unpack '(A2)*', $n);

(Full code on Github.)

Challenge 2:

Sequence Without 1-on-1

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

First we split the input into hour and minute values. I really should have done some validation here to make sure $hours is between 0 and 12 and $mins is between 0 and 59 but I was lazy.

my ($hours, $mins) = $T.split(q{:});

Now the angle for the hour is basically 30° times its numeric value. (12 is the same as 00 so modulo 12 normalizes that.) But the hour hand keeps moving forward depending on how many minutes have passed. So we have to convert the minutes to a fraction of an hour and multiply that by 30 as well and add it.

my $hangle = ($hours % 12) * 30 + ($mins / 60 ) * 30;

The minute hand moves forward 6° for every minute.

my $mangle = $mins * 6;

The difference between the two angles is the absolute value of subtracting one from the other.

my $diff = abs($hangle - $mangle);

But if you think about it, you could measure the difference two ways depending on if you go clockwise or counter-clockwise. For example at 9:00, the hour hand is on 9 and the minute hand is on 12. If you go clockwise, the angle between the two is 270°. But if you go counter-clockwise, the angle is 90°. The spec says we have to find the smaller angle and this next line does exactly that.

my $angle = min($diff, 360 - $diff);

Finally, we print the answer. To satisfy my pet peeve, there is code to make sure degrees is properly pluralized.

say "$angle degree", ($angle != 1 ?? 's' !! q{}); 

(Full code on Github.)

This is the Perl version which works the same way. In fact it is almost identical to Raku.

my ($hours, $mins) = split q{:}, $T;

my $hangle = ($hours % 12) * 30 + ($mins / 60 ) * 30;
my $mangle = $mins * 6;
my $diff = abs($hangle - $mangle);
my $angle = min($diff, 360 - $diff);

say "$angle degree", ($angle != 1 ? 's' : q{});

Perl doesn't have a builtin min() function so I added one.

sub min {
    my ($a, $b) = @_;

    return $a < $b ? $a : $b;
}

(Full code on Github.)