by Arne Sommer

# Unique Difference with Raku and Perl

[203] Published 25. September 2022.

This is my response to The Weekly Challenge #183.

## Challenge #183.1: Unique Array

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]) ```

Raku does not have arrayrefs, but real multidimentional arrays are much better.

File: unique-array ```#! /usr/bin/env raku unit sub MAIN (:v(:\$verbose)); my @list1 = ([1,2], [3,4], [5,6], [1,2]); # [1] my @list2 = ([9,1], [3,7], [2,5], [2,5]); # [1] say unique-array(@list1); # [2] say unique-array(@list2); # [2] sub unique-array (@list) # [3] { my @unique; # [4] my %seen; # [5] for @list -> \$ref # [6] { next if %seen{\$ref}; # [7] @unique.push: \$ref; # [8] %seen{\$ref}++; # [9] } say ": Seen: " ~ %seen.raku if \$verbose; return @unique; # [10] } ```

[1] I have chosen to hard code the arrays.

[2] Then we call the procedure on each one, printing the result.

[3] The procedure.

[4] The return value will end up here; the list of unique sublists.

[5] A list of sublist seen so far.

[6] Iterate over the sublists. (Do not let the variable name «\$ref» fool you; this is an array.)

[7] Skip sublists we have already seen. Note that using an array as a key will stringify it. The result is the two values with a space character between them.

[8] Add the sublist to the result.

[9] Mark the sublist as seen.

[10] Return the unique array.

Running it:

```\$ ./unique-array [[1 2] [3 4] [5 6]] [[9 1] [3 7] [2 5]] ```

We got brackets all the way, instead of only for the sublists. That is probably ok.

With verbose mode:

```\$ ./unique-array -v : Seen: {"1 2" => 1, "3 4" => 1, "5 6" => 1} [[1 2] [3 4] [5 6]] : Seen: {"2 5" => 1, "3 7" => 1, "9 1" => 1} [[9 1] [3 7] [2 5]] ```

### A Perl Version

This is straight forward translation of the Raku version.

File: unique-array-perl ```#! /usr/bin/env perl use strict; use warnings; use feature 'say'; use feature 'signatures'; no warnings 'experimental::signatures'; my @list1 = ([1,2], [3,4], [5,6], [1,2]); my @list2 = ([9,1], [3,7], [2,5], [2,5]); say "(", join(", ", map { "[\$_->[0],\$_->[1]]" } unique_array(@list1)), ")"; # [1] say "(", join(", ", map { "[\$_->[0],\$_->[1]]" } unique_array(@list2)), ")"; # [1] sub unique_array (@list) { my @unique; my %seen; for my \$ref (@list) { next if \$seen{"\$ref->[0] \$ref->[1]"}; # [1] push @unique, \$ref; \$seen{"\$ref->[0] \$ref->[1]"}++; # [1] } say ": Seen: %seen" if \$verbose; return @unique; } ```

[1] Stringification of an arrayref does not work (the result is a string like this: `ARRAY(0x56073743b5f0)`, where the first part is the type, and the second the memory location. That location will obviously not be the same for different objects - evenif the values turn out to be the same. So we have to do the stringification manually.

Running it gives the same result as the Raku version, except that we got the brackets right this time - as we printed them manually with «map»).

```\$ ./unique-array-perl ([1,2], [3,4], [5,6]) ([9,1], [3,7], [2,5]) ```

Verbose mode has gone, as it is difficult to print these structures, as shown by [1].

## Challenge #183.2:

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.

Examples ```Input: \$date1 = '2019-02-10' \$date2 = '2022-11-01' Output: 3 years 264 days Input: \$date1 = '2020-09-15' \$date2 = '2022-03-29' Output: 1 year 195 days Input: \$date1 = '2019-12-31' \$date2 = '2020-01-01' Output: 1 day Input: \$date1 = '2019-12-01' \$date2 = '2019-12-31' Output: 30 days Input: \$date1 = '2019-12-31' \$date2 = '2020-12-31' Output: 1 year Input: \$date1 = '2019-12-31' \$date2 = '2021-12-31' Output: 2 years Input: \$date1 = '2020-09-15' \$date2 = '2021-09-16' Output: 1 year 1 day Input: \$date1 = '2019-09-15' \$date2 = '2021-09-16' Output: 2 years 1 day ```

Placing the dates in a text file, one set per row, gives us an easy way of supplying them to the program.

File: dates.txt ```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 2023-01-01 2023-01-01 ```

I have added the last one.

### Perl

Let us start with the Perl version this time.

There are more than one Date module, but Date::Calc - which I used two weeks ago (in Hot Sentence with Raku and Perl) - works out here as well.

