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
Written on February 15, 2021