Primarily Happy
with Raku
and Perl

by Arne Sommer

Primarily Happy with Raku and Perl

[183] Published 15. May 2022.

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

Challenge #164.1: Prime Palindrome

Write a script to find all prime numbers less than 1000, which are also palindromes in base 10. Palindromic numbers are numbers whose digits are the same in reverse. For example, 313 is a palindromic prime, but 337 is not, even though 733 (337 reversed) is also prime.

File: prime-palindrome
#! /usr/bin/env raku

unit sub MAIN ($limit = 1000);  # [1]

(1 ..^ $limit).grep( *.is-prime ).grep({ $_ eq $_.flip }).join(", ").say;
# [1a] ######  # [2] ############ # [3] ################# # [4] ### # [5]

[1] "less than 1000", or any orther limit you may care to specify. We start with all the integers from 1 up to (but not including) that limit [1a].

[2] Then we use grep to get rid of non-primes.

[3] Another grep, this time to get rid of non-palindromes (where we flip the string and get the same result).

[4] Then we join the values together as a comma separated string,

[5] and print it.

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

Running it:

$ ./prime-palindrome
2, 3, 5, 7, 11, 101, 131, 151, 181, 191, 313, 353, 373, 383, 727, 757, 787, \
  797, 919, 929

$ ./prime-palindrome 12000
2, 3, 5, 7, 11, 101, 131, 151, 181, 191, 313, 353, 373, 383, 727, 757, 787, \
  797, 919, 929, 10301, 10501, 10601, 11311, 11411

A Perl Version

This is straight forward translation of the Raku version, except that I have chosen an explicit loop and more traditional next statements to skip values (instead of grep). Chaining commands, as in Raku, is difficult due to the Perl syntax. (And the result tends to be unreadable.)

File: prime-palindrome-perl
#! /usr/bin/env perl

use strict;
use warnings;
use feature 'say';
use Math::Prime::Util 'is_prime';

my $limit = int(shift(@ARGV) || 0) || 1000;  # [1]

say $limit;

my @result;

for my $current (1 .. $limit -1)
{
  next unless is_prime($current);
  next unless $current eq reverse($current);

  push(@result, $current);
}

say join(", ", @result);

[1] The innermost || ensures that running the program without arguments does not cause a warning (when we cast an undefined value to an integer). The outernost || ensures that zero (explicit and undefined from the innermost) and non-numeric values are replaced with 1000.

Running it gives the same result as the Raku version:

$ ./prime-palindrome-perl
1000
2, 3, 5, 7, 11, 101, 131, 151, 181, 191, 313, 353, 373, 383, 727, 757, 787, \
  797, 919, 929

$ ./prime-palindrome-perl 12000
12000
2, 3, 5, 7, 11, 101, 131, 151, 181, 191, 313, 353, 373, 383, 727, 757, 787, \
  797, 919, 929, 10301, 10501, 10601, 11311, 11411

Reverse Bonus

What if the reverse number should also be a prime (instead of palindromic). The reverse number has to be different from the number itself (i.e. not palindromic).

File: reverse-prime
#! /usr/bin/env raku

unit sub MAIN ($limit = 1000);

(1 ..^ $limit).grep( *.is-prime )
  .grep({ $_ ne $_.flip && $_.flip.is-prime }).join(", ").say;

Running it:

$ ./reverse-prime
13, 17, 31, 37, 71, 73, 79, 97, 107, 113, 149, 157, 167, 179, 199, 311, \
 337, 347, 359, 389, 701, 709, 733, 739, 743, 751, 761, 769, 907, 937, 9\
 41, 953, 967, 971, 983, 991

Challenge #164.2: Happy Numbers

Write a script to find the first 8 Happy Numbers in base 10. For more information, please check out Wikipedia.

Starting with any positive integer, replace the number by the sum of the squares of its digits, and repeat the process until the number equals 1 (where it will stay), or it loops endlessly in a cycle which does not include 1.

Those numbers for which this process end in 1 are happy numbers, while those numbers that do not end in 1 are unhappy numbers.

Example:
19 is Happy Number in base 10, as shown:
19 => 1^2 + 9^2
   => 1   + 81
   => 82 => 8^2 + 2^2
         => 64  + 4
         => 68 => 6^2 + 8^2
               => 36  + 64
               => 100 => 1^2 + 0^2 + 0^2
                      => 1 + 0 + 0
                      => 1

