I'm trying to solve the following Advent of Code problem in F# for practice.
Description of problem (can be found here):
Basically, there's a 'key pad' that I need to figure the combination to. To do that, I have to follow instructions in the form 'U'/'D'/'L'/'R' to move a key up, down, left, or right in a gird of keys (see puzzleInput
below). A newline means to take down the number I'm currently on and record it. I am bounded to the grid (eg. if I'm on the upper edge I can't move any more if the instruction was 'U'). The challenge is in 2 parts, in the first part the keypad is a 3x3 numerical grid (1..9). In the second part the keypad is a diamond shaped 5x5 alphanumeric grid (1..9, A, B, C, D) - movement is constrained to the diamond shape.
My attempt:
open System
open System.IO
// possible instructions, halt means to 'take down current number'
type Instruction =
| Up
| Down
| Right
| Left
| Halt
// helper method used later
let getLastElementOf seq =
seq |> List.rev |> List.head
// method to get code, see sample input below
let GetCode rawInput keyPadButtons startPos =
let parse (rawInput:string) :List<Instruction> =
rawInput.ToCharArray()
|> Seq.map (function
| 'U' -> Up
| 'D' -> Down
| 'R' -> Right
| 'L' -> Left
| '\n' -> Halt
| _ -> failwith "Unexpected token.")
|> (fun x -> Seq.toList x @ [Halt])
// used to enforce grid bounds, method used by followOne
let validate pos fallbackPos (tdarr:List<List<char>>) =
try
if tdarr.[fst pos].[snd pos] <> ' ' //I'm using ' ' as a padding
then pos
else fallbackPos
with
| e -> fallbackPos
// follows one instruction, method used by followAll
let followOne (grid:List<List<char>>) (pos:int * int) (instruction:Instruction) :int * int =
validate (match instruction with
| Up -> fst pos - 1 , snd pos
| Down -> fst pos + 1, snd pos
| Right -> fst pos, snd pos + 1
| Left -> fst pos, snd pos - 1
| _ -> failwith "Unknown instructon.") pos grid
// follow all instructions & produce result
let followAll (keyPadButtons:List<List<char>>) (instructions:List<Instruction>) :string =
instructions
|> Seq.mapFold (fun state instruction ->
"", (
if instruction = Halt
then fst state,
snd state + keyPadButtons
.[fst (getLastElementOf (fst state))]
.[snd (getLastElementOf (fst state))].ToString()
else fst state
@[followOne keyPadButtons (getLastElementOf (fst state)) instruction],
snd state
)
) ([startPos], "")
|> snd |> snd
rawInput
|> parse
|> followAll keyPadButtons
[<EntryPoint>]
let main argv =
let puzzleInput = "DLUUULUDLRDDLLLUDULLULLRUURURLUULDUUUDLDDRUDLUULLRLDDURURDDRDRDLDURRURDLDUURULDDULDRDDLDLDLRDRUURLDLUDDDURULRLLLLRLULLUDRDLDUURDURULULULRLULLLULURLRDRDDDDDDDLRLULUULLULURLLDLRLUDULLDLLURUDDLDULDLULDDRLRLRDDLRURLLLURRLDURRDLLUUUUDRURUULRLDRRULLRUDLDRLUDRDRDRRDDURURRDRDRUDURDLUDRUDLRRULDLRDDRURDDUUDLDRDULDDRRURLLULRDRURLRLDLLLUULUUDLUDLDRRRRDUURULDUDUDRLDLLULLLRDDDDDLRDDLLUULLRRRDURLRURDURURLUDRRLRURDRDRRRRULUDLDRDULULRUDULLLUDRRLRLURDDURULDUUDULLURUULRDRDULRUUUDURURDDRRUDURRLRDRULRUUU
LDRURRUUUULDRDDDLLULDRUDDRLLDLDRDLRUDDDLDDULULULLRULDUDRRDLRUURURDRURURDLLRUURDUUDRLDURDRDLRRURURDUUUURUURRLLLDRDUURRRRURULUUUDLUDDRUURRLDULRDULRRRRUDURRLURULRURRDRDLLDRRDUDRDURLDDRURULDRURUDDURDLLLUURRLDRULLURDRDRLDRRURRLRRRDDDDLUDLUDLLDURDURRDUDDLUDLRULRRRDRDDLUDRDURDRDDUURDULRRULDLDLLUDRDDUDUULUDURDRLDURLRRDLDDLURUDRLDUURLLRLUDLLRLDDUDLLLRRRLDLUULLUDRUUDRLDUUUDUURLRDDDDRRDRLDDRDLUDRULDDDRDUULLUUUUULDULRLLLRLLDULRDUDDRDDLRRLRDDULLDURRRURDDUDUDDRLURRLUUUULLDRDULUUDRDULDLLUDLURDLLURRDLUULURRULRLURRRRRUURDDURLRLLDDLRRDUUURDRDUDRDDDLLDDRDRRRLURRDUULULULULRRURDDLDDLLLRUDDDDDDLLLRDULURULLRLRDRR
DDRLLLDLRRURRDLDDRUURRURRLRRRRUURUURDLURRRDDLRUDRURLUURLLRRLRLURLURURDULLLLDLRURULUUDURRLULRDRDRRDDLLULRLUDLUUUDRLLRRURRLDULDDLRRLUUUUDDLRLDRLRRDRDLDDURDDRDDLDLURLRRRDDUDLLRLRLURRRRULLULLLLDRLDULDLLDULRLDRDLDDRRDDDDRUDRLLURULRLDDLLRRURURDDRLLLULLULDDRDLDDDLRLLDRLDRUURRULURDDRLULLDUURRULURUUDULLRUDDRRLLDLLRDRUDDDDLLLDDDLLUUUULLDUUURULRUUDUUUDDLDURLDRDRRLLUDULDLUDRLLLDRRRULUUDDURUDRLUDDRRLLDUDUURDDRURLUURDURURURRUUDUDDLLLDRRRURURRURDLRULLDUDRLRLLRUDRUDLR
RRRDRLRURLRRLUURDRLDUURURLRDRRUDLLUUDURULLUURDLLDRRLURRUDUUDRRURLRRDULLDDLRRRUDUUDUUDLDDDLUUDLDULDDULLDUUUUDDUUDUDULLDDURRDLRRUDUDLRDUULDULRURRRLDLLURUDLDDDRRLRDURDLRRLLLRUDLUDRLLLRLLRRURUDLUDURLDRLRUDLRUULDRULLRLDRDRRLDDDURRRUDDDUDRRDRLDDRDRLLRLLRDLRDUDURURRLLULRDRLRDDRUULRDDRLULDLULURDLRUDRRDDDLDULULRDDRUDRLRDDRLDRDDRRRDUURDRLLDDUULRLLLULLDRDUDRRLUUURLDULUUURULLRLUDLDDLRRDLLRDDLRDRUUDURDDLLLDUUULUUDLULDUDULDRLRUDDURLDDRRRDLURRLLRRRUDDLDDRURDUULRUURDRRURURRRUUDUDULUDLUDLLLUUUULRLLRRRRDUDRRDRUDURLUDDLDRDLDDRULLRRULDURUL
DLLLRDDURDULRRLULURRDULDLUDLURDDURRLLRRLLULRDLDRDULRLLRDRUUULURRRLLRLDDDRDRRULDRRLLLLDLUULRRRURDDRULLULDDDLULRLRRRUDRURULUDDRULDUDRLDRRLURULRUULLLRUURDURLLULUURUULUUDLUDLRRULLLRRLRURDRRURDRULRURRUDUDDDRDDULDLURUDRDURLDLDLUDURLLRUULLURLDDDURDULRLUUUDLLRRLLUURRDUUDUUDUURURDRRRRRRRRRUDULDLULURUDUURDDULDUDDRDDRDRLRUUUUDLDLRDUURRLRUUDDDDURLRRULURDUUDLUUDUUURUUDRURDRDDDDULRLLRURLRLRDDLRUULLULULRRURURDDUULRDRRDRDLRDRRLDUDDULLDRUDDRRRD"
let part1Code = GetCode puzzleInput [ [' '; ' '; ' '; ' '; ' '];
[' '; '1'; '2'; '3'; ' '];
[' '; '4'; '5'; '6'; ' '];
[' '; '7'; '8'; '9'; ' '];
[' '; ' '; ' '; ' '; ' '] ] (2, 2)
let part2Code = GetCode puzzleInput [ [' '; ' '; ' '; ' '; ' '; ' '; ' '];
[' '; ' '; ' '; '1'; ' '; ' '; ' '];
[' '; ' '; '2'; '3'; '4'; ' '; ' '];
[' '; '5'; '6'; '7'; '8'; '9'; ' '];
[' '; ' '; 'A'; 'B'; 'C'; ' '; ' '];
[' '; ' '; ' '; 'D'; ' '; ' '; ' '];
[' '; ' '; ' '; ' '; ' '; ' '; ' ']] (3, 1)
printfn "Part 1: %A, Part 2: %A" part1Code part2Code
This code yields the correct answers required by the challenge. The problem is I think it is too verbose (I'm doing this to learn clean code) and honestly I think there must be a better way to do most things that I haven't considered. Feedback on how to shorten (DRY) this code and to make it more readable is appreciated.