List Replication in F#

Lloyd Atkinson
"Great things are done by a series of small things brought together."
"Great things are done by a series of small things brought together."

Implementing a Hacker Rank challenge

I have an ongoing task to further my knowledge of functional programming and in particular F# - the functional language for .NET. A challenge on Hacker Rank is list replication.

The problem is as follows:

Given a list, repeat each element in the list N amount of times.

Input Format

The first line contains the integer S where S is the number of times you need to repeat the elements. The next X lines each contain an integer. These are the X elements in the array.

Output Format

Output each element of the original list S times, each on a separate line. You have to return the list/vector/array of S * X integers. The relative positions of the values should be the same as the original list provided in the input.

The following example input and output is provided:

3
1
2
3
4
1
1
1
2
2
2
3
3
3
4
4
4

This is the implementation I wrote. One of my aims was to write an implementation that was not the most concise code possible so that it is more approachable. Unlike many other implementations I saw this one has validation. A number is required as the first line and the program terminates if it is not. It also simply ignores any non-numeric input later on.

I feel there is a lot of room for improvement but I need a deeper understanding of both the syntax and idiomatic F# in order to improve it. For example, I would ideally use Seq.choose and/or Option.bind to refactor away the nested pattern match in the readLines function.

However I did make sure to separate the pure functions from the impure side effects and I/O, with the parseNumber function. Of course, separating them is simply a good practice anyway. Functional programming really does promote many beneficial values.

open System

let parseNumber (number: string) =
    match Int32.TryParse number with
    | (true, number) -> Some number
    | _ -> None

let readFirstLine = parseNumber (Console.ReadLine())

let rec readLines accumulatedLines =
    let line = Console.ReadLine()
    let parsed = parseNumber line

    match line with
    | null
    | "" -> Seq.rev accumulatedLines
    | _ ->
        match parsed with
        | Some number -> readLines (number :: accumulatedLines)
        | None -> readLines accumulatedLines

[<EntryPoint>]
let main args =
    let count = readFirstLine

    match count with
    | Some count ->
        let numbers = readLines []

        Seq.iter
            (fun number ->
                for _ in 1 .. count do
                    printfn "%d" number)
            numbers

        0
    | None ->
        printfn "First line must be a number"
        1

A rant about Hacker Rank’s input mechanism

Unfortunately Hacker Rank chose to implement a very awkward and unnecessarily painful input mechanism. It writes to stdin but in a psuedo-unbounded fashion. That is, the number of lines of input often varies while also never providing a clean mechanism for signalling when input is complete. That isn’t strictly true, as it does actually close stdin but… why make it so much harder than it has to be?

Firstly, several of the other supported languages provide a “template” empty function where reading stdin and writing to stdout are taken care of for you - the function is then a pure function. The Hacker Rank F# option does not provide this template and so we must write extra code.

Secondly and more importantly, why does it not simply write a blank line to stdin? Think of all those programming exercises that go something like “if the user enters a blank line or types quit terminate the program”. Hacker Rank could instead write a blank line but it does not. This leads to the code being probably double what it really needs to be. If you run the above code locally you’ll see I took care of both approaches; null for when stdin has been closed and "" for when a user types an empty line.

Share:

Need help with your software project? Let's talk

Stay up to date

Subscribe to my newsletter to stay up to date on my articles and projects