60 Days of Euler in F# - Problem 47

The Problem

The first two consecutive numbers to have two distinct prime factors are:

  • 14 = 2 × 7
  • 15 = 3 × 5

The first three consecutive numbers to have three distinct prime factors are:

  • 644 = 2² × 7 × 23
  • 645 = 3 × 5 × 43
  • 646 = 2 × 17 × 19.

Find the first four consecutive integers to have four distinct prime factors. What is the first of these numbers?

The Solution

Once again, we need primes. Lots of them. So we'll start by stealing the getSomePrimes function from yesterday.

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 i =
    match i with
    | i when isPrime i -> i
    | i -> nextPrime (i + 1)

let getSomePrimes num =
    let primeGenerator (candidate,count) =
        if count > num then
            None
        elif candidate = 2 then
            Some(2,(3,count+1))
        else
            let next = nextPrime candidate
            Some(next,(next+2,count+1))

    Seq.unfold primeGenerator (2,1)

let factors = getSomePrimes 1000 |> List.ofSeq

So how many primes do we need? As with yesterday's problem, I don't know. I'm going to pick 100 because "it sounds good" and adjust later if I need to.

Why 100? We're looking for 4 prime factors. If we generate 1000 primes and take the last 4, we get:

  • 7883
  • 7901
  • 7907
  • 7919

If you multiply those 4 numbers together you get 3,899,919,746,694,739. That's way out of 32-bit integer territory. Are we willing to bet that the answer is less than that? Yes, we are.

Now we'll write a function to get the prime factors of a number.

let f n =
    let rec getFactors (acc : Set<int>) (factors : int list) =
       match factors with
       | [] -> acc
       | x::xs -> 
            if n % (x*x) = 0 then
                getFactors (Set.add (x*x) acc) xs
            elif n % x = 0 then
                getFactors (Set.add x acc) xs
            else
                getFactors acc xs

    getFactors Set.empty<int> factors

The function f takes a number n and returns its prime factors.

The recursive internal getFactors function simply starts with a list of factors and tests them all to see if they are factors of n. The problem definition clearly shows that squares of primes are allowed to be counted as a "distinct factor" so we account for that case.

Now we need a function to look at a number and see if it has the specified number of distinct prime factors. In fact, we need to know if several consecutive numbers have that property, so we'll take a list of numbers as input instead of just a single number. We need to verify that:

  • Each number has 4 distinct factors
  • The set of 4 numbers has 16 distinct factors

The problem definition doesn't come right out and say that "distinct prime factors" means "distinct across the 4 consecutive numbers", but you don't get the right answer without accounting for that.

[<Literal>]
let factorCount = 4

[<Literal>]
let requiredDistinct = 16

let haveDistinctPrimeFactors numbers =
    let numbersFactors = 
        numbers
        |> Seq.map f
        |> List.ofSeq

    let distinctCount =
        numbersFactors
        |> List.reduce (fun acc elem -> acc + elem)
        |> Set.count

    (numbersFactors |> List.forall (fun x -> (Set.count x) = factorCount)) &&
    (distinctCount = requiredDistinct)

The haveDistinctPrimeFactors function starts by computing a couple of values.

numbersFactors becomes a list of factors for each of the input numbers.

distinctCount is the count of the distinct factors across all 4 input numbers. The factors are of type Set so when we add them together, we wind up with the union of all 4 sets. Thus, the count of that union is the number of distinct factors.

If the count of distinct factors for each input is 4 and the count of distinct factors is requiredDistinct then the function returns true; otherwise, it returns false.

Now we can solve the problem:

#time
Seq.unfold (fun state -> Some(state, state+1)) 647
|> Seq.filter (fun x -> haveDistinctPrimeFactors [x..x+3])
|> Seq.head
#time

We start by using Seq.unfold to generate an infinite sequence of numbers starting at 647. The problem definition says that 644..646 is the first set of consecutive numbers of have 3 prime factors, so we'll start looking right after that.

Next, we filter out any values that don't have the required distinct prime factors and return the head of the resulting sequence. That is the answer.

Other Posts in This Series