## 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.

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.)

If you define an arithmetic series or a geometric series, yes, it is usually supposed to progress, although I think you could very well define a geometric series with a ratio of 1, where all the terms are the same as the initial one. Similarly, an arithmetic series might have a 0 or a negative increment, so that the terms would not be ascending. With no specification at all as to what type of series, I understand the word to mean any sequence of three numbers. Having said that, I guess that you are totally free to chose your own interpretation of the word (just as I chose to reject any null number in the series, as it would boil down to really less that three numbers from the standpoint of addition).