Perl Weekly Challenge 100.
My solutions (task 1 and task 2) to the The Weekly Challenge - 100.
Task 1: Fun Time
Submitted by: Mohammad S Anwar
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
One-liner without checks. I use a regular expression to extract the hours, minutes and pm indicator.
perl -E 'foreach(@ARGV){/^\s*(\d\d?)(:(\d\d?))?(\s*pm)?/i;
say $1>12? sprintf("%s->%02d:%02d%s",$_,$1-12,$3,"pm")
:$4? sprintf("%s->%02d:%02d%s",$_,$1+12,$3,"")
: sprintf("%s->%02d:%02d%s",$_,$1,$3,"");}'\
"05:15 pm" "5:15pm" "19:15" "5:15"
Results:
05:15 pm->17:15
5:15pm->17:15
19:15->07:15pm
5:15->05:15
Full version with simple checks:
# Perl weekly challenge 100
# Task 1: Fun Time
#
# See https://wlmb.github.io/2021/02/15/PWC100/#task-1-fun-time
use strict;
use warnings;
use v5.12;
sub usage {
say @_ if @_;
say <<END;
Converts time between 12 and 24 hour formats
Usage;
./ch-1.pl time1 time2 ...
Each argument must have the format "hh:mm:ss ampm"
where the minutes and seconds are optional and
ampm is either am or pm or null.
If ampm is given, the hour should be not greater than 12 nor
smaller than 1. If not, hour should be smaller than 24.
Minutes and seconds should be smaller than 60.
END
exit 1;
}
# The conversion rules are so crazy I'd better use tables.
# There is no 24-th hour, 12 pm< 1pm, 12am < 1am, there is 00, but not 00AM
my @from_am=(undef, 1, 2, 3, 4, 5, 6, 7, 8, 9,1 ,11, 0); # No 00AM
my @from_pm=(undef, 13,14,15,16,17,18,19,20,21,22,23,12); #No 24
my @to_am= ( 12, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11); #00 is 12AM
my @to_pm= ((undef)x12,12, 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11); #12 is 12PM but 13 is 1PM
foreach(@ARGV){
#Match allowing one or two digit hours, optional minutes, optional seconds and am,pm indicator
usage("Bad format: $_") unless /^(\d\d?)(:(\d\d?))?(:(\d\d?))?\s*(am|pm)?\s*$/i;
my ($hour,$minute,$second,$indicator)=($1,$3,$5,$6//"");
usage("Minute should be less than 60: $_") if defined $minute and $minute>=60;
usage("Second should be less than 60: $_") if defined $second and $second>=60;
my ($newhour,$newindicator)=
lc $indicator eq "am"? ($from_am[$hour],"")
:lc $indicator eq "pm"? ($from_pm[$hour],"")
:defined $to_am[$hour]?($to_am[$hour],"am")
:defined $to_pm[$hour]?($to_pm[$hour],"pm")
:(undef,undef);
usage("Bad hour: $_") unless defined $newhour;
say sprintf("Input:\t%s\nOutput:\t%02d", $_, $newhour),
(defined $minute?sprintf(":%02d", $minute):""),
(defined $second?sprintf(":%02d",$second):""),
" $newindicator";
}
Examples 1 and 2:
./ch-1.pl "05:15 pm" "19:15"
Results:
Input: 05:15 pm
Output: 17:15
Input: 19:15
Output: 07:15 pm
Other examples:
./ch-1.pl "05:15 am" "05:15" "1 pm" "13" "0:00"
Results:
Input: 05:15 am
Output: 05:15
Input: 05:15
Output: 05:15 am
Input: 1 pm
Output: 13
Input: 13
Output: 01 pm
Input: 0:00
Output: 12:00 am
Task 2: Triangle Sum
Submitted by: Mohammad S Anwar 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
One could enumerate all paths recursively and choose the minimum, but it is unnecesary: If a path is optimum and we split it at some point into an initial and a final part, the final part should also be optimum. Thus, we can build the optimal path from the bottom up.
# Perl weekly challenge 100
# Task 2: Triangle Sum
#
# See https://wlmb.github.io/2021/02/15/PWC100/#task-2-triangle-sum
use strict;
use warnings;
use v5.12;
use List::Util qw(min);
use List::MoreUtils qw(pairwise);
# Read all numbers from @ARGV as a flat list arranging them into rows to build triangle
my @rows;
my @row;
foreach(@ARGV){
push @row, $_;
push(@rows, [@row]),@row=() if @row > @rows;
}
push @rows,[@row] if @row; # add the last row
my $expected=@rows*(@rows+1)/2; #expected triangular number
my $got=@ARGV;
die "Not enough numbers. Expected $expected, got $got" unless $expected==$got;
# Build the optimum paths for each row combining them with those of the next row
my @next_row=(0)x@{$rows[-1]};
my $cost;
my @choices;
foreach my $current_row(reverse @rows){ # move upwards
my @current_row=pairwise {$a+$b} @$current_row, @next_row; #get totalcost for each element
$cost=$current_row[0],last if @current_row==1; # done?
# Find best choices for each index of the row above
my @chosen_indices=map {$current_row[$_]<=$current_row[$_+1]?$_:$_+1}(0..@current_row-2);
@next_row=@current_row[@chosen_indices];
# Build a triangle of chosen indices for later display
unshift @choices, [@chosen_indices];
}
#print input triangle and optimal cost
say "Input:\n", join "\n", map {join " ", @$_} @rows;
say "Output: $cost";
say "Explanation:"; # Print triangle bracketing chosen path
my $best_index=0;
foreach my $i(0..@rows-1){ #Indices of rows
my $row=$rows[$i];
my $choice=$choices[$i];
say join " ", map {$_==$best_index?"[$row->[$_]]":$row->[$_]} (0..@$row-1);
$best_index=$choice->[$best_index];
}
Example 1:
./ch-2.pl 1 2 4 6 4 9 5 1 7 2
Results:
Input:
1
2 4
6 4 9
5 1 7 2
Output: 8
Explanation:
[1]
[2] 4
6 [4] 9
5 [1] 7 2
Example 2:
./ch-2.pl 3 3 1 5 2 3 4 3 1 3
Results:
Input:
3
3 1
5 2 3
4 3 1 3
Output: 7
Explanation:
[3]
3 [1]
5 [2] 3
4 3 [1] 3