Advent of Code 2024 - Day 3 in F#
Date: 2024-12-03 | create | build | fsharp | advent-of-code |
In this post I'll share my solution to Advent of Code 2024 Day 3 using F#.
HAMINIONs Members get full access to the project files so you can git clone
and run it yourself.
Day 3 - Part 1
Prompt: https://adventofcode.com/2024/day/3
Given:
- Jumbled instructions
Ask:
- Find valid instruction
- Valid instruction is of form
mul(X,Y)
- Where X, Y are numbers
- Valid instruction is of form
- Then sum all of the values
SubProblems:
- A: Finding valid / invalid instructions
- A.1: Use Regex
- Can precisely describe the valid commands and pull them out
- A.2: Some sort of parsng
- We could do parsing ourselves - find start
mul(
and end)
- But this can quickly get complicated w lots of logic ourselves
- The required regex here is pretty straightforward so prob is better
- We could do parsing ourselves - find start
- A.1: Use Regex
- B: Summing products
- B.1: Once we have the multiply statements, can pull out X and Y, multiply and add them
Solutions:
- A: Pull out multiply instructions, add products
- Use Regex to pull out multiply instructions - O(~n) but could be more expensive at O(2^n)
- Sum the products
Code:
let read_all_input_text: unit -> string = fun () ->
let exe_dir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
let file_path = Path.Combine(exe_dir, "Static", "day_3_input.txt")
let all_text = File.ReadAllText(file_path)
all_text
let read_all_mul_values_from_input
(all_text: string)
: (int * int) list
=
let mul_regex_pattern = @"mul\((\d+),(\d+)\)"
let matches = Regex.Matches(all_text, mul_regex_pattern)
matches
|> Seq.map (fun m ->
let a = int m.Groups[1].Value
let b = int m.Groups[2].Value
(a, b)
)
|> Seq.toList
let solve_part1 = fun () ->
printfn "Day 3.1: Solving"
let total =
read_all_input_text()
|> read_all_mul_values_from_input
|> List.sumBy (fun t ->
let a, b = t
a * b
)
printfn "Day 3.1: Total: %A" total
My answer: 175015740 -> Accepted
Day 3 - Part 2
Prompt: https://adventofcode.com/2024/day/3
Given:
- Same input as above
Ask:
- Sum products of mul instructions
- Valid instructions are:
- mul(X,Y)
- Don't have a don't() clause in front of them / or have a do() clause before them
- do() -> on
- don't() -> off
- Valid instructions are:
SubProblems:
- A: Finding enabled / disabled sections
- A.1: Could do a big old regex
- But this is kinda hard to read - regex gets v complicated fast
- A.2: Could pre-process enabled / disabled sections of strings - O(n), n
- Pre-process enabled vs disabled
- Put together enabled sections
- Then run mul regex on enabled only
- How:
- Find all don'ts
- Find all do's
- Parse over indexes finding sections where they are enabled vs disabled
- Enabled - do - next don't that's larger than it
- Create new string with just those indices together
- Pass into mul sumby
- A.1: Could do a big old regex
Solutions:
- A: Pre-process enabled / disabled sections, pass into vals
Code:
let get_all_starting_indices
(all_text: string)
(regex_pattern: string)
: int list
=
Regex.Matches(all_text, regex_pattern)
|> Seq.map (fun m -> m.Index)
|> List.ofSeq
let get_enabled_input_sections
(all_text: string)
: string
=
let dont_string = @"don't\(\)"
let do_string = @"do\(\)"
let all_dont_indices =
get_all_starting_indices
all_text
dont_string
let all_do_indices =
get_all_starting_indices
all_text
do_string
(*
* Do section is do -> next don't
* Don't section is don't -> next do
Iteration:
* Sliding window - first do to first don't (or end)
*)
let first_dont_index = all_dont_indices[0]
let mutable do_index = 0
let mutable dont_index = 1
let mutable processed_string_index = first_dont_index
let mutable all_enabled_indices: (int * int) List = List<(int * int)>()
all_enabled_indices.Add((0, first_dont_index))
while do_index < all_do_indices.Length && dont_index < all_dont_indices.Length do
// Find next do
let mutable next_do_string_pointer = 0
while do_index < all_do_indices.Length && next_do_string_pointer <= processed_string_index do
next_do_string_pointer <- all_do_indices[do_index]
do_index <- do_index + 1
// Find next don't
let mutable next_dont_string_pointer = 0
while (dont_index < all_dont_indices.Length
&& (next_dont_string_pointer <= processed_string_index
|| next_dont_string_pointer <= next_do_string_pointer)) do
next_dont_string_pointer <- all_dont_indices[dont_index]
dont_index <- dont_index + 1
all_enabled_indices.Add((next_do_string_pointer, next_dont_string_pointer))
processed_string_index <- next_dont_string_pointer
// Catch case where we still have a do left
if do_index < all_do_indices.Length && dont_index >= all_do_indices.Length then
let mutable next_do_string_pointer = all_do_indices[do_index]
while do_index < all_do_indices.Length && next_do_string_pointer <= processed_string_index do
next_do_string_pointer <- all_do_indices[do_index]
do_index <- do_index + 1
if next_do_string_pointer > processed_string_index then
all_enabled_indices.Add((next_do_string_pointer, all_text.Length - 1))
all_enabled_indices
|> Seq.map (fun indices ->
let start, end_index = indices
all_text.[start..end_index]
)
|> String.concat ""
let solve_part2 = fun() ->
printfn "Day 3.2: Solving"
let total =
read_all_input_text()
|> get_enabled_input_sections
|> read_all_mul_values_from_input
|> List.sumBy (fun t ->
let a, b = t
a * b
)
printfn "Day 3.2: Total: %A" total
My Answer -> Accepted
Notes:
- I am not very happy with this code - I think the sliding window stuff is over complicated and there's probably a more functional, easier to read version utilizing built-ins.
- I got hung up on a few edge cases - like treating the first part as enabled and dealing with a final
do()
statement
Next
This one was a little tricky and I don't love my solution to this but it works and sometimes you just have to be okay with that.
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.