60 Days of Euler in F# - Problem 35

The Problem

The number, 197, is called a circular prime because all rotations of the digits: 197, 971, and 719, are themselves prime.

There are thirteen such primes below 100: 2, 3, 5, 7, 11, 13, 17, 31, 37, 71, 73, 79, and 97.

How many circular primes are there below one million?

The Solution

We'll start with some helper functions that we've used over and over again in this series. We need to turn a string into a sequence of digits, and generate a sequence of primes below 1 million.

let digitize n =
    let str = sprintf "%i" n
    str |> Seq.map (fun x -> int(x) - int('0'))

let intsqrt i = int(sqrt(float i))

let isPrime i =
    if i <= 1 then false
    elif i = 2 then true
    elif (i &&& 1) = 0 then false
    else
        let sr = intsqrt i
        seq { 3..2..sr } |> Seq.forall (fun f -> i%f<>0)

let rec nextPrime x =
    match x with
    | x when isPrime x -> x
    | x -> nextPrime (x + 1)

let getPrimesBelow max =
    let primeGenerator candidate =
        if candidate > max then
            None
        elif candidate = 2 then
            Some(2,3)
        else
            let next = nextPrime candidate
            if next >= max then
                None
            else
                Some(next,next+2)

    Seq.unfold primeGenerator 2

We've covered all this ground before. In Problem 3, for example. So, I won't go into the details of these functions.

Next, we need to figure out how to generate all the rotations of a number.

let rotate step l =
    let l1,l2 = l |> List.splitAt step
    List.append l2 l1

let toNumber (l : int list) =
    l
    |> Seq.rev
    |> Seq.mapi (fun place d -> d * (pown 10 place))
    |> Seq.sum

let rotationsOf n = 
    let digits = digitize n |> List.ofSeq
    [for i in 1..((List.length digits) - 1) -> digits |> rotate i] |> List.map toNumber

The rotate function takes as input an integer step and a list of digits. It uses List.splitAt to break the list into two chunks, and then uses List.append to generate a new list with the second chunk at the front. So, using the problem's example of 197, we can get the other 2 rotations as follows:

rotate 1 [1; 9; 7] = [9; 7; 1]
rotate 2 [1; 9; 7] = [7; 1; 9]

The toNumber function takes a list of digits and turns them back into a number. It does this by reversing the order of the list, mapping each element in the list to d * 10place, and them summing the result. Some experimenting I did later shows that it is just as fast, if not faster, to convert the digits to a string and then use the built-in CLR functions for converting string to int, but this way is more fun so I left it as-is.

Now, we can figure out if a given number is a circular prime.

let isCircularPrime n =
    rotationsOf n
    |> List.forall isPrime

isCircularPrime tests all rotations of n to see if they are prime and returns true if they are. We don't need to test n itself because we already know it's prime. Testing primes is computationally expensive, so eliminating that one redundant check reduces program execution time by almost 30%.

And now we can get the answer:

#time
getPrimesBelow 1000000
|> Seq.filter isCircularPrime
|> Seq.length
#time

We generate a sequence of all primes below one million, filter out the ones that aren't circular, and return the length of the resulting sequence, which is the answer.

Other Posts in This Series