Welcome to Day 13 of the Perl 6 One-Liner Advent Calendar! Today’s one-liner will be quite long, and it would be better to write it in two lines, but it will show a very nice feature of Perl 6’s Date objects: it can be easily used in a range.
Today, we are solving Problem 19 of the Project Euler. In essence, the task is to count Sundays between 1 January 1901 and 31 December 2000, which fall on the first of the months.
The Date object in Perl 6 implements the succ and prec methods, which are incrementing and decrementing the date. It is also possible to use two dates as the boundaries of a range:
say (
Date.new(year => 1901) ..^ Date.new(year => 2001)
).grep({.day == 1 && .day-of-week == 7}).elems
There are a few moments to comment here.
First, the two Date objects are created with a single named parameter, the year. This is possible because the signature of the constructor has default values for both the month and the day:
multi method new(
Date: Int:D() :$year!,
Int:D() :$month = 1, Int:D() :$day = 1,
:&formatter, *%_) {
. . .
}
So it’s easy to create a date for 1 January, but you can’t do that for the last day of the year. But Perl 6 has a nice range operator ..^, which excludes the right boundary and allows us to save quite a few characters (while we are not playing Perl 6 Golf yet :-).
The longer version, with all explicit parts of the dates, would then look like this:
say (
Date.new(year => 1901, month => 1, day => 1) ..
Date.new(year => 2000, month => 12, day => 31)
).grep({.day == 1 && .day-of-week == 7}).elems
You create a range and grep its values using a combined condition. Remember that there is no need to explicitly type $_ when you want to call a method on the default variable.
An alternative would be to use two greps with a star:
say (
Date.new(year => 1901, month => 1, day => 1) ..
Date.new(year => 2000, month => 12, day => 31)
).grep(*.day == 1).grep(*.day-of-week == 7).elems
An exercise for you to make at home:Â Print the number of days left until the end of this Advent Calendar. See you tomorrow!
Even shorter (and onelinerier):
say +(Date.new(1901,1,1)..^Date.new(2001,1,1)).grep: {.day == 1 && .day-of-week == 7}
Thanks for the introduction to dates and a lot more too. I was going to challenge myself to working out every 2nd Sunday when I had a look at the docs and it was very easy to convert your one liner to second sunday…
{ .day-of-week == 7 && .weekday-of-month == 2}
These are great, and a great jumping off point for more exploration…