Lychrel Longing
with Raku and Perl

by Arne Sommer

Lychrel Longing with Raku and Perl

[153] Published 7. November 2021.

This is my response to the Perl Weekly Challenge #137.

Challenge #137.1: Long Year

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

[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
File: long-year
#! /usr/bin/env raku

unit sub MAIN (Int :$lower = 1900, Int :$upper = 2100);

my @long-years;

for $lower .. $upper -> $year
{
  @long-years.push: $year if Date.new("$year-12-31").week-number == 53; # [1]
}

say @long-years.join(", ");

[1] Add the year to the list if it has 53 weeks. The Date class does the heavy lifting for us.

See docs.raku.org/type/Date for information about the Date class.

Running it:

$ ./long-year
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

That's ok-ish. Except for the missing newlines after every five entries in the challenge. Let us remedy that:

File: long-year-tabulated
#! /usr/bin/env raku

unit sub MAIN (Int :$lower = 1900, Int :$upper = 2100, :t(:$tabulated));

my @long-years;

for $lower .. $upper -> $year
{
  @long-years.push: $year if Date.new("$year-12-31").week-number == 53;
}

my $i = 1;

say $tabulated
  ?? @long-years.map({ ($i++ %% 5) ?? "$_,\n" !! "$_, " }).join
  !! @long-years.join(", ");

Running it:

$ ./long-year-tabulated -t
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, 

That is better. Except the trailing comma on the last line.

We can chop it off:

File: long-year-tabulated2
#! /usr/bin/env raku

unit sub MAIN (Int :$lower = 1900, Int :$upper = 2100, :t(:$tabulated));

my @long-years;

for $lower .. $upper -> $year
{
  @long-years.push: $year if Date.new("$year-12-31").week-number == 53;
}

my $i = 1;

say $tabulated
  ?? @long-years.map({ ($i++ %% 5) ?? "$_,\n" !! "$_, " }).join.chop(2)  # [1]
  !! @long-years.join(", ");

[1] The very last character is a space. The offending comma is the last but one. Thus we chop off two characters.

See docs.raku.org/routine/chop for information about the chop function.

A one-liner version:

File: long-year-oneliner
#! /usr/bin/env raku

say (1900..2100).grep({ Date.new("$_-12-31").week-number == 53 }).join(", ");

The output is also a one-liner, in a bout of poetic justice.

A Perl Version

This is straight forward translation of the first Raku version.

File: long-year-perl
#! /usr/bin/env perl

use strict;
use warnings;
use feature 'say';
use DateTime;      # [1]

my $lower = 1900;
my $upper = 2100;

my @long_years;

for my $year ($lower .. $upper)
{
  push(@long_years, $year)
    if DateTime->new(year => $year, month => 12, day => 31)->week_number() == 53;
}

say join(", ", @long_years);

[1] There are quite a lot of time and/or date handling modules. I have chosen «DateTime».

Running it gives the same result as the Raku version:

$ ./long-year-perl
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

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

File: lychrel-number
#! /usr/bin/env raku

unit sub MAIN (Int $n where 10 <= $n <= 1000);             # [1]

say + is-lychrel($n);                                      # [2]

sub is-lychrel ($current is copy)                          # [3]
{
  my $i = 0;                                               # [4]
  
  loop
  {
    $current = $current + $current.flip;                   # [5]

    return False if $current == $current.flip;             # [6]

    return True if $i++ == 500 || $current >= 10_000_000;  # [7]
  } 
}

[1] Ensure that we get an integer withing the specified limits.

[2] Coerce the result to 0 or 1, as I have chosen to let the procedure return False or True.

[3] Note the use of is copy so that we can change the variable in the procedure.

[4] Number of iterations.

[5] Add the reversed version of the value, which we get with flip.

See docs.raku.org/routine/flip for more information about flip.

[6] Do we have a palindrome? If so, it is not a Lychrel number.

[7] Give in eventually, and assume that it is a Lychrel number.

Running it:

$ ./lychrel-number 56
0

$ ./lychrel-number 57
0

$ ./lychrel-number 59
0

That was not excactly exciting.

Let us have a go at the Lychrel numbers, as a sequence:

File: lychrel-sequence
#! /usr/bin/env raku

unit sub MAIN (Int $n where $n > 0);

my $lychrel := (10 .. *).grep({ is-lychrel($_) });  # [1]

say $lychrel[^$n].join(", ");

sub is-lychrel ($current is copy)
{
  my $i = 0;
  
  loop
  {
    $current = $current + $current.flip;

    return False if $current == $current.flip;

    return True if $i++ == 500 || $current >= 10_000_000;  # [3]
  } 
}

[1] The sequence.

[2] Print (after computing) the requested number of values.

Running it:

$ ./lychrel-sequence 10
89, 98, 167, 177, 187, 196, 266, 276, 286, 295

$ ./lychrel-sequence 100
89, 98, 167, 177, 187, 196, 266, 276, 286, 295, 365, 375, 385, 394, 464, \
474, 484, 493, 563, 573, 583, 592, 662, 672, 682, 689, 691, 739, 761, 771, \
781, 788, 790, 829, 838, 849, 860, 869, 870, 879, 880, 887, 899, 928, 937, \
948, 968, 978, 986, 989, 998, 999, 1297, 1387, 1397, 1477, 1487, 1495, \
1496, 1497, 1567, 1577, 1585, 1586, 1587, 1657, 1667, 1675, 1676, 1677, \
1747, 1757, 1765, 1766, 1767, 1792, 1797, 1798, 1837, 1847, 1855, 1856, \
1857, 1882, 1887, 1888, 1894, 1897, 1927, 1937, 1945, 1946, 1947, 1972, \
1977, 1978, 1984, 1987, 1991, 1995

Note that the termination in [3] will give us false positives, as 89 isn't a Lychrel number (according to the wikipedia article). But we have done as requested by the challenge.

We can extend the original program, with a «sort of like the sequence» mode. Use the «-u» sommand line option to get all the values upto the specified value. I have added verbose mode at the same time.

File: lychrel-number-upto
#! /usr/bin/env raku

unit sub MAIN (Int $n where 10 <= $n <= 1000, :u(:$upto), :v(:$verbose));

$upto
  ?? (10 .. $n).map({ say "$_ -> { + is-lychrel($_) }" })
  !! say + is-lychrel($n);

sub is-lychrel ($current is copy)
{
  my $i = 0;
  
  loop
  {
    my $flipped =  $current.flip;
    say ": $current + $flipped = { $current + $flipped }" if $verbose;
    $current = $current + $current.flip;

    return False if $current == $current.flip;

    return True if $i++ == 500 || $current >= 10_000_000;
  } 
}

Running it (with abridged output):

$ ./lychrel-number-upto -u 100
10 -> 0
11 -> 0
12 -> 0
13 -> 0
…
97 -> 0
98 -> 1
99 -> 0
100 -> 0

With verbose mode, on a single value:

$ ./lychrel-number-upto -v 89
: 89 + 98 = 187
: 187 + 781 = 968
: 968 + 869 = 1837
: 1837 + 7381 = 9218
: 9218 + 8129 = 17347
: 17347 + 74371 = 91718
: 91718 + 81719 = 173437
: 173437 + 734371 = 907808
: 907808 + 808709 = 1716517
: 1716517 + 7156171 = 8872688
: 8872688 + 8862788 = 17735476
1

Perl

This is a straight forward translation of the Raku version.

File: lychrel-number-perl
#! /usr/bin/env perl

use strict;
use warnings;
use feature 'say';
use feature 'signatures';

no warnings qw(experimental::signatures);

my $n = $ARGV[0] // "";

die "Please specify an integer in the range 10 .. 1000"
  unless $n =~ /^[1-9]\d*$/;

die "Please specify an integer in the range 10 .. 1000"
  if $n < 10 || $n > 1000;

say is_lychrel($n);

sub is_lychrel ($current)
{
  my $i = 0;
  
  while (1)
  {
    $current = $current + reverse $current;

    return 0 if $current == reverse $current;

    return 1 if $i++ == 500 || $current >= 10_000_000;
  } 
}

Running it gives the same result as the Raku version:

$ ./lychrel-number-perl 56
0

$ ./lychrel-number-perl 57
0

$ ./lychrel-number-perl 59
0

And that's it.