Perl Weekly Challenge 133: Smith Numbers

by Abigail

Challenge

Write a script to generate first 10 Smith Numbers in base 10.

According to Wikipedia:

In number theory, a Smith number is a composite number for which, in a given number base, the sum of its digits is equal to the sum of the digits in its prime factorization in the given number base.

Solution

This is mostly about factorizing a number; something we have done in previous challenges.

Perl

We won't be writing our own factorization function. Instead, we will use the module Math::Prime::Util which has a method factor. This method takes a non-negative integer as argument, and returns a list of factors of that number.

use Math::Prime::Util qw [factor];

We will write a subroutine to calculate the sum of the digits of a number. In fact, the subroutine will take a list of numbers, and returns the sum of all its digits.

We do the latter by concatenating all the given numbers, extracting the digits, and summing them:

sub digitsum (@n) {sum "@n" =~ /\d/ag}

Now it's just a matter of starting to count from 1, checking each number whether it's a Smith Number, and stopping once we have ten of them:

my $c = 0;
my $n = 0;
do {
    my @factors = factor ++ $n;
    $c ++, say $n if @factors > 1 && digitsum ($n) == digitsum @factors;
} until $c == $COUNT;

Note that we filter out numbers which only have a single factor, as Smith Numbers are defined to be composite numbers.

Find the full program on GitHub.

C

We start off with a function which factorizes a number. We will use the fact that the ten Smith Numbers we have to produce are all less than 1000. This means we only have to check primes up to 31 to find factors, as \(31^2 < 1000 < 37^2\). We also know that no number less than 1000 has ten or more factors, but it can have nine factors, as \(2^9 < 1000 < 2^{10}\).

The function takes two arguments: a number to be factorize, and an array to populate with factors. The array will be large enough. The function returns the number of factors.

short small_primes [] = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31};
# define SMALL_PRIMES_SIZE 11
# define MAX_FACTORS        9

size_t factorize (short n, short * factors) {
    size_t f_i = 0;   /* Index in output structure */

    for (size_t i = 0; i < SMALL_PRIMES_SIZE && n > 1; i ++) {
        short prime = small_primes [i];
        while (n % prime == 0) {
            factors [f_i ++] = prime;
            n /= prime;
        }
    }
    /*
     * Possible left over large prime
     */
    if (n > 1) {
        factors [f_i ++] = n;
    }

    return (f_i);
}

We also have a digitsum function, which takes an array of numbers, and returns the sum of all its digits. We do this by taking all the numbers from the array, and repeatedly modding and dividing the number by 10, summing the results of the mod operations:

# define BASE 10

short digitsum (size_t n, short * numbers) {
    short out = 0;
    char * tmp;
    for (size_t i = 0; i < n; i ++) {
        short number = numbers [i];
        while (number) {
            out    += number % BASE;
            number /= BASE;
        }
    }
    return (out);
}

We can now create the main program. First, we have to malloc memory for the array which gets populated with the factors:

short * factors;
if ((factors = (short *) malloc (MAX_FACTORS * sizeof (short))) == NULL) {
    perror ("Malloc failed");
    exit (1);
}

We can now try each number, check whether they are a Smith Number, and stop when we have ten of them:

# define COUNT 10

size_t c = 0;
short  n = 0;

while (c < COUNT) {
    size_t fc = factorize (++ n, factors);
    if (fc > 1 && digitsum (1, &n) == digitsum (fc, factors)) {
        printf ("%d\n", n);
        c ++;
    }
}

And don't forget to free the malloced memory!

free (factors);

Find the full program on GitHub.


Please leave any comments as a GitHub issue.