Detecting an endless loop is difficult, so I have chosen to let the code run for a specified number of iterations, before giving in. That number is 100, which works out fine in practice. It can be overridden, if you want to. (Verbose mode verifies this, as the 8 happy numbers are detected in at most 5 steps - or 6 steps if we go for 1000 of them.)

File: happy-numbers
#! /usr/bin/env raku

unit sub MAIN (:l(:$limit) = 8, :d(:$delta) = 100, :v(:$verbose));  # [0]

my @result;

for 1 .. * -> $number                   # [1]
{
  my $n = $number;                      # [2]
  my $i = $delta;                       # [3]
  
  while $n != 1 && $i > 0               # [4]
  {
    $n = happy($n);                     # [5]
    $i--;                               # [6]
  }

  if $verbose
  {
    say $n == 1
      ?? ": $number is happy (in { 100 - $i } step(s))"
      !! ": $number is not happy";
  }
  
  @result.push($number) if $n == 1;     # [7]
  
  last if @result.elems == $limit;      # [8]
}

say @result.join(", ");                 # [9]

sub happy ($number)                     # [10]
{
  return $number.comb.map( * ** 2).sum; # [10a]
}

[1] Iterate over the numbers to check; from 1 and up. The exit strategy is decided by the number of matching numbers, set in [0] and checked in [8]

[2] As we are changing (transforming) the number, and still neeed the original one - as that is the number to report.

[3] The number of iterations before giving in. Also a copy, as we use this as a new counter for each number (in [1]). The default value is 100 (set in [0]), but you can override it if you want to.

[4] As long as we have not found the number to be happy ($n != 1) and have not exceeded the number of transformations to try ($i > 0).

[5] Do another transformation (happify the number, so to speak).

[6] Count down the number of iterations left (before we give in).

[7] Add the number if we have reached 1 (i.e. the number is happy).

[8] Stop looking for additional happy numbers, if we have fulfilled the quota.

[9] Print the result; the list of 8 (or another amount) first happy numbers.

[10] Transform the number to the next level. It may be happy, and it may not. Square each digit, and add them together to get the transformation [10a].

Running it:

$ ./happy-numbers
1, 7, 10, 13, 19, 23, 28, 31

With verbose mode:

$ ./happy-numbers -v
: 1 is happy (in 0 step(s))
: 2 is not happy
: 3 is not happy
: 4 is not happy
: 5 is not happy
: 6 is not happy
: 7 is happy (in 5 step(s))
: 8 is not happy
: 9 is not happy
: 10 is happy (in 1 step(s))
: 11 is not happy
: 12 is not happy
: 13 is happy (in 2 step(s))
: 14 is not happy
: 15 is not happy
: 16 is not happy
: 17 is not happy
: 18 is not happy
: 19 is happy (in 4 step(s))
: 20 is not happy
: 21 is not happy
: 22 is not happy
: 23 is happy (in 3 step(s))
: 24 is not happy
: 25 is not happy
: 26 is not happy
: 27 is not happy
: 28 is happy (in 3 step(s))
: 29 is not happy
: 30 is not happy
: 31 is happy (in 2 step(s))
1, 7, 10, 13, 19, 23, 28, 31

Note the low number of steps (transformations) required for the happy numbers.

Perl

This is a straight forward translation of the Raku version.

File: happy-numbers-perl
#! /usr/bin/env perl

use strict;
use warnings;
use feature 'say';
use Getopt::Long;
use List::Util qw(sum);
use feature 'signatures';

no warnings qw(experimental::signatures);

my $limit   =   8;
my $delta   = 100,
my $verbose =   0;

GetOptions("limit" => \$limit, "delta" => \$delta, "verbose" => \$verbose);

my @result;
my $number = 0; 

while (++$number)   # [1]
{
  my $n = $number;
  my $i = $delta;
  
  while ($n != 1 && $i > 0) 
  {
    $n = happy($n);
    $i--;
  }

  if ($verbose)
  {
    $n == 1
      ? say ": $number is happy (in " . ( 100 - $i ) . " step(s))"
      : say ": $number is not happy";
  }
  
  push(@result, $number) if $n == 1;
  
  last if @result == $limit;
}

say join(", ", @result);

sub happy ($number)
{
  return sum(map { $_ ** 2 } split(//, $number));
}

[1] Note the prefix incrementation, as the postfix version (which is more usual) would cause the loop to not run at all.

Running it gives the same result as the Raku version:

$ ./happy-numbers-perl
1, 7, 10, 13, 19, 23, 28, 31

And that's it.