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.