Perl Weekly Challenge 183.
My solutions (task 1 and task 2 ) to the The Weekly Challenge - 183.
Task 1: Unique Array
Submitted by: Mohammad S Anwar
You are given list of arrayrefs.
Write a script to remove the duplicate arrayrefs from the given list.
Example 1
Input: @list = ([1,2], [3,4], [5,6], [1,2])
Output: ([1,2], [3,4], [5,6])
Example 2
Input: @list = ([9,1], [3,7], [2,5], [2,5])
Output: ([9, 1], [3,7], [2,5])
I use the magic of the Perl Data Language PDL
to solve this task. I
construct the solution progressively below. First I input the array of
vectors as an 2d-array, one row for each vector, using PDL’s ability
to parse strings.
perl -MPDL -MPDL::NiceSlice -E '
$x=pdl shift; say $x;
' "[[1,2], [3,4], [5,6], [1,2]]"
Results:
[
[1 2]
[3 4]
[5 6]
[1 2]
]
I compare each row with each other row by introducing dummy
indices in the second and third positions and using andover
to check
if all elements of the vectors coincide.
perl -MPDL -MPDL::NiceSlice -E '
$x=pdl shift; $a=($x->(:,:,*1)==$x(:,*1,:))->andover; say $a;
' "[[1,2], [3,4], [5,6], [1,2]]"
The result is a 2D matrix
[
[1 0 0 1]
[0 1 0 0]
[0 0 1 0]
[1 0 0 1]
]
that shows that in this example, row 3 is equal to row 0. Besides, each row is equal to itself.
I eliminate the trivial case and the redundant cases by comparing the
xvals
and yvals
of the matrix,
perl -MPDL -MPDL::NiceSlice -E '
$x=pdl shift; $a=($x->(:,:,*1)==$x(:,*1,:))->andover;
$w=$a&($a->xvals<$a->yvals); say +$w;
' "[[1,2], [3,4], [5,6], [1,2]]"
and orover
the results
[
[0 0 0 0]
[0 0 0 0]
[0 0 0 0]
[1 0 0 0]
]
to get those rows that are duplicate of any other row:
perl -MPDL -MPDL::NiceSlice -E '
$x=pdl shift; $a=($x->(:,:,*1)==$x(:,*1,:))->andover;
$w=($a&($a->xvals<$a->yvals))->orover; say +$w;
' "[[1,2], [3,4], [5,6], [1,2]]"
The results
[0 0 0 1]
show that I must remove row-3. I invert the result find the rows that I have to keep and get their indices:
perl -MPDL -MPDL::NiceSlice -E '
$x=pdl shift; $a=($x->(:,:,*1)==$x(:,*1,:))->andover;
$w=which(!($a&($a->xvals<$a->yvals))->orover); say +$w;
' "[[1,2], [3,4], [5,6], [1,2]]"
which in this example are rows 0, 1 and 2. Results:
[0 1 2]
Finally, I use this as a vector of indices into the original array and print the results:
perl -MPDL -MPDL::NiceSlice -E '
$x=pdl shift; $a=($x->(:,:,*1)==$x(:,*1,:))->andover;
$w=which(!($a&($a->xvals<$a->yvals))->orover); say +$x->mv(-1,0)->index1d($w)->mv(0,-1);
' "[[1,2], [3,4], [5,6], [1,2]]"
Results:
[
[1 2]
[3 4]
[5 6]
]
I test the code with the second example
perl -MPDL -MPDL::NiceSlice -E '
$x=pdl shift; $a=($x->(:,:,*1)==$x(:,*1,:))->andover;
$w=which(!($a&($a->xvals<$a->yvals))->orover); say +$x->mv(-1,0)->index1d($w)->mv(0,-1);
' "[[9,1], [3,7], [2,5], [2,5]]"
Results:
[
[9 1]
[3 7]
[2 5]
]
With a very small modification I can make the code to work with
ndarrays of any dimension as input, by flattening them before doing
the comparisons with clump
. Thus the complete code is
perl -MPDL -MPDL::NiceSlice -E '
$x=pdl shift; $f=$x->clump(-2); $a=($f->(:,:,*1)==$f(:,*1,:))->andover;
$w=which(!($a&($a->xvals<$a->yvals))->orover); say +$x->mv(-1,0)->index1d($w)->mv(0,-1);
' "[[1,2], [3,4], [5,6], [1,2]]"
Results:
[
[1 2]
[3 4]
[5 6]
]
Thus, the original example still works.
Example with matricial, instead of vectorial inputs
perl -MPDL -MPDL::NiceSlice -E '
$x=pdl shift; $f=$x->clump(-2); $a=($f->(:,:,*1)==$f(:,*1,:))->andover;
$w=which(!($a&($a->xvals<$a->yvals))->orover); say +$x->mv(-1,0)->index1d($w)->mv(0,-1);
' "[[[1,2],[3,4]],[[5,6],[7,8]], [[5,6],[7,8]], [[9,10],[11,12]]]"
Results:
[
[
[1 2]
[3 4]
]
[
[5 6]
[7 8]
]
[
[ 9 10]
[11 12]
]
]
Example with scalar instead of vectorial inputs:
perl -MPDL -MPDL::NiceSlice -E '
$x=pdl shift; $f=$x->clump(-2); $a=($f->(:,:,*1)==$f(:,*1,:))->andover;
$w=which(!($a&($a->xvals<$a->yvals))->orover); say +$x->mv(-1,0)->index1d($w)->mv(0,-1);
' "[0,1,0,2,3,0,2]"
Results:
[0 1 2 3]
The full code is
1 # Perl weekly challenge 183
2 # Task 1: Unique Array
3 #
4 # See https://wlmb.github.io/2022/09/20/PWC183/#task-1-unique-array
5 use v5.36;
6 use experimental qw(try);
7 use PDL;
8 use PDL::NiceSlice;
9 die <<EOF
10 Usage: $0 "[V1,V2...]" ...
11 to print the unique arrays among V1, V2...
12 Each V_i is an array [N1, N2...] of numbers N_j, or an array of arrays [W1,W2...] where
13 each Wj is an array of...
14 Admits multiple arguments.
15 EOF
16 unless @ARGV;
17 for(@ARGV){
18 try {
19 my $input=pdl $_;
20 my $flat=$input->clump(-2);
21 my $repetitions=($flat->(:,:,*1)==$flat(:,*1,:))->andover;
22 my $redundant=($repetitions&($repetitions->xvals < $repetitions->yvals))->orover;
23 my $keep_indices=which(!$redundant);
24 say +$input->mv(-1,0)->index1d($keep_indices)->mv(0,-1);
25 }
26 catch ($m) {
27 say "\n$m\nInvalid ndarray: $_";
28 }
29 }
30
Examples, including vectors, matrices, scalars and an error:
./ch-1.pl "[[1,2], [3,4], [5,6], [1,2]]" "[[9,1], [3,7], [2,5], [2,5]]" \
"[[[1,2],[3,4]],[[5,6],[7,8]], [[5,6],[7,8]], [[9,10],[11,12]]]" \
"[0,1,0,2,3,0,2]" "[1,a,3]"
Results:
[
[1 2]
[3 4]
[5 6]
]
[
[9 1]
[3 7]
[2 5]
]
[
[
[1 2]
[3 4]
]
[
[5 6]
[7 8]
]
[
[ 9 10]
[11 12]
]
]
[0 1 2 3]
PDL::Core::new_pdl_from_string: found disallowed character(s) 'a' in '[1,a,3]', value now: '[1,a,3]' at ./ch-1.pl line 21.
Invalid ndarray: [1,a,3]
Task 2: Date Difference
Submitted by: Mohammad S Anwar
You are given two dates, $date1 and $date2 in the format YYYY-MM-DD.
Write a script to find the difference between the given dates in terms on years and days only.
Example 1
Input: $date1 = '2019-02-10'
$date2 = '2022-11-01'
Output: 3 years 264 days
Example 2
Input: $date1 = '2020-09-15'
$date2 = '2022-03-29'
Output: 1 year 195 days
Example 3
Input: $date1 = '2019-12-31'
$date2 = '2020-01-01'
Output: 1 day
Example 4
Input: $date1 = '2019-12-01'
$date2 = '2019-12-31'
Output: 30 days
Example 5
Input: $date1 = '2019-12-31'
$date2 = '2020-12-31'
Output: 1 year
Example 6
Input: $date1 = '2019-12-31'
$date2 = '2021-12-31'
Output: 2 years
Example 7
Input: $date1 = '2020-09-15'
$date2 = '2021-09-16'
Output: 1 year 1 day
Example 8
Input: $date1 = '2019-09-15'
$date2 = '2021-09-16'
Output: 2 years 1 day
Manipulating dates is quite complex, so I rely on the DateTime
and
Date::Parse
packages. I’m not convinced my use is the best, but it
fits a 3-liner.
Example 1:
perl -MDateTime -MDate::Parse -E '
($d1,$d2)=map {DateTime->from_epoch($_)} sort {$a<=>$b} map {str2time($_)}@ARGV;
$y=$d2->subtract_datetime($d1)->years; $d2->subtract(years=>$y);
($d)=$d2->delta_days($d1)->in_units("days");say "$y years, $d days";
' 2019-02-10 2022-11-01
Result:
3 years, 264 days
Example 2:
perl -MDateTime -MDate::Parse -E '
($d1,$d2)=map {DateTime->from_epoch($_)} sort {$a<=>$b} map {str2time($_)}@ARGV;
$y=$d2->subtract_datetime($d1)->years; $d2->subtract(years=>$y);
($d)=$d2->delta_days($d1)->in_units("days");say "$y years, $d days";
' 2020-09-15 2022-03-29
Result:
1 years, 195 days
Example 3:
perl -MDateTime -MDate::Parse -E '
($d1,$d2)=map {DateTime->from_epoch($_)} sort {$a<=>$b} map {str2time($_)}@ARGV;
$y=$d2->subtract_datetime($d1)->years; $d2->subtract(years=>$y);
($d)=$d2->delta_days($d1)->in_units("days");say "$y years, $d days";
' 2019-12-31 2020-01-01
Result:
0 years, 1 days
Example 4:
perl -MDateTime -MDate::Parse -E '
($d1,$d2)=map {DateTime->from_epoch($_)} sort {$a<=>$b} map {str2time($_)}@ARGV;
$y=$d2->subtract_datetime($d1)->years; $d2->subtract(years=>$y);
($d)=$d2->delta_days($d1)->in_units("days");say "$y years, $d days";
' 2019-12-01 2019-12-31
Result:
0 years, 30 days
Example 5:
perl -MDateTime -MDate::Parse -E '
($d1,$d2)=map {DateTime->from_epoch($_)} sort {$a<=>$b} map {str2time($_)}@ARGV;
$y=$d2->subtract_datetime($d1)->years; $d2->subtract(years=>$y);
($d)=$d2->delta_days($d1)->in_units("days");say "$y years, $d days";
' 2019-12-31 2020-12-31
Result:
1 years, 0 days
Output: 1 year
Example 6:
perl -MDateTime -MDate::Parse -E '
($d1,$d2)=map {DateTime->from_epoch($_)} sort {$a<=>$b} map {str2time($_)}@ARGV;
$y=$d2->subtract_datetime($d1)->years; $d2->subtract(years=>$y);
($d)=$d2->delta_days($d1)->in_units("days");say "$y years, $d days";
' 2019-12-31 2021-12-31
Result:
2 years, 0 days
Example 7:
perl -MDateTime -MDate::Parse -E '
($d1,$d2)=map {DateTime->from_epoch($_)} sort {$a<=>$b} map {str2time($_)}@ARGV;
$y=$d2->subtract_datetime($d1)->years; $d2->subtract(years=>$y);
($d)=$d2->delta_days($d1)->in_units("days");say "$y years, $d days";
' 2020-09-15 2021-09-16
Result:
1 years, 1 days
Example 8:
perl -MDateTime -MDate::Parse -E '
($d1,$d2)=map {DateTime->from_epoch($_)} sort {$a<=>$b} map {str2time($_)}@ARGV;
$y=$d2->subtract_datetime($d1)->years; $d2->subtract(years=>$y);
($d)=$d2->delta_days($d1)->in_units("days");say "$y years, $d days";
' 2019-09-15 2021-09-16
Results:
2 years, 1 days
The code produced the expected results in all the given examples.
Thus, with more confidence I proceed to the full code.
1 # Perl weekly challenge 183
2 # Task 2: Date Difference
3 #
4 # See https://wlmb.github.io/2022/09/20/PWC183/#task-2-date-difference
5 use v5.36;
6 use experimental qw(try for_list);
7 use DateTime;
8 use Date::Parse;
9 die <<FIN unless @ARGV and @ARGV%2==0;
10 Usage: $0 D1 D2 ...
11 Print the date difference between dates D1 and D2, each in the format YYYY-MM-DD.
12 Additional pairs of dates may be added;
13 FIN
14
15 for my ($date1, $date2)(@ARGV){
16 try {
17 my ($dt1, $dt2)=map {DateTime->from_epoch($_)} # ordered datetimes
18 sort {$a<=>$b}
19 map {str2time($_)//die "Wrong date: $_"} ($date1, $date2);
20 my $years=$dt2->subtract_datetime($dt1)->years; # year difference
21 $dt2->subtract(years=>$years); # reduce difference to within a year
22 my $days=$dt2->delta_days($dt1)->in_units("days"); # remaining days
23 my $years_name=$years==1?"year":"years"; # singular or plural
24 my $days_name=$days==1?"day":"days";
25 say "The difference between $date1 and $date2 is $years $years_name and $days $days_name";
26 }
27 catch($m){
28 say "Failed with $date1 and $date2\n$m";
29 }
30 }
Examples:
./ch-2.pl 2019-02-10 2022-11-01 2020-09-15 2022-03-29 2019-12-31 2020-01-01 \
2019-12-01 2019-12-31 2019-12-31 2020-12-31 2019-12-31 2021-12-31 \
2020-09-15 2021-09-16 2019-09-15 2021-09-16
Results:
The difference between 2019-02-10 and 2022-11-01 is 3 years and 264 days
The difference between 2020-09-15 and 2022-03-29 is 1 year and 195 days
The difference between 2019-12-31 and 2020-01-01 is 0 years and 1 day
The difference between 2019-12-01 and 2019-12-31 is 0 years and 30 days
The difference between 2019-12-31 and 2020-12-31 is 1 year and 0 days
The difference between 2019-12-31 and 2021-12-31 is 2 years and 0 days
The difference between 2020-09-15 and 2021-09-16 is 1 year and 1 day
The difference between 2019-09-15 and 2021-09-16 is 2 years and 1 day