Perl Weekly Challenge: Week 100
Challenge 1:
Fun Time
You are given a time (12 hour / 24 hour).
Write a script to convert the given time from 12 hour format to 24 hour format and vice versa.
Ideally we expect a one-liner.
Example 1
Input: 05:15 pm or 05:15pm
Output: 17:15
Example 2
Input: 19:15
Output: 07:15 pm or 07:15pm
The spec asks for a one-liner so here goes...
($h,$m,$a)=shift=~/\A\s*((?:2[0-4])|(?:1\d)|(?:0*\d))\:([0-5]*\d)\s*([ap]m)?\s*/i;($h==0)?($h=12and$a=q{am}):($h==12)?($a=($a)?$a:q{pm}):($h>12)?($h-=12and$a=q{pm}):($a&&$a=~/pm/i)?($h+=12and$a=q{}):($a=q{});printf qq{%02d:%02d %s\n},$h,$m,$a;
Oof! At approximately 250 characters of inscrutable line noise, this is the kind of code that gives Perl a bad name. But if you format it and expand it out a little bit it's not that bad. In particular there are three devices, I used that are not
needed in an expanded version. The use of ? ... :
to replace if/else
. The use of 'and' to join two statements into one. And the use of q
and qq
for quoting strings.
($h, $m, $ampm) = $ARGV[0] =~ / \A \s* ( (?: 2[0-4]) | (?: 1\d) | (?: 0*\d) ) \: ([0-5]*\d) \s* ([ap]m)? \s* /ix;
We take the time as a command line argument. A regular expression parses it into three parts. First there is possibly some whitespace and then the hour from 0 (or 00) to 24. The hour is followed by a colon and then minutes from 0 (or 00) to 59. After some more optional whitespace there can optionally be the string 'am' or 'pm' (or 'AM', 'PM' etc. the /i
flag makes the regexp case-insensitive) and then some optional trailing whitespace. These three bits are stored as the variables $h
,
$m
and $a
(or $ampm
in the expanded version for clarity.)
There are four possible scenarios on how to parse the time each dependent on the value of $hour
.
if ($h == 0) {
$h = 12;
$ampm = 'am';
If the hour is 0 or 00, the canonical time is 12am.
} elsif ($h == 12) {
$ampm = (defined $ampm) ? $ampm: 'pm';
If the hour is 12 it is noon so it should be 12pm. However it is possible some might write 12am refering to midnight so
in order to accomodate parsing that, if $ampm
was present in the input, it is left as is.
} elsif ($h > 12) {
$h -= 12;
$ampm = 'pm';
If the hour is greater than 12, it is 'pm' in the 24-hour format so 12 is subtracted to get the canonical hour and $ampm
is
set to 'pm'.
} elsif (defined $ampm && $ampm =~ /pm/i ) {
$h += 12;
$ampm = q{};
If the hour is less than 12 it is either 'am' in the 24-hour format or either 'am' or 'pm' in the 12-hour format. We'll
only know if $ampm
has been defined. If it has and it is 'pm', we add 12 to $hour
to get the 24-hour format value. According to the spec we don't need to print $ampm
in this case so it is cleared.
} else {
$ampm = q{};
}
If $ampm
wasn't 'pm' it must have been 'am'. Once again we don't need to print it in this case.
printf "%02d:%02d %s\n", $h, $m, $a;
Finally we have the time in its' canonical format so we can print it.
This is the Raku version. Raku is a little less forgiving in parsing so we need a bit more white space around certain keywords but other than that, it is the same as Perl.
my ($h,$m,$a) = @*ARGS[0].match(/^\s*(2<[0..4]>||1\d||0*\d)\:(<[0..5]>*\d)\s*(<[ap]>m)?\s*/,:i).list;($h==0)??($h=12 and $a=q{am})!!($h==12)??($a=($a)??$a!!q{pm})!!($h>12)??($h-=12 and $a=q{pm})!!($a&&$a~~m:i/pm/)??($h+=12 and $a=q{})!!($a=q{});printf qq{%02d:%02d %s\n},$h,$m,$a;
Challenge 2:
Triangle Sum
You are given triangle array.
Write a script to find the minimum path sum from top to bottom.
When you are on index i
on the current row then you may move to either index i
or index i + 1
on the next row.
Example 1
Input: Triangle = [ [1], [2,4], [6,4,9], [5,1,7,2] ]
Output: 8
Explanation: The given triangle
1
2 4
6 4 9
5 1 7 2
The minimum path sum from top to bottom: 1 + 2 + 4 + 1 = 8
[1]
[2] 4
6 [4] 9
5 [1] 7 2
Example 2
Input: Triangle = [ [3], [3,1], [5,2,3], [4,3,1,3] ]
Output: 7
Explanation: The given triangle
3
3 1
5 2 3
4 3 1 3
The minimum path sum from top to bottom: 3 + 1 + 2 + 1 = 7
[3]
3 [1]
5 [2] 3
4 3 [1] 3
This was a comparatively easy one to solve. First, the Perl version.
my ($input) = @ARGV;
my @levels = @{ eval $input };
The input is already in the form of perl syntax. That means we can get it into
our program via evaluating it as perl code with the eval()
function. But don't
do it the way I've done it here in a real scenario. eval
ing outside text which
has not been thoroughly vetted is an invitation for horrible things to happen. But
I think we can trust Mohammed :-)
my $count = 0;
my $i = 0;
for my $level (@levels) {
if ($level->[$i] < ($level->[$i + 1] // 'inf')) {
For each level of the triangle, we examine to see which one is smaller either the current index from the last level (starting at 0 on the top level) or the index to its' right. This might not exist if are at the edge. If it doesn't we pretend it equals infinity which will always be greater than any other value in the level.
$count += $level->[$i];
} else {
$count += $level->[$i + 1];
$i = $i + 1;
}
}
say $count;
This is Raku.
use MONKEY-SEE-NO-EVAL;
As I mentioned, eval
(or EVAL
as Raku calls it) is very powerful and fraught with peril.
So much so, that the Raku compiler won't let you use it unless you include this pragma beforehand
if for no other reason than to remind readers of the code you are doing something which could be dangerous.
sub MAIN(
Str $input #= a representation of a triangle array as nested arrays
) {
my @levels = EVAL $input;
my $count = 0;
my $i = 0;
for @levels -> @level {
if @level[$i] < (@level[$i + 1] // ∞) {
I'm sorry but I'll never get over how cool it is that Raku lets you use the ∞ symbol.
$count += @level[$i];
} else {
$count += @level[$i + 1];
$i = $i + 1;
}
}
say $count;
}