Project Euler in F# – Problem 5 (alternative solution)
There was some buzz concerning my last post on Project Euler Problem 5, since the solution presented implemented a naive brute-force algorithm and its performance was very poor.
In my mind it was only meant to be an introductory article devoted to presenting a first solution to a common problem, the least common multiple (LCM) of a set of numbers, and I planned to write a second post (the one you are reading now) to show how a "smart" algorithm could lead to outstanding performance.
Let's read the text of the exercise another time:
2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder.
What is the smallest number that is evenly divisible by all of the numbers from 1 to 20?
As we already explained, the exercise just asks us to find the least common multiple of all numbers from 1 to 20, and we can compute it by using the greatest common divisor of these numbers.
In fact, we have that:
lcm (a,b) = a * b / gcd (a,b)
In order to compute the LCM of the numbers from 1 to 20, we start computing the LCM of the first two numbers (1 and 2) and then we go on computing the LCM of the result and the third element of the sequence. We go on this way until all elements have been included in the computation.
The application of the same function to all elements of a sequence while keeping an "accumulator" is called folding, and it is a common practice in functional programming.
Let's look at the code:
#light open Microsoft.FSharp.Math.BigInt // Greater Common Divisor let rec gcd a b = match b with | b when b = 0I -> a | b -> gcd b (a % b) // Least Common Multiple let lcm a b = a * b / (gcd a b) let answer = Seq.fold1 lcm [1I .. 20I]
At line 11 we defined the least common multiple as explained above, while at line 5 we have the implementation of the Euclidean algorithm for the Greatest Common Divisor.
Then we simply fold the lcm function on the sequence [1 .. 20] and we get the output.
Do you remember how much time our "optimized" algorithm in the last article takes to perform the same calculation? 25 seconds.
The solution presented here only needs 15 milliseconds!
Project Euler in F# – Problem 5
The exercise we are going to face today is ranked as one of the easiest of Project Euler's, so I don't think you will have any problem in solving it.
However, it allows me to discuss a little bit on algorithm optimization and how to refine a working solution to obtain a better one.
The text of the exercise says:
2520 is the smallest number that can be divided by each of the numbers from 1 to 10 without any remainder.
What is the smallest number that is evenly divisible by all of the numbers from 1 to 20?
Some programming languages (for instance Haskell) provide a native function for evaluating the least common multiple (LCM) of two numbers, i.e. the smallest positive integer that is multiple of both numbers.
In F# we don't have this function, hence we have to write our own code to perform this calculation, and we need it to be able to find the LCM of a set of numbers and not only two.
Let's see the implementation of my algorithm:
#light
open System
let nums = 1 |> Seq.unfold (fun i -> Some (i, i + 1))
let divisibleByList list n =
Seq.for_all (fun x -> n % x = 0) list
let answer = nums |> Seq.first (fun i -> if divisibleByList [1 .. 20] i then Some (i) else None)
My solution will simply take the integers starting from 1 and check if they are evenly divisible by all numbers from 1 to 20.
We don't know when to stop, so at line 4 we have to create an infinite list of integers.
To do so, we use the unfold function, which allows us to create a lazy list by passing another function to be repeatedly evaluated to provide the elements of the list.
In our case we start from 1 and for each iteration we add one to the previous value, without an upper limit.
At line 6 we have the definition of the divisibleByList function, which takes a list of numbers and an integer n and checks if n is evenly divisible by all the elements of the given list.
This is done by using Seq.for_all, which tests if all the elements of the sequence satisfy the given predicate.
We have now everything we need to find our solution.
Let's just feed the divisibleByList function with the elements of the infinite list of integers and stop when we find the first that satisfy our conditions, thanks to Seq.first.
The solution is complete, but it is not very fast, since it takes about 8 minutes to run in my computer.
We can improve this time by using some very simple math.
First of all, we are sure that the number we are looking for will be a multiple of 2, so we can skip all odd numbers, by changing line 4 with:
let nums = 2 |> Seq.unfold (fun i -> Some (i, i + 2))
With this simple change the execution time drops to 4 minutes, as the amount of integers to check is is actually halved.
I want to go a little further and instead of checking all even numbers we can just test the multiples of 20.
To do so, we change again line 4 with this new one:
let nums = 20 |> Seq.unfold (fun i -> Some (i, i + 20))
Now I can be proud of myself as the execution time is reduced to 25 seconds, twenty times less than the original solution!
Obviously this is not the only solution and there are smarter approaches, maybe we can see them in a future post.


