This article has been moved from «perl6.eu» and updated to reflect the language rename in 2019.
This is my response to the Perl Weekly Challenge #{{PWC}}.
This is easy. We start with a list (or rather a sequence) of the years, and create a new «Date» object for the 25. December of each year. The «day-of-week» method gives us the (you guessed it) day of the week, where 1 is Monday and so on up to 7 for Sunday.
File: sun-x-mas-where
unit sub MAIN (Int :$from = 2019, Int :$to where $to >= $from = 2100);
for $from .. $to -> $year
{
say "25 Dec $year is Sunday." if Date.new($year, 12, 25).day-of-week == 7;
}
I have added support for user specified start and end years, and have added a «where» clause on the end year demanding that it is the same or greater than the start year.
Running it:
$ raku sun-x-mas-where
25 Dec 2022 is Sunday.
25 Dec 2033 is Sunday.
25 Dec 2039 is Sunday.
25 Dec 2044 is Sunday.
25 Dec 2050 is Sunday.
25 Dec 2061 is Sunday.
25 Dec 2067 is Sunday.
25 Dec 2072 is Sunday.
25 Dec 2078 is Sunday.
25 Dec 2089 is Sunday.
25 Dec 2095 is Sunday.
With user specified upper and lower limits:
$ raku sun-x-mas-where --from=1982 --to=2010
25 Dec 1983 is Sunday.
25 Dec 1988 is Sunday.
25 Dec 1994 is Sunday.
25 Dec 2005 is Sunday.
See docs.raku.org/type/Date for more information about the «Date» class.
See
docs.raku.org/type/Signature#index-entry-where_clause for more information
about where
.
We can get rid of the «where» clause by using «...» (a range) instead of «..» (a sequence) as the latter can count down as well. I have also changed the type of the variables from «Int» to «UInt» (Unsigned Int), to prevent negative values.
File: sun-x-mas
unit sub MAIN (UInt :$from = 2019, UInt :$to = 2100);
for $from ... $to -> $year
{
say "25 Dec $year is Sunday." if Date.new($year, 12, 25).day-of-week == 7;
}
See docs.raku.org/type/UInt for more information about the «UInt» type.
As a one-liner, just for fun (with a couple of newlines added for readability):
File: sun-x-mas-oneliner
say "25 Dec $_ is Sunday."
if Date.new($_, 12, 25).day-of-week == 7
for @*ARGS[0] // 2019 ... @*ARGS[1] // 2100;
Note that you loose the named arguments, and must specify the limits as positional arguments. Specify none, the start, or both start and stop:
$ raku sun-x-mas-oneliner 2090
25 Dec 2095 is Sunday.
If you don't think that «if» and (especially) «for» belong in a one-liner, use «map» and «grep». This is also a one-liner, but again with newlines added for readability:
File: sun-x-mas-grep
(@*ARGS[0] // 2019 ... @*ARGS[1] // 2100)
.grep({ Date.new($_, 12, 25).day-of-week == 7 })
.map({ say "25 Dec $_ is Sunday." });
The traditional «if» and «for» approach is easier to understand.
All versions of the program give the same result, but the error handling (and reporting) differ.
The «at least one of the number is even» clause is redundant, as it is necessary to get an even sum out of three integers:
The number of solutions is infinite if we allow non-integers or negative integers, so I have chosen to disregard them.
We can write this as a one-liner:
File: series-3
.say for (1..10, 1..10, 1..10).flat.combinations(3).unique(:with(&[eqv]))
# 6 # 1 ####################### # 2 ## 3 ############ 4 ##################
.grep(*.sum == 12);
# 5 ##############
[1] The numbers can occur several times, so we set up a list with three instances of each of them. I stop at 10, as that is the highest number that works (10 + 1 + 1 = 12).
[2] The parens gave us a list with three elemenst (each consisting of a sequence 1..10), so we apply «flat» to get a single list.
[3] This gives is all the possible combinations of three elements from the list. The combinations are unique, as in from unique positions in the input list. When we have duplicates, as we have here, we get duplicates in the result.
[4] This removes duplicate lists. A bare «unique» doesn't work on lists, so we specify that the comparison should use the «eqv» operator.
[5] The we use «grep» to choose the lists where the sum is 12.
[5] And finally we print the lists, one by one.
See docs.raku.org/type/List#routine_combinations for more information about the «combinations» method.
See docs.raku.org/routine/unique for more information about the «unique» method.
See docs.raku.org/routine/eqv for more information about the «eqv» operator.
Running it:
$ raku series-3
(1 2 9)
(1 3 8)
(1 4 7)
(1 5 6)
(1 6 5)
(1 7 4)
(1 8 3)
(1 9 2)
(1 10 1)
(1 1 10)
(2 3 7)
(2 4 6)
(2 5 5)
(2 6 4)
(2 7 3)
(2 8 2)
(2 9 1)
(2 1 9)
(2 2 8)
(3 4 5)
(3 5 4)
(3 6 3)
(3 7 2)
(3 8 1)
(3 1 8)
(3 2 7)
(3 3 6)
(4 5 3)
(4 6 2)
(4 7 1)
(4 1 7)
(4 2 6)
(4 3 5)
(4 4 4)
(5 6 1)
(5 1 6)
(5 2 5)
(5 3 4)
(5 4 3)
(5 5 2)
(6 1 5)
(6 2 4)
(6 3 3)
(6 4 2)
(6 5 1)
(7 1 4)
(7 2 3)
(7 3 2)
(7 4 1)
(8 1 3)
(8 2 2)
(8 3 1)
(9 1 2)
(9 2 1)
(10 1 1)
So let us remove them:
File: series-3-unduplicated
.say for (1..10, 1..10, 1..10).flat.combinations(3)>>.sort.unique(:with(&[eqv])).grep(*.sum == 12);
This doesn't work:
$ raku series-3-unduplicated
The iterator of this Seq is already in use/consumed by another Seq
(you might solve this by adding .cache on usages of the Seq, or
by assigning the Seq into an array)
in block <unit> at series-3-unduplicated line 3
The «>>.sort» part sorts the order of the sublists, so that we can ge trid of duplicates.
I am unable to sort it out. Explicit array assignment at each step doesn't help:
File: series-3-unduplicated2
my @source = (1..10, 1..10, 1..10).flat;
my @comb = @source.combinations(3)>>.sort;
say @comb.WHAT;
my @unique = @comb.unique(:with(&[eqv]));
my @result = @unique.grep(*.sum == 12);
.say for @result;
$ raku series-3-unduplicated2
(Array)
The iterator of this Seq is already in use/consumed by another Seq
(you might solve this by adding .cache on usages of the Seq, or
by assigning the Seq into an array)
in block <unit> at series-3-unduplicated2 line 8
The error comes from the «@unique ...» line. I don't get it, as the input is an array (as shown by the «say» line above it, and not a Sequence. (I'll update the article if anybody helps me out.)
I can cheat:
File: series-3-cheating
my %seen;
for (1 .. 10, 1..10, 1..10).flat.combinations(3).unique(:with(&[eqv])).grep(*.sum == 12)
{
my @sorted = $_.sort;
next if %seen{@sorted.Str};
say @sorted;
%seen{@sorted.Str} = True;
}
Running it:
$ raku series-3-cheating
[1 2 9]
[1 3 8]
[1 4 7]
[1 5 6]
[1 1 10]
[2 3 7]
[2 4 6]
[2 5 5]
[2 2 8]
[3 4 5]
[3 3 6]
[4 4 4]
Note that the lines in the output isn't sorted. That can be fixed, but I'll leave that as an excercise for the readers.
And that's it.
my @comb = (1..10, 1..10, 1..10).flat.combinations(3)>>.sort>>.&slip;
And because I love the cross operator (which also has the advantage of returned the results in sorted order):
my @comb = (1..10 X[xx] 3).flat.combinations(3)>>.sort>>.&slip;
Having done that, I figured I should try to understand why this was needed, and why it worked. According to the docs, both 'combinations' and 'sort' return a Seq:
multi method combinations(List:D: Int() $of --> Seq:D)
multi method sort(List:D: --> Seq:D)
So the story would be: >>.sort
consumes the sequences emitted by combinations and,
one by one, emits a new set of sequences. To handle each one of those in turn, another
'hyper' operation is needed, calling 'slip' on each element of that list: >>.&slip
.
Very much enjoy reading your solutions of the PWC! (especially when it makes me think...)