Project Euler: Problem 12

Published:

The sequence of triangle numbers is generated by adding the natural numbers. So the 7th triangle number would be 1 + 2 + 3 + 4 + 5 + 6 + 7 = 28. The first ten terms would be:

1, 3, 6, 10, 15, 21, 28, 36, 45, 55, ...

Let us list the factors of the first seven triangle numbers:

 1: 1
 3: 1, 3
 6: 1, 2, 3, 6
10: 1, 2, 5, 10
15: 1, 3, 5, 15
21: 1, 3, 7, 21
28: 1, 2, 4, 7, 14, 28

We can see that 28 is the first triangle number to have over five divisors.

What is the value of the first triangle number to have over five hundred divisors?

Project Euler Problem 12

At first it seemed like a piece of cake, however it wasn't as easy as I thought it was...

Solution

Well, what we first need here, regardless of how you do it (pretty much), is a source of triangle numbers.

public class TriangleSequence : IEnumerable<ulong>
{
    public IEnumerator<ulong> GetEnumerator()
    {
        ulong sum = 0;
        for (ulong n = 1;; n++)
        {
            sum += n;
            yield return sum;
        }
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Pretty simple and straight forward. We can then move on to the next issue.

Take one

Like mentioned, I thought this one would be a piece of cake. I mean, having the sequence of triangle numbers in place, how hard could it be? My first solution, probably the most obvious one, is to go through each triangle number, find the factors of it, see if there are more than 500 of them, and move to the next if it isn't.

I created a basic factorization method using the rather naive trial-division method and wrote a simple Linq statement to get the answer.

public static IEnumerable<ulong> GetDivisors(ulong number)
{
    if (number == 0)
        yield break;

    var limit = number / 2;
    for (ulong i = 1; i <= limit; i++)
        if (number % i == 0)
            yield return i;

    yield return number;
}


var answer = new TriangleSequence()
    .Select(x => new
        {
            Number = x,
            DivisorCount = Factorization.GetDivisors(x).Count(),
        })
    .First(x => x.DivisorCount > 500)
    .Number;

And then I ran it. A few seconds went by and no answer. Minutes went by and still no answer. Considering the fact that most of my other problems was solved in less than a few seconds, this was just taking too long. A smarter solution is needed.

Take two

The way to speed this up is "of course" to find a faster factorization algorithm, so I went hunting for a more effective one. But, while I was on that hunt, I realized that I was perhaps on the wrong path here. I mean, what do I even need those factors for? What I really need is just the count of the factors. I wasn't sure if such a method existed though, but while roaming around on StackOverflow (like I tend to do sometimes) I stumbled over some Python code. Later I also found some formulas. So, time for some math.

Any integer can be expressed as

N=p1a1p2a2p3a3N = p_1^{a_1} \cdot p_2^{a_2} \cdot p_3^{a_3} \cdot \ldots

where pnp_n is a distinct prime number and ana_n is its exponent. The count of divisors D(N)D(N) can then be found by the formula

D(N)=(a1+1)(a2+1)(a3+1)D(N) = (a_1 + 1) \cdot (a_2 + 1) \cdot (a_3 + 1) \cdot \ldots

For example:

N=36=3222=94N = 36 = 3^2 \cdot 2^2 = 9 \cdot 4
D(32)=(2+1)(2+1)=9D(32) = (2 + 1) \cdot (2 + 1) = 9

With this knowledge and an example implementation in python, I was able to throw together the following code.

public class ProbablePrimeSequence : IEnumerable<ulong>
{
    public IEnumerator<ulong> GetEnumerator()
    {
        yield return 2;
        yield return 3;
        ulong i = 5;
        while (true)
        {
            yield return i;
            if (i % 6 == 1)
                i += 2;
            i += 2;
        }
    }
    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

public static ulong GetCountOfDivisors(ulong number)
{
    if (number == 0)
        return 0;

    var divisors = 1UL;

    foreach (var prime in new ProbablePrimeSequence())
    {
        var exponent = 0UL;
        while (number % prime == 0)
        {
            exponent += 1;
            number /= prime;
        }

        if (exponent > 0)
            divisors *= exponent + 1;

        if (number == 1)
            break;
    }
    return divisors;
}

Now, why this works by just using probable primes and not pure primes, I am not sure to be honest. But it sure is a lot faster. My tests doesn't fail and I get the right answer so I'll just move on 😛 (However, leave a comment if you know, cause I would like to know too 🙂 )

Anyways, now we can finally find our answer using the following straight forward statement.

var answer = new TriangleSequence()
    .First(x =>GetCountOfDivisors(x) > 500);

The running time of that lays around 320 milliseconds, which I think is pretty acceptable although not blazingly fast.

What do you think of my solution? Have I overlooked something obvious? How would you solve this one? I am curious and would like to know, so please leave a comment 😄