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
  • 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
  • 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

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

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.