This is my response to The Weekly Challenge #187.
Foo and Bar gone on holidays seperately to the same
city. You are given their schedule i.e. start date and end date.
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.
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
«start Foo», ef stands for «end Foo».
The b in the third and fourth variables stands for Bar.
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
@n, having at least 3 numbers.
(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.
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.