Perl Weekly Challenge 137.

My solutions (task 1, and task 2 ) to the The Weekly Challenge - 137.

Task 1: Long Year

Submitted by: Mohammad S Anwar
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.

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

An ordinary year has 365 days unless it is a leap year, in which case it has 366 days. Dividing by 7, we find it has 52 weeks and one (or two) additional days. So in a sense, it can’t have 53 weeks. However, it might overlap 53 weeks, i.e., part of the first week, part of the last, and 51 full intermediate weeks. However, this is the norm, so most years have 53 weeks in this sense. Trying to make sense of the matter, I found that there is an ISO standard for numbering weeks, so that all weeks start on a Monday and end on Sunday, and week 1 is that which contains the first Thursday of the year. If some year N starts in a Wednesday, the year N+1 would start on a Thursday, so that the first week of that year would start on Monday, December 29, of the year N. However, if a year N starts in a Thursday, next year N+1 would start in a Friday, so the first week of that year would not begin until Monday, January 4 and year N would be a long year. Thus, ordinary years beginning on Thursdays are long years. Similarly, leap years starting on either Wednesdays or Thrsdays are also long.

Thus, to solve the challenge I have to find out the weekday for January 1 and if the year is or not a leap year. To that end, I can follow the rule: those years whose number is a multiple of 4 are leap years, except for those that are multiple of 100, which are not, except those that are multiple of 400 which are leap years. I can use the fact that January 1, 1900 was a Monday, according to

date -d 1900-01-01

Mon Jan  1 12:00:00 AM LMT 1900

I do an extrapolation of the Gregorian calendar towards the nonexistent year 0000, which would have started on Sunday. Thus I add an offset of -1 when calculating the first weekday of each year using 0000 as a reference.

The one liner version is

perl -MText::Wrap=wrap,\$columns -Minteger -E '$columns=62;
     say wrap("", "", grep {$x=$_-1; $d=($x+$x/4-$x/100+$x/400)%7;
     $l=(!($_%400)||!($_%4)&&$_%100); $d==3||$l&&$d==2} 1900..2100)'

Results:

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

and a somewhat more readable full version is

# Perl weekly challenge 137
# Task 1: Long year
#
# See https://wlmb.github.io/2021/11/02/PWC137/#task-1-long-year
use v5.12;
use warnings;
use Text::Wrap qw(wrap $columns);
use integer;
$columns=62;
say wrap("", "", grep {is_long_year($_)} ($ARGV[0]//1900..$ARGV[1]//2100));
sub is_long_year {
    my $YY=shift;
    # Skip a day for every 'Gregorian' leap year extrapolating since 0000.
    my $first_weekday=(($YY-1)+($YY-1)/4-($YY-1)/100+($YY-1)/400)%7; # 0=Monday
    my $leap=$YY%400==0||$YY%4==0&&$YY%100!=0;
    return $first_weekday==3 || $leap&&$first_weekday==2;
}

./ch-1.pl

Results:

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

Task 2: Lychrel Number

Submitted by: Mohammad S Anwar
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.
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

A short solution may be found by iterating and checking the conditions straightforwardly.

perl -E 'say "In: $_ Out: ", ly($_) foreach(@ARGV);
 sub ly {$n=shift; $r=reverse $n; while($n<10_000_000)
  {return 0 if $n eq $r; $n+=$r; $r=reverse $n} return 1}' 56 57 59 177

Results:

In: 56 Out: 0
In: 57 Out: 0
In: 59 Out: 0
In: 177 Out: 1

Again, the full version is slightly more clear and can easily provide the reasoning for rejecting a given number.

# Perl weekly challenge 137
# Task 2: Lychrel number
#
# See https://wlmb.github.io/2021/11/02/PWC137/#task-2-lychrel-number
use v5.12;
use warnings;
my ($lower, $upper, $maxcount, $largest)=(10,1000,500,10_000_000); #bounds
foreach(@ARGV){
    my @why=lychrel($_);
    say "Input: $_\nOutput: ", @why?"0\n":"1?", @why>1?join("\n", @why):"";
    say "";
}
sub lychrel {
    my $n=shift;
    my $r=reverse $n;
    say("Failed $lower<=$_<=$upper"), exit unless $lower<=$_<=$upper;
    my $count=$maxcount;
    my @why=("as");
    while(--$count){
	return (@why) if $n eq $r;
	push @why, "$n+$r=". ($n+=$r);
	return if $n>=$largest;
	$r=reverse $n;
    }
    return;
}

In case of success I add added question mark to the output, as with more patience (more iterations, larger bounds) I might have obtained a palindrome.

Example:

./ch-2.pl 56 57 59

Results:

Input: 56
Output: 0
as
56+65=121

Input: 57
Output: 0
as
57+75=132
132+231=363

Input: 59
Output: 0
as
59+95=154
154+451=605
605+506=1111

Other examples:

./ch-2.pl 5
./ch-2.pl 1001
./ch-2.pl 89 98 167 190 196 997 999

Results:

Failed 10<=5<=1000
Failed 10<=1001<=1000
Input: 89
Output: 1?

Input: 98
Output: 1?

Input: 167
Output: 1?

Input: 190
Output: 0
as
190+091=281
281+182=463
463+364=827
827+728=1555
1555+5551=7106
7106+6017=13123
13123+32131=45254

Input: 196
Output: 1?

Input: 997
Output: 0
as
997+799=1796
1796+6971=8767
8767+7678=16445
16445+54461=70906
70906+60907=131813
131813+318131=449944

Input: 999
Output: 0
Written on November 2, 2021