Perl Weekly Challenge 30: Sunday Christmas and Triplets

These are some answers to the Week 30 of the Perl Weekly Challenge organized by Mohammad S. Anwar.

Spoiler Alert: This weekly challenge deadline is due in a few days from now (October 20, 2019). This blog post offers some solutions to this challenge, please don't read on if you intend to complete the challenge on your own.

Challenge # 1: Sunday Christmas

Write a script to list dates for Sunday Christmas between 2019 and 2100. For example, 25 Dec 2022 is Sunday.

Christmas on Sunday in Perl 5

I'll be using the Time::Local core module which provides reciprocal functions of the gmtime and localtime built-in functions.

#!/usr/bin/perl
use strict;
use warnings;
use feature qw/say/;
use Time::Local;

say "Years during which Christmas falls on a Sunday:";
for my $year (119..200) {
    my $date = timegm(0, 0, 0, 25, 11, $year);
    say $year + 1900 if (gmtime $date)[6] == 0;
}

Note that both the built-in gmtime and the module timegm functions count the year from 1900 upward (so that 2019 should be input as 119) and start the month count at 0, so that December is considered to be 11.

The above program displays the following:

Years during which Christmas falls on a Sunday:
2022
2033
2039
2044
2050
2061
2067
2072
2078
2089
2095

We could also do it as a (slightly long) one-liner:

$ perl -MTime::Local -E 'say join " ", map {(gmtime $_)[5] + 1900}  grep { (gmtime $_)[6] == 0 } map {timegm(0, 0, 0, 25, 11, $_)} 119..200;'
2022 2033 2039 2044 2050 2061 2067 2072 2078 2089 2095

Christmas on Sunday in Perl 6

In Perl 6, the Date data type offers the built-in methods we need for date computations, including finding day of week.

use v6;
for 2019..2100 -> $year {
    say "Christmas of year $year falls on a Sunday." 
        if Date.new($year, 12, 25).day-of-week == 7;
}

which duly prints out:

Christmas of year 2022 falls on a Sunday.
Christmas of year 2033 falls on a Sunday.
Christmas of year 2039 falls on a Sunday.
Christmas of year 2044 falls on a Sunday.
Christmas of year 2050 falls on a Sunday.
Christmas of year 2061 falls on a Sunday.
Christmas of year 2067 falls on a Sunday.
Christmas of year 2072 falls on a Sunday.
Christmas of year 2078 falls on a Sunday.
Christmas of year 2089 falls on a Sunday.
Christmas of year 2095 falls on a Sunday.

We could also do it in the form of a Perl 6 one-liner:

$ perl6 -e 'say grep {Date.new($_, 12, 25).day-of-week == 7}, 2019..2100;'
(2022 2033 2039 2044 2050 2061 2067 2072 2078 2089 2095)

Integer Triplets Whose Sum is 12

Write a script to print all possible series of 3 numbers, where in each series at least one of the number is even and sum of the three numbers is always 12. For example, 3,4,5.

This is not specified, but we will consider that all three numbers should be strictly positive (i.e. larger than or equal to 1), because if we were to admit 0 as one of the numbers, it would no longer be a real triplet (in the context of addition). A consequence is that the largest number that can be used is 10 (to obtain 12 when adding twice 1).

Integer Triplets in Perl 5

We will use three nested loops for visiting all possibilities for the three numbers. However, we don't want to obtain duplicate triplets such as (1, 2, 9), (2, 1, 9), (9, 1, 2), etc., which are all the same. Therefore, when looping on the second number, we will loop from the first number to 10, and similarly for the third number. Thus, each triplet will be in (non strict) ascending order and won't get any duplicate.

We also need at least one of the three numbers to be even; for that, we can check whether the product of the three numbers is even (more on this later).

Our first (somewhat naïve) implementation could be as follows:

#!/usr/bin/perl
use strict;
use warnings;
use feature qw/say/;

for my $i (1..10) {
    for my $j ($i..10) {
        last if $i + $j > 11;
        for my $k ($j..10) {
            next unless $i * $j * $k % 2 == 0; # Check 1 number is even
            my $sum = $i + $j + $k;
            last if $sum > 12;
            say "$i, $j, $k" if $sum == 12;
        }
    }
}

This program prints the following correct result:

$ perl triplet.pl
1, 1, 10
1, 2, 9
1, 3, 8
1, 4, 7
1, 5, 6
2, 2, 8
2, 3, 7
2, 4, 6
2, 5, 5
3, 3, 6
3, 4, 5
4, 4, 4

But we're doing a bit too much work here when we check whether one of the numbers is even. The only case where none of the numbers of a triplet is even is when all three numbers are odd, and the sum of three odd integers cannot be 12 (and, more generally, cannot be an even number). So, we simply don't need to check that one number is even: checking that the sum of the 3 numbers if 12 is sufficient to prove that one at least of the three numbers is even.

So we can rewrite the nested loops as follows:

for my $i (1..10) {
    for my $j ($i..10) {
        last if $i + $j > 11;
        for my $k ($j..10) {
            my $sum = $i + $j + $k;
            last if $sum > 12;
            say "$i, $j, $k" if $sum == 12;
        }
    }
}

And this new version produces the same output.

Integer Triplets in Perl 6

We've seen before that we don't need to check that one of the numbers is even.

For solving this problem in Perl 6, we would like to use the X cross product operator in order to generate all possible triplets and then keep those whose sum is 12.

But if we do something like this:

for 1..10 X 1..10 X 1..10 -> $triplet {
    next unless ([+] | $triplet) == 12;
    say $triplet;
}

we obtain duplicate triplets:

...
(1 2 9)
...
(2 1 9)
...
(2 9 1)
...
(9 1 2)
(9 2 1)
...

We can get rid of this problem by keeping only triplets in which the numbers are in (non strict) ascending order:

use v6;
for 1..10 X 1..10 X 1..10 -> $triplet {
    next unless [<=] | $triplet;  # ascending order
    say $triplet if 12 == [+] $triplet;
}

which produces the desired result:

$ perl6 triplets.p6
(1 1 10)
(1 2 9)
(1 3 8)
(1 4 7)
(1 5 6)
(2 2 8)
(2 3 7)
(2 4 6)
(2 5 5)
(3 3 6)
(3 4 5)
(4 4 4)

Wrapping up

The next week Perl Weekly Challenge is due to start soon. If you want to participate in this challenge, please check https://perlweeklychallenge.org/ and make sure you answer the challenge before 23:59 BST (British summer time) on Sunday, October, 27. And, please, also spread the word about the Perl Weekly Challenge if you can.

2 Comments

I thought a series had to progress? (So 1,2,3 would be a series but not 1,1,2).

(My math is bad though so I'm probably wrong. But I based my answer on that definition.)

Leave a comment

About laurent_r

user-pic I am the author of the "Think Perl 6" book (O'Reilly, 2017) and I blog about the Perl 5 and Raku programming languages.