@Euler[1]

Sorry folks, I know it's been a while. I've been busy installing Gentoo and settling into college. Anyway, let's look at some more Project Euler problems.

Problem 3:
What is the largest prime factor of the number 600,851,475,143?

The easiest (and therefore, worst) way to go about this would be to systematically test each number between 1 and 600,851,475,143 to see if it was a divisor, then check each divisor to see if it was prime, then return the largest of those. But aha! We don't need to check all the numbers, just the ones that are less than the square root of 600,851,475,143! So we really only need to test 775,147 numbers. We also know that primes can't be even, so we can start our loop index at 3 and increment by 2 each time instead of 1.

sub problem3 {
    for ($i = 3; $i < 775_147; $i += 2) {
        if (600_851_475_143 % $i == 0) and (# $i is prime) {
            $max = $i if ($i > $max);
        }
    }
    return $max;
}

Note that Perl lets you to add underscores anywhere you want in a number. Many people use them as replacements for commas.
Unfortunately, as you might have noticed, we don't really have a good way of checking to see if $i is prime. We could check all the numbers less than $i to see if they were divisors, but that would just add to the woeful inefficiency of this function. Instead, let's skip ahead to the good solution.

The good solution, it turns out, doesn't use a drastically different approach. There's only one crucial change: each time we find a divisor, we divide by it. Not only does this speed up the process by reducing the numbers we must check, it also guarantees that we'll only be finding prime factors, and that the last divisor we find will be the largest prime factor.

sub problem3 {
    $a = 600_851_475_143; $b = 3;
    while ($b != $a) {
        if ($a % $b == 0) {
            $a /= $b;
        else {
            $b += 2;
        }
    }
    return $a;
}

Of course, this can be greatly shortened by using a conditional and reverse loop structure:

sub problem3 {
    $a = 600_851_475_143; $b = 3;
    ($a % $b == 0) ? ($a /= $b) : ($b += 2) while ($b != $a);
    return $a;
}

On my i5 Gentoo machine, this code returned the correct answer in 0.002s. Much faster than the other method.

Next problem:

Problem 4:
A palindromic number reads the same both ways. The largest palindrome made from the product of two 2-digit numbers is 9009 = 91 × 99.
Find the largest palindrome made from the product of two 3-digit numbers.

This problem is harder than it looks, and you'll see why in a bit. The first thing we should do is find a way to check if a number is palindromic. Initially, I tried converting the number to an array and comparing each element to its mirror. But it turns out there's a much easier way: reverse operates just fine on a number, so it can be used to quickly find palindromes. Our first attempt might look something like this:

sub problem4 {
    for ($a = 999; $a > 100; $a--) {
        for ($b = 999; $b > 100; $b--) {
            return $a * $b if ($a * $b == reverse $a * $b);
        }
    }
}

Which will return 580085. However, Project Euler will tell you that this is the wrong answer! How could that be?
Well, what are the 3-digit numbers factors of 580005? Adding some print statements to the loop reveals that they are 995 and 583. 583 is suspiciously small, so that should be an indication of what went wrong. The problem is that we are decrementing $b much faster than $a. So it turns out that there is a palindromic number whose divisors fall between 995 and 583, producing a greater result overall. To find this number, we have to calculate all the palindromic numbers to find biggest one. This is easily accomplished in Perl:

sub problem4 {
    for $a (reverse 100..999) {
        for $b (reverse 100..$a) {
            if ($a * $b == reverse $a * $b) {
                $max = $a * $b if ($a * $b > $max);
            }
        }
    }
    return $max;
} 

Also note that the for loops have been cleaned up -- although the ..? operator can't be used to decrement, adding a reverse will produce the same effect. The right bound of the second loop has also been changed, since it's unnecessary to check numbers higher than $a.
This new approach returns the correct answer, though it takes more time than I'd like. We can make it run faster by upping the lower bound from 100 to 900, but that seems like cheating to me; it doesn't respect the spirit of the problem.

Leave a Reply

Theme: Esquire by Matthew Buchanan.