Perl Weekly Challenge 187.
My solutions (task 1 and task 2 ) to the The Weekly Challenge - 187.
Task 1: Days Together
Submitted by: Mohammad S Anwar
Two friends, Foo and Bar gone on holidays seperately to the same city.
You are given their schedule i.e. start date and end date.
To keep the task simple, the date is in the form DD-MM and all dates belong to the
same calendar year i.e. between 01-01 and 31-12. Also the year is non-leap year
and both dates are inclusive.
Write a script to find out for the given schedule, how many days they spent
together in the city, if at all.
Example 1
Input: Foo => SD: '12-01' ED: '20-01'
Bar => SD: '15-01' ED: '18-01'
Output: 4 days
Example 2
Input: Foo => SD: '02-03' ED: '12-03'
Bar => SD: '13-03' ED: '14-03'
Output: 0 day
Example 3
Input: Foo => SD: '02-03' ED: '12-03'
Bar => SD: '11-03' ED: '15-03'
Output: 2 days
Example 4
Input: Foo => SD: '30-03' ED: '05-04'
Bar => SD: '28-03' ED: '02-04'
Output: 4 days
Using the simplifications allowed by the task statement, I make a routine to convert from dates to 0-based day of the year. Then I subtract the smallest of the ending dates from the largest of the starting dates. If negative, there is no overlap. Otherwise, it is the off-by-one answer. This yelds the 2-liner
perl -MList::Util=sum,max,min -E '
@m=qw(31 28 31 30 31 30 31 31 30 31 30); sub d($x){($d,$m)=split "-", $x; $d-1+sum @m[(0..$m-2)]}
$d=min(map {d($ARGV[$_])}(1,3))-max(map {d($ARGV[$_])}(0,2)); say $d<0?0:$d+1
' 12-01 20-01 15-01 18-01
perl -MList::Util=sum,max,min -E '
@m=qw(31 28 31 30 31 30 31 31 30 31 30); sub d($x){($d,$m)=split "-", $x; $d-1+sum @m[(0..$m-2)]}
$d=min(map {d($ARGV[$_])}(1,3))-max(map {d($ARGV[$_])}(0,2)); say $d<0?0:$d+1
' 02-03 12-03 13-03 14-03
perl -MList::Util=sum,max,min -E '
@m=qw(31 28 31 30 31 30 31 31 30 31 30); sub d($x){($d,$m)=split "-", $x; $d-1+sum @m[(0..$m-2)]}
$d=min(map {d($ARGV[$_])}(1,3))-max(map {d($ARGV[$_])}(0,2)); say $d<0?0:$d+1
' 02-03 12-03 11-03 15-03
perl -MList::Util=sum,max,min -E '
@m=qw(31 28 31 30 31 30 31 31 30 31 30); sub d($x){($d,$m)=split "-", $x; $d-1+sum @m[(0..$m-2)]}
$d=min(map {d($ARGV[$_])}(1,3))-max(map {d($ARGV[$_])}(0,2)); say $d<0?0:$d+1
' 30-03 05-04 28-03 02-04
Results:
4
0
2
4
The full code is similar. I just add a few tests. A better code would
use the package DateTime
and friends, to allow for dates in
different years and for leap years.
1 # Perl weekly challenge 187
2 # Task 1: Days Together
3 #
4 # See https://wlmb.github.io/2022/10/17/PWC187/#task-1-days-together
5 use v5.36;
6 use List::Util qw(sum max min);
7 sub ddmm2days($x){
8 state @month_lengths=qw(31 28 31 30 31 30 31 31 30 31 30 31);
9 die "Wrong format: $x" unless $x=~/(\d\d)-(\d\d)/;
10 my ($d,$m)=($1, $2);
11 die "Wrong month: $m" unless 1<=$m<=12;
12 die "Wrong day: $d. Only $month_lengths[$m-1] days in month $m"
13 unless 1<=$d<=$month_lengths[$m-1];
14 $d-1+sum @month_lengths[(0..$m-2)]
15 }
16 die <<"FIN" unless @ARGV==4;
17 Usage: $0 fs fe bs be
18 to obtain the number of days Foo and Bar are together,
19 where fs and fe are the start and end dates of Foo's holidays and
20 bs and be are the start and end dates of Bar's holidays. Dates are in the
21 format DD-MM.
22 FIN
23 my ($foo_start, $foo_end, $bar_start, $bar_end)=@ARGV;
24 my $days= min(map {ddmm2days($_)} ($foo_end, $bar_end))
25 - max(map {ddmm2days($_)}($foo_start, $bar_start));
26 $days=$days<0?0:1+$days;
27 say "Overlap between intervals ($foo_start, $foo_end) and ($bar_start, $bar_end) is $days days";
Examples:
./ch-1.pl 12-01 20-01 15-01 18-01
./ch-1.pl 02-03 12-03 13-03 14-03
./ch-1.pl 02-03 12-03 11-03 15-03
./ch-1.pl 30-03 05-04 28-03 02-04
Results:
Overlap between intervals (12-01, 20-01) and (15-01, 18-01) is 4 days
Overlap between intervals (02-03, 12-03) and (13-03, 14-03) is 0 days
Overlap between intervals (02-03, 12-03) and (11-03, 15-03) is 2 days
Overlap between intervals (30-03, 05-04) and (28-03, 02-04) is 4 days
Task 2: Magical Triplets
Submitted by: Mohammad S Anwar
You are given a list of positive numbers, @n, having at least 3 numbers.
Write a script to find the triplets (a, b, c) from the given list that
satisfies the following rules.
1. a + b > c
2. b + c > a
3. a + c > b
4. a + b + c is maximum.
In case, you end up with more than one triplets having the maximum then
pick the triplet where a >= b >= c.
Example 1
Input: @n = (1, 2, 3, 2);
Output: (3, 2, 2)
Example 2
Input: @n = (1, 3, 2);
Output: ()
Example 3
Input: @n = (1, 1, 2, 3);
Output: ()
Example 4
Input: @n = (2, 4, 3);
Output: (4, 3, 2)
I first sort the list in descending order. Then I use combinations
from Algorithm::Combinatorics
to produce all triplets (a,b,c). I
choose the first (if any) that obeys b+c>a assuming that
combinations
returns combinations in order. As a>=b>=c, and all
numbers are positive, this guarantees that a+b>c and a+c>b. The code
fits a oneliner:
perl -MAlgorithm::Combinatorics=combinations -MList::Util=first -E '
@n=sort {$b<=>$a} @ARGV; say join " ", @$_ for first {$_->[1]+$_->[2]>$_->[0]} combinations(\@n, 3)
' 1 2 3 2
Results:
3 2 2
perl -MAlgorithm::Combinatorics=combinations -MList::Util=first -E '
@n=sort {$b<=>$a} @ARGV; say join " ", @$_ for first {$_->[1]+$_->[2]>$_->[0]} combinations(\@n, 3)
' 1 3 2
Results:
perl -MAlgorithm::Combinatorics=combinations -MList::Util=first -E '
@n=sort {$b<=>$a} @ARGV; say join " ", @$_ for first {$_->[1]+$_->[2]>$_->[0]} combinations(\@n, 3)
' 4 3 2
Results:
4 3 2
I add a few checks to the full code. I use an iterator to avoid needlesly producing all triplets.
1 # Perl weekly challenge 187
2 # Task 2: Magical Triplets
3 #
4 # See https://wlmb.github.io/2022/10/17/PWC187/#task-2-magical-triplets
5 use v5.36;
6 use Algorithm::Combinatorics qw(combinations);
7 use List::Util qw(first);
8 die <<FIN unless @ARGV;
9 Usage: $0 N1 N2 N3 [N4...]
10 to find the maximum ordered triplet with elements from {N1, N2...} that obeys
11 the strict triangle inequalities.
12 FIN
13 my @set = sort {$b<=>$a} @ARGV;
14 die "All numbers should be positive" unless $set[-1]>0;
15 my $iterator=combinations(\@set, 3);
16 while(my $triplet=$iterator->next){
17 die "Expected ordered triplet: $triplet" # Currently uneeded
18 unless $triplet->[0]>=$triplet->[1]>=$triplet->[2];
19 next unless $triplet->[1]+$triplet->[2]>$triplet->[0];
20 say "Input: (", join(", ", @ARGV), ") Output: (", join(", ", @$triplet), ")";
21 exit;
22 }
23 say "Input: (", join(", ", @ARGV), ") Output: ()";
Examples:
./ch-2.pl 1 2 3 2
./ch-2.pl 1 3 2
./ch-2.pl 1 1 2 3
./ch-2.pl 2 4 3
#+end_example
Results:
Input: (1, 2, 3, 2) Output: (3, 2, 2)
Input: (1, 3, 2) Output: ()
Input: (1, 1, 2, 3) Output: ()
Input: (2, 4, 3) Output: (4, 3, 2)