Peter
Peter Campbell Smith

Palindromic primes
and moody numbers

Weekly challenge 164 — 9 May 2022

Week 164 - 9 May 2022

Task 2

Task — 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 ends in 1 are happy numbers, while those numbers that do not end in 1 are unhappy numbers.

Examples


Example 1:
19 is a 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

Analysis

My algorithm is as follows:

  1. Take a positive integer p.
  2. Set q = p
  3. Compute s, the sum of the squares of the digits of q.
  4. If s is 1 then p is happy. Stop.
  5. If s is a previously seen value of s then p is sad. Stop.
  6. Set q = s and go back to step 3.

This isn't too hard to code, and the required 8 happy numbers are easily within the ambit of Perl integers.

The harder bit, though, is generating the output in the format specified by Mohammad. I got all the '=>' lining up as required, but lost the will to live before lining up the squares under the corresponding digits.

Script


#!/usr/bin/perl

# Peter Campbell Smith - 2022-05-09
# PWC 164 task 2

use v5.28;
use strict;
use warnings;
use utf8;

my ($test, $so_far, $i, @digits, $d, @seen, @sad, $result1, $result2, $indent, $found);

$found = 0;
@sad = ();

# loop in the hope that we find 8 happy numbers before 1000
TEST: for $test (1 .. 1000) {
    last if $found == 8;  # success!
    
    $indent = 0;
    $so_far = $test;   # this will be our running sum of squares
    @seen = ();        # these are sums already seen for this $test (indicating looping)
    $result1 = '';
    
    # now iterate over the adding the digits squares
    for $i (1 .. 10) {
        
        # split $so_far into digits
        @digits = split('', $so_far);
        
        # this is all stuff to format the output as per Mohammad's example
        $result1 .= qq[$so_far => ];
        $indent += length($so_far) + ($i == 1 ? 1 : 4);
        $result2 = (' ' x $indent) . qq[=> ];
        
        # now sum the square of the digits
        $so_far = 0;
        for $d (@digits) {
            $so_far += $d**2;
            
            # more formatting stuff
            $result1 .= qq[$d^2 + ];
            $result2 .= $d**2 . qq[ + ];
        }

        # more formatting stuff
        $result1 = substr($result1, 0, -2) . qq[\n] . 
                substr($result2, 0, -2) . qq[\n] . (' ' x $indent) . '=> ';
        
        # if $so_far is 1 we are happy!
        if ($so_far == 1) {
            say qq[\n${result1}1];
            $found ++;
            next TEST;
            
        # if $so_far has been seen already for this $test or is already known to be $sad
        # then we're in a loop and $test is sad
        } elsif ($seen[$so_far] or $sad[$so_far]) {
            $sad[$so_far] = 1;
            next TEST;
        }

        # if neither of the above are true then we note that we've seen $seen and keep going
        $seen[$so_far] = 1;
    }
}

Output


1 => 1^2 
  => 1 
  => 1

7 => 7^2 
  => 49 
  => 49 => 4^2 + 9^2 
        => 16 + 81 
        => 97 => 9^2 + 7^2 
              => 81 + 49 
              => 130 => 1^2 + 3^2 + 0^2 
                     => 1 + 9 + 0 
                     => 10 => 1^2 + 0^2 
                           => 1 + 0 
                           => 1

10 => 1^2 + 0^2 
   => 1 + 0 
   => 1

13 => 1^2 + 3^2 
   => 1 + 9 
   => 10 => 1^2 + 0^2 
         => 1 + 0 
         => 1

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

23 => 2^2 + 3^2 
   => 4 + 9 
   => 13 => 1^2 + 3^2 
         => 1 + 9 
         => 10 => 1^2 + 0^2 
               => 1 + 0 
               => 1

28 => 2^2 + 8^2 
   => 4 + 64 
   => 68 => 6^2 + 8^2 
         => 36 + 64 
         => 100 => 1^2 + 0^2 + 0^2 
                => 1 + 0 + 0 
                => 1

31 => 3^2 + 1^2 
   => 9 + 1 
   => 10 => 1^2 + 0^2 
         => 1 + 0 
         => 1