I’m learning F#. I’m working through the advent of code problems. I pasted my day 6 solution below.
After completing 6 days of puzzles, I find I really miss break statements and early return statements that are in many other languages. In order to return from the while loop in walk
, I had to invent the WalkResult
type, and later call Option.get
which may throw an exception if I got the logic wrong. Plus, about 10 lines of code ends up further indented in an else block. None of this would be necessary if F# provided an early return statement.
Also, in part1, I count characters the functional way using Array.sumBy
and friends. In part2 I abandon that technique and use a mutable count and for loops. I find part2 much easier to read.
Are there better ways to do what I’m attempting to do?
open System.IO
let input = File.ReadAllLines("input.txt")
// Find the starting position
let mutable startPos = (-1, -1)
for row in 0..input.Length-1 do
if (-1, -1) = startPos then
let line = input[row]
let i = line.IndexOf('^')
if i >= 0 then
startPos <- (row, i)
let rows = input.Length
let cols = input[0].Length
type WalkResult =
| Escaped
| InfiniteLoop
// Trace the path, leaving footprints of a, b, d, and h in lines.
// The least significant 4 bits of a, b, d, and h are distinct.
// a: 0001
// b: 0010
// d: 0100
// h: 1000
// The most significant 4 bits are all 0110.
let pathMask = uint8 0b01100000
let walk (lines: char array array) =
let mutable step = (-1, 0, 'a')
let mutable result = None
let mutable pos = startPos
while Option.isNone result do
let (row, col) = pos
let (i, j, c) = step
// Calculate the new value for this space.
let footPrint = lines[row][col]
let footPrintByte = uint8 footPrint
let newFootPrint =
if pathMask &&& footPrintByte = pathMask then
char (footPrintByte ||| (uint8 c)) // Merge with footprint
else
c // Replace space with footprint.
if footPrint = newFootPrint then
result <- Some InfiniteLoop
else
lines[row][col] <- newFootPrint
// Examine the next space.
let (row, col) = (row + i, col + j)
if row < 0 || row >= rows || col < 0 || col >= cols then
result <- Some Escaped
else if lines[row][col] = '#' then
// Turn right 90 degrees.
step <-
match step with
| (-1, 0, 'a') -> (0, 1, 'b')
| (0, 1, 'b') -> (1, 0, 'd')
| (1, 0, 'd') -> (0, -1, 'h')
| (0, -1, 'h') -> (-1, 0, 'a')
| bad -> failwith "Bad step"
else
pos <- (row, col) // Leave a footprint in the next space.
Option.get result
let part1 =
let lines = input |> Array.map (fun line -> line.ToCharArray())
walk lines |> ignore
// Count path.
let isPathChar = fun (c: char) -> pathMask = ((uint8 c) &&& pathMask)
let count =
lines |> Array.sumBy (fun row ->
Array.filter isPathChar row |> Array.length)
printfn "part1 %d" count
let part2 =
let mutable count = 0
for row in 0..rows-1 do
for col in 0..cols-1 do
let lines = input |> Array.map (fun line -> line.ToCharArray())
lines[row][col] <- '#'
if InfiniteLoop = walk lines then
count <- count + 1
printfn "part2 %d" count