with Raku

This is my response to The Weekly Challenge #187.

Two friends,

To keep the task simple, the date is in the form

Write a script to find out for the given schedule, how many days they spent together in the city, if at all.

Example 1:

`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

This is relatively easy, using `Date`

objects and the `day-of-year`

method.

#! /usr/bin/env raku
unit sub MAIN (:v(:$verbose));
my $year = 2022; # [1]
say days-together({ 'Foo' => { 'SD' => '12-01', 'ED' => '20-01' }, # [2]
'Bar' => { 'SD' => '15-01', 'ED' => '18-01' }});
say days-together({ 'Foo' => { 'SD' => '02-03', 'ED' => '12-03' },
'Bar' => { 'SD' => '13-03', 'ED' => '14-03' }});
say days-together({ 'Foo' => { 'SD' => '02-03', 'ED' => '12-03' },
'Bar' => { 'SD' => '11-03', 'ED' => '15-03' }});
say days-together({ 'Foo' => { 'SD' => '30-03', 'ED' => '05-04' },
'Bar' => { 'SD' => '28-03', 'ED' => '02-04' }});
sub days-together ($struct) # [3]
{
my $sf = Date.new($year ~ "-" # [4]
~ $struct<Foo>.<SD>.split("-").reverse.join("-")).day-of-year;
my $ef = Date.new($year ~ "-"
~ $struct<Foo>.<ED>.split("-").reverse.join("-")).day-of-year;
my $sb = Date.new($year ~ "-"
~ $struct<Bar>.<SD>.split("-").reverse.join("-")).day-of-year;
my $eb = Date.new($year ~ "-"
~ $struct<Bar>.<ED>.split("-").reverse.join("-")).day-of-year;
say ": $sf, $ef, $sb, $eb" if $verbose; # [5]
return 0 if $sf > $eb; # [6]
return 0 if $sb > $ef; # [6a]
my $start = max($sf, $sb); # [7]
my $end = min($ef, $eb); # [8]
return $end - $start + 1; # [9]
}

[1] We need a non-leap year for correct calculation of the February/March transition, and 2022 (the current year) just happens to be one.

[2] I am unsure if we should actually take the input format literally, but it sure was fun to so so.

[3] The procedure doing the job.

[4]
Create a `Date`

object. Note the
syntax; first the year (from [1]), then the month and day of month - which is
the reverse of the input format. So we `split`

the input,
`reverse`

the two values, and `join`

them together again.
Then we apply the `day-of-year`

method to get the accumulated day
number in the year.

**sf** stands for
«**s**tart **F**oo», **ef** stands for «**e**nd **F**oo».
The **b** in the third and fourth variables stands for **B**ar.
Why not use `$start-foo`

and so on, you may ask. Why not indeed...

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

class.

See
docs.raku.org/type/Date#(Dateish)_method_day-of-year for more
for information about the `day-of-year`

method.

[5] You can use verbose mode to get the four day-of-year values, if you want to see them.

[6] The two periods do not overlap if the Foo one starts after the Bar one has ended. (And vice versa [6a].)

[7] If we get here, we know that the periods do overlap. Get the latest start date.

[8] and the latest end date. This is the overlap period.

[9] The actual number of days is the difference + 1, as both the start and end dates should be included in the tally.

Running it:

$ ./days-together
4
0
2
4

Looking good.

With verbose mode:

$ ./days-together -v
: 12, 20, 15, 18
4
: 61, 71, 72, 73
0
: 61, 71, 70, 74
2
: 89, 95, 87, 92
4

You are given a list of positive numbers,

Write a script to find the triplets

Example 1:

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

Let us start with rule 4; the highest sum of any triplet selection. Let us do it in REPL:

`combinations(3)`

gives us all the possible
cominations of three values from a list with (in this case) five elements:

```
> <1 2 3 4 5>.combinations(3)
((1 2 3) (1 2 4) (1 2 5) (1 3 4) (1 3 5) (1 4 5) (2 3 4) (2 3 5) (2 4 5) (3 4 5))
```

See
docs.raku.org/routine/combinations
for more information about `combinations`

.

The sum of each list (or combination):

```
> <1 2 3 4 5 6>.combinations(3)>>.sum
(6 7 8 8 9 10 9 10 11 12)
```

And finally the highest value:

```
> <1 2 3 4 5>.combinations(3)>>.sum.max
12
```

This just happens to be the last element in the list of sums, as the input list was
sorted in ascending order - and `combinations`

seems to retain that order.
We should not assume that this is will always be the case. (And it is indeed not
the case in three of the examples.)

Then the triplets. The first list we got (see above) includes
all the combinations (as a set, where order does not matter), but order do matter to
us. We can slap on `permutations`

to get the permutations of each list.

```
> <1 2 3 4 5>.combinations(3)>>.permutations
(((1 2 3) (1 3 2) (2 1 3) (2 3 1) (3 1 2) (3 2 1)) \
((1 2 4) (1 4 2) (2 1 4) (2 4 1) (4 1 2) (4 2 1)) \
((1 2 5) (1 5 2) (2 1 5) (2 5 1) (5 1 2) (5 2 1)) \
((1 3 4) (1 4 3) (3 1 4) (3 4 1) (4 1 3) (4 3 1)) \
((1 3 5) (1 5 3) (3 1 5) (3 5 1) (5 1 3) (5 3 1)) \
((1 4 5) (1 5 4) (4 1 5) (4 5 1) (5 1 4) (5 4 1)) \
((2 3 4) (2 4 3) (3 2 4) (3 4 2) (4 2 3) (4 3 2)) \
((2 3 5) (2 5 3) (3 2 5) (3 5 2) (5 2 3) (5 3 2)) \
((2 4 5) (2 5 4) (4 2 5) (4 5 2) (5 2 4) (5 4 2)) \
((3 4 5) (3 5 4) (4 3 5) (4 5 3) (5 3 4) (5 4 3)))
```

See
docs.raku.org/routine/permutations for more information about `permutations`

.

Each row (I have added the newlines) correspond to a sublist in the
original `combination`

result, as shown above.

Two levels (of lists inside lists) would be better. We can use `[*;*]`

to flatten the two levels down to one. (The number of semicolons gives the number
of levels to flatten, and the only other character allowed is the `*`

(a whatever star).)

```
> <1 2 3 4 5>.combinations(3)>>.permutations[*;*]
((1 2 3) (1 3 2) (2 1 3) (2 3 1) (3 1 2) (3 2 1) (1 2 4) (1 4 2) (2 1 4) \
(2 4 1) (4 1 2) (4 2 1) (1 2 5) (1 5 2) (2 1 5) (2 5 1) (5 1 2) (5 2 1) \
(1 3 4) (1 4 3) (3 1 4) (3 4 1) (4 1 3) (4 3 1) (1 3 5) (1 5 3) (3 1 5) \
(3 5 1) (5 1 3) (5 3 1) (1 4 5) (1 5 4) (4 1 5) (4 5 1) (5 1 4) (5 4 1) \
(2 3 4) (2 4 3) (3 2 4) (3 4 2) (4 2 3) (4 3 2) (2 3 5) (2 5 3) (3 2 5) \
(3 5 2) (5 2 3) (5 3 2) (2 4 5) (2 5 4) (4 2 5) (4 5 2) (5 2 4) (5 4 2) \
(3 4 5) (3 5 4) (4 3 5) (4 5 3) (5 3 4) (5 4 3))
```

See
docs.raku.org/language/subscripts#index-entry-flattening_ for more
information about `[*;*]`

.

So far, so good. Byt what about duplicates - which we get if we have duplicate values in the input list:

> <1 1 1 1 2>.combinations(3)>>.permutations[*;*]
((1 1 1) (1 1 1) (1 1 1) (1 1 1) (1 1 1) (1 1 1) (1 1 1) (1 1 1) (1 1 1) \
(1 1 1) (1 1 1) (1 1 1) (1 1 2) (1 2 1) (1 1 2) (1 2 1) (2 1 1) (2 1 1) \
...

Getting rid of duplicates with a plain `unique`

does not work:

```
> <1 1 1 1 2>.combinations(3)>>.permutations[*;*].unique
((1 1 1) (1 1 1) (1 1 1) (1 1 1) (1 1 1) (1 1 1) (1 1 1) (1 1 1) (1 1 1) \
(1 1 1) (1 1 1) (1 1 1) (1 1 2) (1 2 1) (1 1 2) (1 2 1) (2 1 1) (2 1 1) \
...
```

The Equvivalence Operator `eqv`

is
the thing. We can specify that `unique`

, which by default considers scalar
values only, should use that to compare them:

```
> <1 1 1 1 2>.combinations(3)>>.permutations[*;*].unique(:with(&[eqv]))
((1 1 1) (1 1 2) (1 2 1) (2 1 1))
```

See
docs.raku.org/routine/eqv for more
information about the Equivalence Operator `eqv`

.

Then we can do the progran:

File: magical-triplets```
#! /usr/bin/env raku
unit sub MAIN (*@n where @n.elems >= 3, :v(:$verbose)); # [1]
my $max = @n.combinations(3)>>.sum.max;
say ":Max: $max" if $verbose;
my @candidates # [2]
= @n>>.Int.combinations(3)>>.permutations[*;*].unique(:with(&[eqv]));
say ":Permutations: { @candidates.raku }" if $verbose;
my @ok = @candidates.grep( { $_[0] + $_[1] > $_[2] && # [3]
$_[1] + $_[2] > $_[0] &&
$_[0] + $_[2] > $_[1] &&
$_.sum == $max });
say ":Rule 1-4 applied: { @ok.raku }" if $verbose;
if @ok.elems == 0 # [4]
{
say "()";
}
elsif @ok.elems == 1 # [5]
{
say "(", @ok[0].join(", "), ")";
}
else # [6]
{
say "(", @ok.grep( { $_[0] >= $_[1] >= $_[2] })[0].join(", "), ")";
}
```

[1] Ensure that we get at least 3 values. Note the missing argument type check.

[2] The `>>.`

part is there as we get the input from
the command line, and that gives us values of the `IntStr`

type - which
will lead to massive output from the `raku`

method in the verbose output.
This enforcer can be removed if you do not care about that. The result will be
correct either way.

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

type.

[3] Apply the four rules, with `grep`

.

[4] No result? Say so.

[5] Exactly one result? Print it. Note that we have a list with one element, which
itself is a list. Thus the `[0]`

index. (The `first`

method
is perhaps nicer on the eyes, but it is longer...)

[6] More than one result? Apply the bonus rule (with `grep`

) and print
the first (and hopefully only) element in the resulting list.

Running it:

$ ./magical-triplets 1 2 3 2
(3, 2, 2)
$ ./magical-triplets 1 3 2
()
$ ./magical-triplets 1 1 2 3
()
$ ./magical-triplets 2 4 3
(4, 3, 2)

Looking good.

With verbose mode:

$ ./magical-triplets -v 1 2 3 2
:Max: 7
:Permutations: [(1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), \
(3, 2, 1), (1, 2, 2), (2, 1, 2), (2, 2, 1), (2, 3, 2), (2, 2, 3), \
(3, 2, 2)]
:Rule 1-4 applied: [(2, 3, 2), (2, 2, 3), (3, 2, 2)]
(3, 2, 2)
$ ./magical-triplets -v 1 3 2
:Max: 6
:Permutations: [(1, 3, 2), (1, 2, 3), (3, 1, 2), (3, 2, 1), (2, 1, 3), \
(2, 3, 1)]
:Rule 1-4 applied: []
()
$ ./magical-triplets -v 1 1 2 3
:Max: 6
:Permutations: [(1, 1, 2), (1, 2, 1), (2, 1, 1), (1, 1, 3), (1, 3, 1), \
(3, 1, 1), (1, 2, 3), (1, 3, 2), (2, 1, 3), (2, 3, 1), (3, 1, 2), \
(3, 2, 1)]
:Rule 1-4 applied: []
()
$ ./magical-triplets -v 2 4 3
:Max: 9
:Permutations: [(2, 4, 3), (2, 3, 4), (4, 2, 3), (4, 3, 2), (3, 2, 4), \
(3, 4, 2)]
:Rule 1-4 applied: [(2, 4, 3), (2, 3, 4), (4, 2, 3), (4, 3, 2), (3, 2, 4), \
(3, 4, 2)]
(4, 3, 2)

A final one, with one result so that we do not have to apply the bonus rule:

$ ./magical-triplets -v 1 1 1
:Max: 3
:Permutations: [(1, 1, 1),]
:Rule 1-4 applied: [(1, 1, 1),]
(1, 1, 1)

And that's it.