Skip to content

Latest commit

 

History

History
66 lines (40 loc) · 3.85 KB

day2.md

File metadata and controls

66 lines (40 loc) · 3.85 KB

Day 2 - primal

Relevancy: 1.9 stable

When I start learning a new programming language, I like to code at least several solutions to Project Euler problems. These are very math-oriented and may not be the best introduction to general purpose programming, but it's a start. Anyway, it's just fun to solve them! (...and way more fun to solve them in a fast way and not by brute force.)

A lot of Project Euler problems involve prime numbers in some way. These include finding nth prime, efficient factorization or checking whether some curious number is prime or not. You could of course write these mathematical procedures yourself, which is also an educational activity. But I'm lazy. I set out to find some ready-made code and stumbled upon the primal library by Huon Wilson. Incidentally this was the first external dependency I ever used in a Rust program, long before crates.io (back when it was called slow_primes).

So let's see what's in there, shall we?

Prime sieve

The first thing to do is to create a sieve (see Wikipedia on Sieve of Eratosthenes for a detailed explanation of the algorithm). We need to set an upper bound on the sieve. There's a clever way to estimate that bound (see the docs for estimate_nth_prime) but for simplicity I'll hardcode it now to 10000.

Let's actually check some numbers for primality:

include:1-4 include:11-16

How about finding 1000th prime number?

include:18-22

The primes_from() method returns an iterator over all prime numbers generated by this sieve (2, 3, 5, 7...). Iterators in Rust have a lot of useful methods; the nth() method skips over n initial iterations, returning the nth element (or None if we exhausted the iterator). The argument is zero-based, so to find 1000th prime we need to pass 999 to nth().

Factorization

Factorization is a way to decompose a number into its divisors. For example, 2610 = 2 * 3 * 3 * 5 * 29. Here's how we can find it out with primal API:

include:23-23

When we run this, we'll get:

$ cargo run
Ok([(2, 1), (3, 2), (5, 1), (29, 1)])

What is this? Let's have a look at the result type of factor():

type Factors = Vec<(usize, usize)>;
fn factor(&self, n: usize) -> Result<Factors, (usize, Factors)>

Looks a bit complicated, but remember the Result type. The Ok variant wraps a vector of pairs of numbers. Each pair contains a prime factor and its exponent (how many times it appears in the factorization). In case of an error we'll get a pair (leftover value, partial factorization).

We can use factorization to find the total number of divisors (including compound ones). This is very important in number theory (although for reasons that are outside the scope of this blog).

Consider the following function:

include:5-9

The trick is to multiply all prime factor exponents, incremented before multiplication. See the explanation at Maths Challenge for the curious. So when we call the function on our 2610 example, we'll get Some(24) as a result.

include:24-24

Further reading