File: date-difference-perl ```#! /usr/bin/env perl use strict; use warnings; use feature 'say'; use File::Slurp; use Date::Calc qw/N_Delta_YMD Delta_Days/; use feature 'signatures'; no warnings 'experimental::signatures'; my \$file = shift(@ARGV) || "dates.txt"; my @rows = read_file(\$file, chomp => 1); # [1] for my \$row (@rows) # [2] { my (\$date1, \$date2) = split(/\s+/, \$row); # [3] say "\$date1 vs \$date2 -> " . date_diff(\$date1, \$date2); # [4] } sub date_diff (\$date1, \$date2) { (\$date1, \$date2) = (\$date2, \$date1) if \$date1 gt \$date2; # [5] my (\$y, \$m, \$d) = N_Delta_YMD(split("-", \$date1), split("-", \$date2)); # [6] my (\$y2, \$m2, \$d2) = split("-", \$date1); # [7] my \$days = Delta_Days(\$y2 + \$y, \$m2, \$d2, split("-", \$date2)); # [8] my @return; # [9] push(@return, plural("year", \$y)) if \$y; # [10] push(@return, plural("day", \$days)) if \$days; # [11] return "0 days" unless @return; # [12] return join(" ", @return); # [13] } sub plural (\$label, \$val) { return "\$val \$label" if \$val == 1; # [14] return "\$val \$label". "s"; # [14a] } ```

[1] «read_file» is supplied by «File::Slurp».

[2] For each row in the file,

[3] Get the start and end dates.

[4] Print the dates, and the difference.

[5] Ensure that the first date is the lowest.

[6] Get the difference between the two dates, in days, months and days.

[7] Get the parts (year, month, day) of the first date.

[8] Add the number of years (from [6]), and then get the difference in days.

[9] The return value.

[10] Add the day(s) part, if any.

[11] Add the year(s) part, if any.

[12] The same day? Say so.

[13] Return the year(s) and day(s).

[14] Add the plural postfix «s» where applicable.

Running it:

```\$ ./date-difference-perl 2019-02-10 vs 2022-11-01 -> 3 years 264 days 2020-09-15 vs 2022-03-29 -> 1 year 195 days 2019-12-31 vs 2020-01-01 -> 1 day 2019-12-01 vs 2019-12-31 -> 30 days 2019-12-31 vs 2020-12-31 -> 1 year 2019-12-31 vs 2021-12-31 -> 2 years 2020-09-15 vs 2021-09-16 -> 1 year 1 day 2019-09-15 vs 2021-09-16 -> 2 years 1 day 2023-01-01 vs 2023-01-01 -> 0 days ```

Looking good.

### Raku

This is a straight forward translation of the Perl version, using the builtin «Date» class.

See docs.raku.org/type/Date for more information about the `Date` class.

File: date-difference ```#! /usr/bin/env raku unit sub MAIN (\$file where \$file.IO.f && \$file.IO.r = "dates.txt"); my @rows = \$file.IO.lines; for @rows -> \$row { my (\$date1, \$date2) = \$row.words; say "\$date1 vs \$date2 -> " ~ date-diff(\$date1, \$date2); } sub date-diff (\$date1, \$date2) { (\$date1, \$date2) = (\$date2, \$date1) if \$date1 gt \$date2; my \$d1 = \$date1.Date; my \$d2 = \$date2.Date; my \$years = 0; # [1] while ( \$d2 >= \$d1.later(:year)) # [1a] { \$years++; # [1b] \$d1 = \$d1.later(:year); # [1c] } my \$days = \$d2.daycount - \$d1.daycount; # [2] my @return; push(@return, plural("year", \$years)) if \$years; push(@return, plural("day", \$days)) if \$days; return "0 days" unless @return; return join(" ", @return); } sub plural (\$label, \$val) { return "\$val \$label" if \$val == 1; return "\$val \$label" ~ "s"; } ```

[1] Count the number of years; as long as the first date is one year or more earlier than the second one [1a], add one year (to the count [1b] and the date [1c].

[2] There is, as far as I can see, no way of comparing (getting the difference) two `Date` objects, but the `daycount` method gives the number of days since the epoch - and subtracting the two daycounts give the difference in days.

Running it gives the same result as the Perl version:

```\$ ./date-difference 2019-02-10 vs 2022-11-01 -> 3 years 264 days 2020-09-15 vs 2022-03-29 -> 1 year 195 days 2019-12-31 vs 2020-01-01 -> 1 day 2019-12-01 vs 2019-12-31 -> 30 days 2019-12-31 vs 2020-12-31 -> 1 year 2019-12-31 vs 2021-12-31 -> 2 years 2020-09-15 vs 2021-09-16 -> 1 year 1 day 2019-09-15 vs 2021-09-16 -> 2 years 1 day 2023-01-01 vs 2023-01-01 -> 0 days ```

And that's it.