Perl Weekly Challenge: Week 30

Week 30 Challenge 1:

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

I ♥ calendar problems so much.

Christmas always falls on December the 25th which is exacly one week before New Years Day of the next year. So if we know the next New Years Day is on a Sunday, we know Christmas is on a Sunday. How do we know which day of the week New Years Day is on? Well, January 1, 2019 was on a Tuesday. January 1, 2020 will be on the next consecutive day of the week i.e. Wednesday. For each subsequent year we increment the day of week by one. The only stumbling block is for leap years which have an extra day. So for the year after a leap year, we have to increment by two. All this sounds more complicated than it is to actually code. Take a look:

my $newYearDay = 3; # start from Jan 1, 2020 which is Wednesday
for my $year (2020 .. 2101) {
    if ($newYearDay % 7 == 0) { # 0 = Sunday
        say $year - 1, '-12-25';
    }
    $newYearDay += (isLeap($year)) ? 2 : 1;
}

(Full code on Github.)

I reused my trusty isLeap() function from challenge 19 and previous challenges.

In Raku you don't need any of that funny business because it has powerful date capabilities builtin so instead I decided to see if I could make this a one-liner. I came up with this:

perl6 -e '"$_-12-25".say for (2019..2100).grep({Date.new($_,12,25).day-of-week==7;});'

(Full code on Github.)

Update: Thanks to Laurent Rosenfeld for pointing out the day of week needs to be compared to 7 not 0 as I originally had it.

Week 30 Challenge 2:

Write a script to print all possible series of 3 positive 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.

A brute force method for solving this would be to just have three nested loops from 1 to 12 each representing one of the numbers, let's call i, j, and k and print any combination that adds up to 12 and contains an even number. But we can get smarter than this.

One, we only need two loops for i and j. Whatever is left after i and j are subtracted from 12 is by definition k.

Two, our loop for i only needs to go up to 10. All three numbers are positive integers so their minimum value is 1. If two of them have a value of 1, the third must equal 10. Values of 11 or 12 are impossible.

Three, for similar reasons to two, the maximum value of j can only be 12 - i - 1.

Putting all that together, I came up with this:

my %results;

for my $i (1 .. 10) {
    for my $j (1 .. (12 - $i - 1)) {
        my $k = 12 - $i - $j;
        $results{join q{ }, sort ($i, $j, $k)}++;
    }
}

say for sort keys %results;

(Full code on Github.)

I stringify the result series and make them the keys in a hash to remove duplicates. I sort the result before hand because e.g. (1,1,10) is the same as (1,10,1) and (10,1,1). (Or is it? the spec doesn't clearly say.)

Also note that I do not explicitly check for one number being even. If either i, j or k are even, the condition is fulfilled. if two of the three are odd, then they add up to an even number and as 12 is even, the remainder of 12 minus that even number must also be even. Therefore we can guarantee that atleast one number in a result series will always be even.

The Raku version is just a straight port of the Perl version so nothing more needs to be said.

sub MAIN() {
    my %results;

    for (1 .. 10) -> $i {
        for (1 .. (12 - $i - 1)) -> $j {
            my $k = 12 - $i - $j;
            %results{($i, $j, $k).sort.join(q{ })}++;
        }
    }

    .say for %results.keys.sort;
}

(Full code on Github.)