List Replication in F#

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.