Posted in:

Today’s Advent of Code challenge had some similarities to yesterday’s. We have a series of “instructions”, which we need to apply one by one, and intermediate states are important. We’re also dealing with coordinates again so int*int tuples are the obvious choice.

For this problem, we essentially can use fold to apply each line of instructions and end up at the digit to be pressed, and then use scan to apply each line at a time, keeping track of our current position.

Something I am slowly getting the hang of is that in F# we can declare let bindings within let bindings. So within the solve function I declare a lookup function which the isValid function uses, which the followInstruction function uses which the followLine function uses. The followInstruction function itself also declares some helper functions that it needs.

By scoping functions to just where they are needed, we make it very obvious that these are single-use helper functions, which makes our code more understandable. They can of course be moved elsewhere if it turns out they are more generally applicable.

Here’s my code (also available on GitHub), and as always, I welcome suggestions for improvement

let solve (keypad:string[]) startPos input =
    let lookup (x,y) = keypad.[y].[x]
    let isValid pos = lookup pos <> ' '
    let followInstruction pos instruction = 
        let addv (x,y) (i,j) = x+i,y+j
        let move = match instruction with | 'U' -> (0,-1) | 'D' -> (0,1) | 'R' -> (1,0) | 'L' -> (-1,0) | _ -> (0,0)
        let newPos = addv pos move
        if isValid newPos then newPos else pos
    let followLine = Seq.fold followInstruction
    input 
        |> Seq.scan followLine startPos
        |> Seq.skip 1 
        |> Seq.map (lookup >> string) 
        |> System.String.Concat 
        |> printfn "Code: %s"

let keypadA = [| "     "; " 123 "; " 456 "; " 789 "; "     " |]
let keypadB = [| "       "; "   1   "; "  234  "; " 56789 "; "  ABC  "; "   D   "; "       " |]

let testInput = [| "ULL  "; "RRDDD"; "LURDL";  "UUUUD" |]
let input = System.IO.File.ReadAllLines (__SOURCE_DIRECTORY__ + "\\input.txt")

solve keypadA (2,2) testInput
solve keypadA (2,2) input // part a 
solve keypadB (3,3) input // part b