Problem 7 — 10 001st Prime

solved Fri, 27 Sep 2019, 13:56 · level 0 · difficulty 1% projecteuler.net github

By listing the first six prime numbers: $2, 3, 5, 7, 11$, and $13$, we can see that the $6$th prime is $13$.

What is the $10\,001$st prime number?


Problem Analysis

The task is to compute the 10,001st prime number - an integer greater than 1 whose only positive divisors are 1 and itself. Naively, I could test each candidate integer for primality by trial division, but that scales poorly. The efficient route is to generate primes in bulk with a sieve. The most familiar is the Sieve of Eratosthenes, but here I use its odd-only cousin, the Sieve of Sundaram, which enumerates the odd primes directly by marking, for every pair i, j, the index i + j + 2ij. The surviving indices k then decode to the odd primes 2k+1. Its marking cost is O(M log M) over a range of size M - far better than per-candidate trial division.

A sieve needs to know how large M must be to contain the n-th prime. This is where the prime number theorem helps: it states that the n-th prime p(n) is asymptotically n * ln(n), where ln is the natural logarithm. For n = 10001, that estimate is about 92,000, and the true answer is 104,743 - so the raw n * ln(n) heuristic is a little tight, but in this solution it works because the Sieve of Sundaram indexes only odd numbers (each array index k stands for the much larger odd number 2k+1), so the effective covered range is roughly doubled. The key observation that makes this fast is that we never need to consider even numbers beyond 2: every other even number is composite, so we sieve only odd numbers and halve both memory and work.

Solution Approach

Sieve of Sundaram (Solution 0 - Python and C)

Both the Python and C files implement the same Sieve of Sundaram, so I describe them together and note language differences as they arise.

Step 1 - Special-case the only even prime. 2 is the sole even prime. If n == 1 I return 2 immediately; from then on I work only with odd numbers.

Step 2 - Size the sieve. I set max_expected_value = int(n * log(n)), the prime number theorem estimate, and allocate an array of that length. Because each index k represents the odd number 2k+1, the actual numeric range covered is about 2 * max_expected_value, which comfortably contains the 10,001st prime.

Step 3 - Sieve over odd numbers. I map odd number 2k+1 to index k, initialising numbers[k] = k so a non-zero value means "still a candidate." The crucial algebra behind the Sieve of Sundaram: multiplying two odd numbers gives (2i+1)(2j+1) = 2(i + j + 2ij) + 1, so their product corresponds to index i + j + 2*i*j. For each surviving index i, I set numbers[i + j + 2*i*j] = 0 for j running from i upward, marking those composites. Starting j at i rather than 1 avoids re-marking products whose smaller factor was already processed.

Step 4 - Extract the answer. The surviving (non-zero) entries are the odd primes in order. I take the (n-2)-th surviving entry: the offset of -2 accounts for index 0 decoding to the non-prime 1, and for 2 already being counted as the 1st prime. I decode it back with 2*k + 1.

Language differences. In Python the sieve is a list, and the inner loop relies on try/except IndexError to detect when the computed index runs past the end of the list, breaking out. In C, out-of-bounds writes are undefined behaviour, so I instead compute the index as a long long and explicitly compare idx > max_expected_value before writing; the C version also skips already-marked entries and frees the heap array at the end. Neither solve() parses arguments or times itself - that is the runner's job (see below).

workbench

Test cases

categoryinputanswer
dev n = 6 13
main n = 10001 104743

Results

solutioncategoryanswerverdict avg seconds cavg seconds py
p0007_s0 dev 13 correct 0.000002 (84) 0.000006 (84)
p0007_s0 main 104743 correct 0.000368 (84) 0.039159 (84)

Files

Tags & blogs

Blogs