Learning F# - Project Euler #1

I’ve recently decided to start learning F# and my initial thoughts are that to learn the syntax effectively I should stick with writing code (and tests) just for domain logic and not think about IO (connecting to APIs, DBs, etc.)

I decided that something like Project Euler would be appropriate for learning the basic F# syntax. I’ve done some of these ‘code katas’ before but it’s been a good few years.

The first Project Euler is fairly simple and doesn’t require an special mathmatical skills.

If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below 1000.

My only goal is to write a working solution in order to start learning the F# syntax.

First attempt

I started off with an empty F# project using the XUnit template. - dotnet new xunit --language f#

I them wrote my first function

let multiple5 x = false

Followed by some tests, which will fail to start with, to assert the behaviour of this function.

    [<Fact>]
    let ``Test that multiple of 5 returns true for 5`` () =
        Assert.True(multiple5 5)

    [<Fact>]
    let ``Test that multiple of 5 returns false for 21`` () =
        Assert.False(multiple5 21)

And then updated the multiple5 function to fix the tests:

let multiple5 x = x % 5 = 0

My initial hurdle was understanding the compiliation order is defined with the fsproj file. This was fairly simple however I made it more difficult as I knew I wanted to be able to test this app on the console and therefore ended up with a problem that my test file has to be later in the compilation order, so that it can reference the actual code, however I ended up with the following error:

error FS0433: A function labeled with the 'EntryPointAttribute' attribute must be the last declaration in the last file in the compilation sequence.

The solution to this is to extract the EntryPoint method out into a seperate file that references the Euler1 module. I actually think this is great as it’s forcing the domain logic to be in a seperate library from the console app code.

I continued with creating the complimenting function, multiple3:

let multiple3 x = x % 3 = 0

And then a function which checks whether the parameter is a multiple of either 3 or 5

  let multiple3or5 x =
        multiple3 x || multiple5 x

Then it’s onto iterating over a sequence of numbers and filtering out the numbers which aren’t multiples of 3 or 5.

    let naturalsWhichAreMultiples x = 
        [|1..x-1|]
        |> Array.filter(multiple3or5)

The final piece of the puzzle is the sum of the numbers returned from the above function.

    let sumOfNaturals max =
        naturalsWhichAreMultiples max
        |> Array.sum

To tie everything together and to get the answer for the project euler problem I call this last function from the app Entry Point. Here is the final Program.fs

module Program
    open Euler1
    [<EntryPoint>]
    let main args = 
        let max = args[0] |> int
        let sum = sumOfNaturals max
        printf $"Sum of natural numbers which are multiples of 3 or 5 to %i{max} is %i{sum}"
        0

And the Euler1 module.

module Euler1 
    let multiple3 x = x % 3 = 0
    
    let multiple5 x = x % 5 = 0

    let multiple3or5 x =
        multiple3 x || multiple5 x

    let naturalsWhichAreMultiples x = 
        [|1..x-1|]
        |> Array.filter(multiple3or5)

    let sumOfNaturals max =
        naturalsWhichAreMultiples max
        |> Array.sum

This is a quite length solution to what is a very simple problem, there’s certainly ways to make the code much smaller but this will come with subsequent iterations.

Second attempt

This second attempt is more of a refactor of the first, I’ve basically merged the code from the 5 functions in the above attempt into a single function. It looks a little more cryptic but I think that’s because I’m not used to reading F# code.

module Euler1
    let sumOfNaturals max =
        [1..max-1]
        |> Seq.filter (fun x -> (x % 3 = 0) || (x % 5 = 0) )
        |> Seq.reduce (+)

The final statement could also be replaced with sum

    let sumOfNaturals max =
        [1..max-1]
        |> Seq.filter (fun x -> (x % 3 = 0) || (x % 5 = 0) )
        |> Seq.sum

Comments

comments powered by Disqus