Advent of Code 2024 - Day 2 in F#
Date: 2024-12-02 | create | build | fsharp | advent-of-code |
In this post I'll share my solution to Advent of Code 2024 Day 2 using F#.
HAMINIONs Members get full access to the project files so you can git clone
and run it yourself.
Day 2 - Part 1
Prompt: https://adventofcode.com/2024/day/2
My Takeaway:
(*
Part 1
Given:
* List of reports
* One report per line
* Report is a list of numbers called `levels` separated by spaces
Ask:
* Figure out which reports are safe
* Safe if:
* All increasing or decreasing
* Levels differ by at least one and at most 3
* Return count of safe reports
Solutions:
* A: Parse safety, then count - O(3n), 2n
* Parse out data into reports - O(n), n
* Iterate over reports to get safety - O(n), n
* Check all increasing or decreasing
* Check if levels differ by 1-3
* Sum safety to get final count - O(n)
*)
Code:
let is_report_safe
(report: int list)
: bool
=
if report.Length <= 1
then true
else
let first = report[0]
let second = report[1]
let is_descending = first >= second
let are_pairs_safe =
report
|> List.pairwise
|> List.map (fun pair ->
let first = fst pair
let second = snd pair
if first = second
then false
else
if (first >= second) <> is_descending
then false
else
let difference = abs (first - second)
if difference < 1 || difference > 3
then false
else
true
)
are_pairs_safe
|> List.forall id
let solve_part1 = fun () ->
printfn "Problem 2.1: Solving"
let exeDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
let filePath = Path.Combine(exeDir, "Static", "day_2_input.txt")
let lines = File.ReadLines(filePath)
let all_reports =
lines
|> Seq.map (fun l ->
l.Split(" ")
|> Array.map (fun i -> int i)
)
let total_safe_reports =
all_reports
|> Seq.map (fun report -> is_report_safe (report |> List.ofArray))
|> Seq.filter (fun report_safety -> report_safety = true)
|> Seq.length
printfn "Problem 2.1: Total Safe Reports - %A" total_safe_reports
total_safe_reports
My Answer -> Accepted
Some notes:
- I'm using the
if / then / else
format for simple early returns. A more functional approach would likely use railroading but sometimes I find guard statements easier to read. - List.pairwise turns a list into a list of tuples containing all pairs
Day 2 - Part 2
Prompt: https://adventofcode.com/2024/day/2
My Takeaways:
(*
Part 2
Given:
* Same reports as above
Ask:
* Count safe reports - assuming you canremove up to 1 bad level
* Removal removes that one level but rest of report must remain okay
Solution: -> A: Simple, reuses code
* A: Same approach as above but if it fails, we try to see if can make it succeed - O(n * m^2)
* Same as above
* For failure cases - try to see if can succeed:
* Brute force - try each combo of the list with one removal
* If any succeed - take it as a success and try again
* B: ~Single pass - O(n * 3m)
* Intuition - if we only have one failure case then there are only 3 values we
need to check - x-1, x, x + 1 - if none of those succeed then we'd have to
remove more than one item to get a "safe" report
* This is similar to A but limits retries to 3n
*)
- I decided to go with Solution A as it seemed simplest to get right. This may not be the optimal Leetcode solution but for this case A seems easier to reason about, utilizes already-tested code, and is not that much slower so here seems like a better choice overall since we don't foresee any perf issues with it.
Code: (is_report_safe
is reused from above code)
let remove_at_index
(list: int list)
(index: int)
: int list
=
List.concat [
list.[0..index - 1]
list.[index + 1..]
]
let is_report_safe_using_dampener
(report: int list)
: bool
=
// If og report is safe -> return true
if (is_report_safe report)
then true
else
// hamy: exists to short-circuit on first "true" value
seq {0..report.Length - 1}
|> Seq.exists(fun index ->
remove_at_index report index
|> is_report_safe
)
let solve_part2 = fun () ->
printfn "Problem 2.2: Solving"
let exe_dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
let file_path = Path.Combine(exe_dir, "Static", "day_2_input.txt")
let lines = File.ReadLines(file_path)
let all_reports =
lines
|> Seq.map (fun l ->
l.Split(" ")
|> Array.map (fun i -> int i)
)
let total_safe_reports =
all_reports
|> Seq.map (fun report -> report |> List.ofArray)
|> Seq.map (
fun report ->
is_report_safe_using_dampener report)
|> Seq.filter (fun report_safety -> report_safety = true)
|> Seq.length
printfn "Problem 2.2: Total Safe Reports - %A" total_safe_reports
total_safe_reports
My answer -> Accepted
Next
See my other AOC 2024 solutions in F# here.
HAMINIONs Members get full access to the project files so you can git clone
and run it yourself.
If you liked this post you might also like:
Want more like this?
The best / easiest way to support my work is by subscribing for future updates and sharing with your network.