Perl Weekly Challenge: Week 137

Challenge 1:

Long Year

Write a script to find all the years between 1900 and 2100 which is a Long Year.

A year is Long if it has 53 weeks.

[UPDATED][2021-11-01 16:20:00]: For more information about Long Year, please refer to wikipedia.

Expected Output

1903, 1908, 1914, 1920, 1925,
1931, 1936, 1942, 1948, 1953,
1959, 1964, 1970, 1976, 1981,
1987, 1992, 1998, 2004, 2009,
2015, 2020, 2026, 2032, 2037,
2043, 2048, 2054, 2060, 2065,
2071, 2076, 2082, 2088, 2093,
2099

Calendrical calculations are a keen interest of mine. In this case the solution was quite straightforward because the referenced Wikipedia page already has a formula you can use. So the code shown below is merely a translation of that without further embellishment.

These two functions represent the Wikipedia formula. One thing that was not readily apparent from reading that page was that the formula uses integer division. In raku this means using the div operator not the normal / which I initially employed. / uses floating point which caused all kinds of puzzling off by one errors until I figured out that I was using the wrong operator.

sub p(Int $year) {
    return (($year + ($year div 4) - ($year div 100) + ($year div 400)) % 7);
}

sub isLongYear(Int $year) {
    return p($year) == 4 || p($year - 1) == 3;
}

Once I had the isLongYear() function, all I had to do is .grep() through all the years between 1900 to 2100 looking for long years and then joining the list together with commas and printing it.

(1900 .. 2100)
    .grep({ isLongYear($_); })
    .join(q{, })
    .say;

(Full code on Github.)

This is the Perl version. Perl doesn't have div like Raku but we can simulate it with a combination of normal / division and the int() function.

sub p {
    my ($year) = @_;
    return (($year + int($year / 4) - int($year / 100) +  int($year / 400)) % 7);
}

sub isLongYear {
    my ($year) = @_;
    return (p($year) == 4 || p($year - 1) == 3); 
}

say join q{, }, grep { isLongYear($_); } (1900 .. 2100);

(Full code on Github.)

Challenge 2:

Lychrel Number

You are given a number, 10 <= $n <= 1000.

Write a script to find out if the given number is Lychrel number. To keep the task simple, we impose the following rules:

a. Stop if the number of iterations reached 500.
b. Stop if you end up with number >= 10_000_000.

[UPDATED][2021-11-01 16:20:00]: If you stop because of any of the above two rules then we expect 1 as an output.

According to wikipedia:

A Lychrel number is a natural number that cannot form a palindrome through the iterative process of repeatedly reversing its digits and adding the resulting numbers.

Example 1
Input: $n = 56
Output: 0

After 1 iteration, we found palindrome number.
56 + 65 = 121
Example 2
Input: $n = 57
Output: 0

After 2 iterations, we found palindrome number.
57 +  75 = 132
132 + 231 = 363
Example 3
Input: $n = 59
Output: 0

After 3 iterations, we found palindrome number.
59 +  95 =  154
154 + 451 =  605
605 + 506 = 1111

Here is the important part of my Raku solution:

sub lychrel(Int $n) {

    my $i = $n;

Strictly speaking this should be an infinite loop but the spec says we can stop after 500 iterations. for 1 .. 500 {

We reverse the number by splitting it into a list of individual digits with .comb(), reversing that list and then joining it back up again. Too late, I've just remembered that Raku has .flip() which would have reversed the number without all this extra work.

        my $r = $i.comb.reverse.join;

This is added to I to form the number to be checked for palindromeness (palindromosity?)

        $i =  $i + $r;

Again I could have used .flip() here. If the number is a palindrome (i.e. it is the same as its' reverse,) we are done and 0 is returned.

        if $i == $i.comb.reverse.join {
            return 0;
        }

The spec says we can stop if the number is greater than 10,000,000. In this case we return 1.

        if $i > 10_000_000 {
            return 1;
        }
    }

If we have done more than 500 iterations without finding a palindrom we also return 1. These two conditions make our code a lot easier but we get a lot of false positives as a result. This code detected 52 Lychrel numbers but most (perhaps all of these?) could have ended up as palindromes if $i was allowed to grow big enough or enough iterations were performed.

    return 1;
}

(Full code on Github.)

This is the Perl version. There's no .flip() in Perl so split, reverse and join was the right thing to do here.

sub lychrel {
    my ($n) = @_;

    my $i = $n;
    for (1 .. 500) {
        my $r = join q{}, (reverse split //, $i);
        $i =  $i + $r;

        if ($i == join q{}, (reverse split //, $i)) {
            return 0;
        }

        if ($i > 10_000_000) {
            return 1;
        }
    }

    return 1;
}

(Full code on Github.)