Today’s challenge was relatively kind, although this tweet from Eric Wastl makes me fear that things could be about to get tricky.

Our input is a list of strings, but what we want is a sequence of the first character from each line, then the second and so on. Sadly F# doesn’t have a direct equivalent to Python’s zip function which is perfect for this problem. I could have repurposed the rotate function I created for day 3, which rotates an array of arrays to solve this, but actually it was simpler just to iterate the input sequence multiple times picking out a letter at a different index each time.

We’re looking for the most (or least for part b) frequent character in each column of the input, and so Seq.countBy is ideal for this which returns a tuple of each character and its frequency. We can then use Seq.maxBy (or Seq.minBy for part b)

My decodePos function takes the sequence of input strings, the selector (Seq.maxBy or Seq.minBy) and the position index we are decoding:

let decodePos (messages:seq<string>) selector n =
    messages |> Seq.map (fun msg -> msg.[n]) |> Seq.countBy id |> selector snd |> fst

And now we just need to run this for each position, which we do by using the length of the first string in the list and applying decodePos to each index. Note that in F# 4, constructors are first-class functions so I can pipe the array of characters directly into the System.String constructor:

let decodeMessages (messages:string[]) selector =
    [|0..messages.[0].Length-1|] |> Array.map (decodePos messages selector) |> System.String 

Now we can use these functions to solve both parts of the problem:

let input = System.IO.File.ReadAllLines (__SOURCE_DIRECTORY__ + "\\input.txt")
decodeMessages input Seq.maxBy |> printfn "Part a: %s"
decodeMessages input Seq.minBy |> printfn "Part b: %s"

That was the code for my original solution, and you can view it on GitHub. But I wanted to try it with zip as well, so I made a poor man’s Python zip that works for string arrays:

let zip (a:string[]) =
    [| for x in 0..a.[0].Length-1 -> [| for y in a -> y.[x] |] |]

and this means we can implement decodeMessages like this:

let decodeMessages selector (messages:string[]) =
    messages |> zip |> Array.map (Seq.countBy id >> selector snd >> fst) |> System.String

Although arguably, we should make this code a bit more readable by expressing the decoding of a single character from an array of characters into its own function. Something like this:

let decodeMessages selector = 
    let decodeChar = Seq.countBy id >> selector snd >> fst
    zip >> (Array.map decodeChar) >> System.String

My alternative solution can be found here.

As always with these puzzles, there is no one “right” way to solve it. I like solutions that are succinct, readable, perform well, and easily adapted to changing requirements. But there’s usually a compromise between those constraints.

Vote on HN
comments powered by Disqus