I’ve been doing the Perl Weekly
Challenges. The
latest
involved more binary manipulation and analogue clocks.
(Note that this is open until 11 July 2021.)
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.
As with last week, one could do this by converting to a binary string,
but we already have bitwise operators so we might as well use them.
(If there were no limit on the input size it might be a bit more
fiddly.)
return (($n & 0x55)<<1) | (($n & 0xAA)>>1);
which looks basically the same in the other four usual languages,
except Raku where there are "+" signs at the start of the bitwise
operators.
But because I like the mental gymnastics, I decided to do this one in
PostScript as well. You can specify numbers in arbitrary bases, which
makes life easier, and this is basically the same algorithm as above.
/seob {
Make a copy of the input.
dup
Bitwise-and it with the lower mask, and shift up the result.
16#55 and
1 bitshift
Swap that on the stack with the other copy of the input.
exch
Upper mask and shift down the other copy.
16#AA and
-1 bitshift
And combine them.
or
} def
And then I wrote a basic test harness, because why wouldn't I? That
runs the tests and spits out a table with input, output, and pass/fail
status. It can also be conveniently invoked, at least where
GhostScript is available, with ps2txt
, which runs the PostScript
code, converts the page image to plain text, and dumps it to standard
output:
$ ps2txt ch-1.ps
1. 101 154 Pass
2. 18 33 Pass
$
(I've written PostScript code before now to send stuff explicitly to
stdout
, but that's not what PostScript wants to do in more than a
very basic way and it makes it appropriately fiddly.)
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.
Which seems again as though it has a fairly straightforward solution:
determine the angle of each hand, then reduce the difference. Raku:
sub ca($n) {
my $a=0;
Extract hour and minute:
if ($n ~~ /(<[0..9]>+)\:(<[0..9]>+)/) {
Convert each one to an angle (note that each minute puts half a degree
on the hour hand, so we're in floating point territory):
my ($ha,$ma)=map {$_ % 360}, ($0*30+$1/2,$1*6);
Take the absolute difference:
$a=abs($ha-$ma);
Reduce until we get an angle lying in (-180..180):
while ($a > 180) {
$a-=360;
}
And take the absolute again.
$a=abs($a);
}
return $a;
}
And it's basically the same algorithm in PostScript, though it looks
quite different and I don't have regexps:
/ca {
Split the input string
(:) search
We now have, for the first test case, (10) (:) (03) true
. Throw away
the true
(I don't care about error handling here).
pop
Get the hour angle (hour component only) and roll it to the bottom of
the working stack. Throw away the (:)
.
cvi 30 mul
3 1 roll pop
We now have 90 (10)
. Make that an integer, copy it, and get the
minute angle.
cvi dup
6 mul
We now have 90 10 60
. Stow that minute angle at the bottom.
3 1 roll
We now have 60 90 10
. Generate the minute component of the hour
angle and add it to the hour component we already have.
2 div add
We now have 60 95
, minute angle and hour angle. Take the difference
and reduce.
sub
abs
{
dup 180 gt
{ 360 sub }
{ exit }
ifelse
} loop
abs
} def
It's very refreshing to write in a completely different paradigm from
time to time. I still need to make notes on what's where in the stack
as I go along, particularly when roll
gets involved. (I'm not sure I
have a feel for what's a "natively PostScript" coding style, but using
the stack rather than defining local variables, and doing stuff in the
order it shows up on the stack rather than shoving it into the order
most convenient for me, seem to be a part of it.)
Full code on
github.