Advent of Code 2024 - Day 3 in F#
Date: 2024-12-03 | advent-of-code | build | create | fsharp |
DISCLOSURE: If you buy through affiliate links, I may earn a small commission. (disclosures)
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 way to support my work is to like / comment / share for the algorithm and subscribe for future updates.
