PWC 183

PWC 183

Challenge 1 (Unique array)

Given an array of arrays, we are asked to remove any duplicate elements (arrays) and return the sub-array with only unique elements.

Thus, given ([1,2],[3,4],[5,6],[1,2]), the script should return ([1,2],[3,4],[5,6]).

Perl 5

In Perl 5, PDL offers an immediate solution via the uniqvec method. One just reads the list into a piddle:

$list = pdl ([1,2],[3,4],[5,6],[1,2]);

and then calls uniqvec on it:

$list->uniqvec 

The uniqvec method still works if the piddle has rows of unequal lengths, as in ([1,2],[3,4],[5,6,7],[1,2]). In this case, it pads the shorter rows with zeroes in the output:

[
    [1,2,0]
    [3,4,0]
    [5,6,7]
]

I have presented my Perl 5 solution as a perldl executable command history.

Here is my perldl script.

Julia

Julia has something similar to uniqvec in the unique function.

println(unique(arry))

Just like PDL, it handles rows of unequal lengths. It does not pad the shorter rows with zeroes.

println(unique([[1,2],[3,4],[5,6,7],[1,2]])

gives:

[[1,2],[3,4],[5,6,7]]

Here is my Julia script. 

Raku

Raku needs slightly more work than the specialized array programming languages. Here is the key snippet of Raku code:

say @arry.map(*.gist).unique; 

I stringify the elements of @arry. Then I use the unique array method to drop duplicates.

For @arry=([1,2],[3,4],[5,6],[1,2]), this prints:

([1 2] [3 4] [5 6])

Here is my Raku script.

This actually returns an array of strings rather than an array of arrays, which maybe is not what the task seeks.  To turn the strings back into arrays is not too easy, since Raku disables its EVAL by default. This is how you do it:

use MONKEY-SEE-NO-EVAL;

my @arry=([1,2],[3,4],[5,6],[1,2]);
say @arry.map(*.raku).unique.map({EVAL($_)});
#([1 2] [3 4] [5 6])

We need .raku rather than .gist.

Raku allows direct equality checks for arrays via the 'eqv' operator. I could not get this to work with unique, so .gist is my fallback.  

Luckily, there are Raku experts in the house who can show us how to use 'eqv'. Here is Robert Ransbottom's solution. The key snippet:

@list.unique( :with( &[eqv]))

This returns an array of arrays.

Using .gist would also run more slowly.  See for example my  PWC 176 blog post (PWC 176 task 1 takes 16 seconds with .gist and 1.8 seconds with eqv).

 

Challenge 2 (Date difference)

Challenge 2 gives us two date strings in YYYY-MM-DD format. We are asked to find the  difference between the dates they represent. This should be expressed in years and days: "A years B days".

Thus for the date strings 2019-02-10 and 2022-11-01, the expected answer would be "3 years 264 days".

Perl 5

For my Perl 5 script, I use Date::Manip

My script gives the expected answers for all the test examples.

But there is what you might call a bug. I don't "fix" it, for reasons discussed below.

The maybe-a-bug is as follows. If I try for example:

diff_in_y_d( '2019-09-15','2020-09-14')

I get the output:

1 year(s)  -1 day(s)

not "0 year(s) 365 day(s)".

This is a deliberate design feature in version 6+ of Date::Manip. The package maintainers opine that it's more natural to think of this time difference as "1 year less a day" rather than "0 years plus 365 days". 

It doesn't seem to be consistently implemented. For example, the difference between '2019-09-01' and '2020-08-31' is "0 years 365 days" from my script or in terms of the raw Date::Manip duration (delta) object "0 years, 11 months, 4 weeks and 2 days". The negative day thing only kicks in when the months are the same for both dates.

I will treat it as a feature not a bug. It's accurate, and the task specification does not rule out a negative number of days.

The fix if desired would be to use version 5 of Date::Manip. In version 5, the calculation of incremental days is always positive.

Let me explain my solution. 

Date::Manip has three ways of computing a date difference or delta object. These are:

  1. Exact: Here the delta is computed in seconds and can be expressed in hours, minutes and seconds, which are simply fixed multiples of seconds. For example, for the dates 2019-02-10 and 2022-11-01, the delta is given by the (Years, Months, Weeks, Days, Hours, Minutes, Seconds) array  (0,0,0,0,32640,0,0) or 32,640 hours.
  2. Semi-exact: Here the difference is computed in days and can be expressed in weeks and days, which are simply fixed multiples of days. A day is a less precise unit than a second, hence this is less exact or semi-exact. For example, for the dates 2019-02-10 and 2022-11-01, the delta is given by the (Years, Months, Weeks, Days, Hours, Minutes, Seconds) array  (0,0,194,2,0,0,0) or 194 weeks and 2 days.
  3. Approx: Here the difference is expressed in a conventional form: Years, Months, Weeks, Days, Hours, Minutes, Seconds. For example, for the dates 2019-02-10 and 2022-11-01, the delta is given by the (Years, Months, Weeks, Days, Hours, Minutes, Seconds) array  (3,9,-1,-2,0,0,0) or 3 years, 9 months, -1 week and -2 days.

With an approx calculation, the months and weeks can be converted to days and added to the remaining number of days to get Years and Days. But this does not give a correct answer. For example, for the dates 2019-02-10 and 2022-11-01, the answer with the approx calculation is 3 years, 266.931875 days, not the correct 3 years, 264 days. When converting months to days, Date::Manip uses a fixed standard "days in a month" number rather than counting the actual days in the actual months in the stretch.

To cope with this, I use a two-step procedure. First I compute the approx difference, and I retain the Years say Y.

I then calculate the date that is Y years ahead of my first input date date1 say date1+Y.  

I then find the semi-exact delta between date1+Y and date2, my second input date. 

This gives me the number of days for the answer.

There's some fiddling with the delta print formats to get what I need. I won't get into  details.

Here is my Perl 5 script using Date::Manip.

I prefer Date::Manip or Date::Calc to DateTime. TIMTOWTDI.

Raku and Julia

My Raku and Julia scripts are both very similar to each other. I wrote the Julia script as a direct port of the Raku version. 

Both Raku and Julia have standard ways to handle dates, built-in in Raku and via the standard Dates module in Julia. In both cases, they compute date differences using the semi-exact approach of Date::Manip, i.e., in days. 

I need to compute the difference in years manually say Y. To do this, I check if the month in the later date date2 is less than that in the earlier date date1 (say April in date1 and March in date2). If it is, we need the raw difference between the years less 1, say 2020-2019-1=0. Otherwise, our date1+Y would overshoot date2. If the month in date2 is not less than the month in date1, then we just take the raw difference between years, say 2020-2019=1.

Then, we compute the date that is Y years ahead of date1, and get days by taking the difference between this date and date2.

For date1=2019-09-15 and date2=2020-09-14, this mimics Date::Manip in giving "1 year -1 day". 

I had checked for whether the date2 month was less, but not if the date2 day was less when the month was the same for both dates. We will get a negative number of days if the latter condition is true. Date::Manip behaves similarly.

Julia is slower than both Perl 5 and Raku, at around 0.9 seconds while Perl 5 and Raku run in around 0.4 seconds. 

Julia is slower in parsing date strings. The Julia time improves to 0.3 seconds if the dates are input in the form  Date(year,month,day) rather than Date("yyyy-mm-dd").


Comments

Popular posts from this blog

PWC 227

PWC 234

PWC 249