13 votes

Day 3: Mull It Over

Today's problem description: https://adventofcode.com/2024/day/3

Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace python with any of the "short names" listed in this page of supported languages):

<details>
<summary>Part 1</summary>

```python
Your code here.
```

</details>

17 comments

  1. [3]
    Crespyl
    (edited )
    Link
    Oooh smells like an interpreter... I really hope we get more of this, my all time favorite AoC was building the Intcode VM a few years back. Ruby def compute_p1(input) re = /mul\((\d+),(\d+)\)/...

    Oooh smells like an interpreter...

    I really hope we get more of this, my all time favorite AoC was building the Intcode VM a few years back.

    Ruby
    def compute_p1(input)
      re = /mul\((\d+),(\d+)\)/
      input.scan(re).map { |match|
        match.map(&:to_i).inject(:*)
      }.sum
    end
    
    def compute_p2(input)
      re = /(do)\(\)|(don't)\(\)|(mul)\((\d+),(\d+)\)/
      commands = input.scan(re).map(&:compact)
    
      state = :do
      value = 0
      until commands.empty?
        cmd = commands.shift
        case cmd[0]
        when "mul" then
          value += (cmd[1].to_i * cmd[2].to_i) unless state == :dont
        when "do"
          state = :do
        when "don't"
          state = :dont
        end
      end
      return value
    end
    
    4 votes
    1. scarecrw
      Link Parent
      Intcode! that was the first year I participated, and definitely got me hooked. I feel like it's been teased a few times since then but we haven't gotten anything quite the same.

      Intcode! that was the first year I participated, and definitely got me hooked. I feel like it's been teased a few times since then but we haven't gotten anything quite the same.

      2 votes
    2. csos95
      Link Parent
      That was my first thought when I read the challenge and I'm looking forward to it!

      That was my first thought when I read the challenge and I'm looking forward to it!

  2. tjf
    Link
    I feared regex would only get me so far, but it worked for both parts quite well! My Python solutions: Part 1 import re import sys operands = re.findall(r'mul\((\d{1,3}),(\d{1,3})\)',...

    I feared regex would only get me so far, but it worked for both parts quite well! My Python solutions:

    Part 1
    import re
    import sys
    
    operands = re.findall(r'mul\((\d{1,3}),(\d{1,3})\)', sys.stdin.read())
    print(sum(int(x) * int(y) for x, y in operands))
    
    Part 2
    import re
    import sys
    
    memory = re.sub(r"don't\(\).*?(do\(\)|$)", '', sys.stdin.read(), flags=re.DOTALL)
    operands = re.findall(r'mul\((\d{1,3}),(\d{1,3})\)', memory)
    print(sum(int(x) * int(y) for x, y in operands))
    
    3 votes
  3. lily
    Link
    Someone has written a regex module for Jai, but it doesn't have an in-built way to find all matches, so I wasn't able to use it. Luckily, it wasn't too bad to solve this without regex - I just...

    Someone has written a regex module for Jai, but it doesn't have an in-built way to find all matches, so I wasn't able to use it. Luckily, it wasn't too bad to solve this without regex - I just used a sliding window and verified the instructions manually. A little verbose, maybe, but good enough.

    Solution (Jai)
    /* Advent of Code 2024
     * Day 03: Mull It Over
     */
    
    #import "Basic";
    #import "File";
    #import "String";
    
    main :: () {
        input, success := read_entire_file("inputs/day_03.txt");
        assert(success);
    
        result_sum := 0;
        result_sum_conditional := 0;
        mul_enabled := true;
    
        // The shortest valid instruction is 4 characters long (do()), so
        // technically with this range, if do() appeared at the very end of the
        // input, we would ignore it. However, that's not a concern, since at that
        // point there is no more room for mul() instructions anyway, meaning the
        // do() instruction would be effectively useless.
        for 0..input.count - 9 {
            // Don't waste time checking slices that aren't even a possibility.
            // The only valid instructions start with m (mul) or d (do/don't).
            if input[it] != #char "m" && input[it] != #char "d" {
                continue;
            }
    
            // The maximum instruction size is 12 (a mul() instruction with two
            // three-digit numbers).
            instruction := slice(
                input,
                it,
                ifx it >= input.count - 12 then input.count - it else 12
            );
    
            end_index := find_index_from_left(instruction, #char ")");
            if end_index == -1 {
                continue;
            }
    
            trimmed_instruction := slice(instruction, 0, end_index + 1);
            if slice(trimmed_instruction, 0, 4) == "mul(" {
                numbers := split(
                    slice(trimmed_instruction, 4, trimmed_instruction.count - 5),
                    ","
                );
    
                if numbers.count == 2 {
                    valid := true;
                    for numbers {
                        all_numbers := true;
                        for it {
                            if it < #char "0" || it > #char "9" {
                                all_numbers = false;
                                break;
                            }
                        }
    
                        if !all_numbers {
                            valid = false;
                            break;
                        }
                    }
    
                    if valid {
                        result := (
                            string_to_int(numbers[0]) * string_to_int(numbers[1])
                        );
    
                        result_sum += result;
                        if mul_enabled {
                            result_sum_conditional += result;
                        }
                    }
                }
            } else if trimmed_instruction == {
                case "do()";    mul_enabled = true;
                case "don't()"; mul_enabled = false;
            }
        }
    
        print("Part 1: %\nPart 2: %\n", result_sum, result_sum_conditional);
    }
    
    2 votes
  4. scarecrw
    Link
    Well this was frustrating. No problems solving it, but every step felt like I was going against the grain. I couldn't get regex groups to work so just fell back on string manipulation and my part...

    Well this was frustrating. No problems solving it, but every step felt like I was going against the grain. I couldn't get regex groups to work so just fell back on string manipulation and my part 2 didn't feel great.

    I'm going to have to dive deeper into Pharo's regex tools, as I'm sure many future days are going to be a pain if I can't get that working well.

    Smalltalk Solution
    Class {
    	#name : 'Day3Solver',
    	#superclass : 'AoCSolver',
    	#instVars : [
    		'operations'
    	],
    	#category : 'AoCDay3',
    	#package : 'AoCDay3'
    }
    
    Day3Solver >> evalMul: aString [ 
    	| idx val1 val2 |
    	idx := aString indexOf: $,.
    	val1 := (aString copyFrom: 5 to: idx) asNumber.
    	val2 := (aString copyFrom: (idx + 1) to: (aString size - 1)) asNumber.
    	^ val1 * val2
    ]
    
    Day3Solver >> parseRawData [
    	operations := rawData regex: 'mul\(\d{1,3},\d{1,3}\)|do\(\)|don''t\(\)' matchesCollect: [ :x | x ]
    ]
    
    Day3Solver >> solvePart1 [
    	| mulOps |
    	mulOps := operations select: [ :op | op beginsWith: 'mul' ].
    	^ (mulOps collect: [ :mulOp | self evalMul: mulOp ]) sum
    ]
    
    Day3Solver >> solvePart2 [
    	| total shouldMul |
    	total := 0.
    	shouldMul := true.
    	operations do: [ :op |
    		((op beginsWith: 'mul') and: shouldMul)
    			ifTrue: [ total := total + (self evalMul: op) ]
    			ifFalse: [ shouldMul := op = 'do()' ] ].
    	^ total
    ]
    
    2 votes
  5. csos95
    (edited )
    Link
    I'm pretty tired tonight (finally remembered to drag myself to the gym a few hours after dinner) so my initial solution is a bit less compact than it could be. I'll probably go back over it...

    I'm pretty tired tonight (finally remembered to drag myself to the gym a few hours after dinner) so my initial solution is a bit less compact than it could be.
    I'll probably go back over it tomorrow and also write a few functions for my utils module.

    Rhai Solution
    import "utils" as utils;
    
    let input = utils::get_input(3, false);
    
    // PART 1
    let total = 0;
    for (section, i) in input.split("mul(").extract(1) {
        let possible_params = section.split(")");
        if possible_params.len() < 2 {
            continue;
        }
        let possible_digits = possible_params[0].split(",");
        if possible_digits.len() != 2
           || possible_digits[0].to_chars().some(|c| c < '0' || c > '9')
           || possible_digits[1].to_chars().some(|c| c < '0' || c > '9') {
            continue;
        }
        let x = possible_digits[0].parse_int();
        let y = possible_digits[1].parse_int();
        total += x * y;
    }
    
    print(`part 1: ${total}`);
    
    // PART 2
    let total = 0;
    let enabled = true;
    for (section, i) in input.split("mul(") {
        if enabled && i != 0 {
            let possible_params = section.split(")");
            if possible_params.len() < 2 {
                continue;
            }
            let possible_digits = possible_params[0].split(",");
            if possible_digits.len() != 2
               || possible_digits[0].to_chars().some(|c| c < '0' || c > '9')
               || possible_digits[1].to_chars().some(|c| c < '0' || c > '9') {
                continue;
            }
            let x = possible_digits[0].parse_int();
            let y = possible_digits[1].parse_int();
            total += x * y;
        }
        if enabled && section.contains("don't()") {
            enabled = false;
        }
        if !enabled && section.contains("do()") {
            enabled = true;
        }
    }
    
    print(`part 2: ${total}`);
    
    My current utils.rhai
    fn get_input(day, example) {
        if example {
            let input = open_file(`day${day}_example.txt`)
                .read_string();
            input.trim();
            input
        } else {
            let input = open_file(`day${day}.txt`)
                .read_string();
            input.trim();
            input
        }
    }
    
    fn is_sorted(array) {
        for i in 0..array.len()-1 {
            if array[i] > array[i+1] {
                return false;
            }
        }
        true
    }
    
    fn is_rev_sorted(array) {
        for i in range(array.len()-1, 0, -1) {
            if array[i] > array[i-1] {
                return false;
            }
        }
        true
    }
    
    fn is_ordered(array) {
        let fails = 0;
        for i in 0..array.len()-1 {
            if array[i] > array[i+1] {
                fails += 1;
                break;
            }
        }
        for i in range(array.len()-1, 0, -1) {
            if array[i] > array[i-1] {
                fails += 1;
                break;
            }
        }
        fails < 2
    }
    

    Edit: I added basic regex to Rhai with new_regex(regex_string) and captures(Regex, input) functions using the regex crate and made a shorter solution.

    Rhai Solution
    import "utils" as utils;
    
    let input = utils::get_input(3, false);
    
    // PART 1
    let total = 0;
    let regex = new_regex(#"mul\(([0-9]+),([0-9]+)\)"#);
    for captures in regex.captures(input) {
        let x = captures[1].parse_int();
        let y = captures[2].parse_int();
        total += x * y;
    }
    
    print(`part 1: ${total}`);
    
    // PART 2
    let total = 0;
    let enabled = true;
    let regex = new_regex(#"(mul|do|don't)\((?:([0-9]+),([0-9]+))?\)"#);
    for captures in regex.captures(input) {
        switch captures[1] {
            "don't" => enabled = false,
            "do" => enabled = true,
            "mul" if enabled => {
                let x = captures[2].parse_int();
                let y = captures[3].parse_int();
                total += x * y;
            }
        }
    }
    
    print(`part 2: ${total}`);
    
    2 votes
  6. [3]
    DataWraith
    Link
    It's always fun to see how the inputs have traps that seem tailored for different ways of approaching the puzzle -- I implemented this twice, because I was curious which approach was nicer, and...

    It's always fun to see how the inputs have traps that seem tailored for different ways of approaching the puzzle -- I implemented this twice, because I was curious which approach was nicer, and both times had different problems crop up that weren't a problem for the other approach. The only trap that was missing was a mul of the form mul(1234,5678).

    Rust

    I first did this using RegEx, but that solution was kind of boring (even though it took me longer than it should have), so here's the hand-rolled evaluator for part 2 I came up with just now:

    pub fn parse2(input: &str) -> PuzzleInput {
        let mut input = input;
        let mut enabled = true;
        let mut muls = Vec::new();
    
        while !input.is_empty() {
            if input.starts_with("do()") {
                input = &input[4..];
                enabled = true;
                continue;
            }
    
            if input.starts_with("don't()") {
                input = &input[7..];
                enabled = false;
                continue;
            }
    
            if !enabled || !input.starts_with("mul(") {
                input = &input[1..];
                continue;
            }
    
            // Skip "mul("
            input = &input[4..];
    
            let Some((a, b)) = input.split_once(")") else {
                // Unclosed parenthesis, skip the rest of the input
                break;
            };
    
            if a.len() > 7 {
                // Too many digits (also generally caused by unclosed or mismatched parentheses)
                input = &input[1..];
                continue;
            }
    
            let Some((x, y)) = a.split_once(",") else {
                // No comma -- skip past the parenthesis
                input = b;
                continue;
            };
    
            let x = x.parse::<usize>().unwrap();
            let y = y.parse::<usize>().unwrap();
    
            muls.push(x * y);
    
            input = b;
        }
    
        PuzzleInput { muls }
    }
    

    Doing it this way actually took less time to implement than installing a regex crate and figuring out how to properly escape everything, though of course, doing it for the second time avoids the stupid mistakes you make when you rush.

    2 votes
    1. [2]
      kari
      Link Parent
      I did it in with Regex, too, but in Racket, and I might try steal a recursive variant of your solution instead because yours is nice and I'm worried about having to add operations down the line xD

      I did it in with Regex, too, but in Racket, and I might try steal a recursive variant of your solution instead because yours is nice and I'm worried about having to add operations down the line xD

      1 vote
      1. DataWraith
        Link Parent
        I actually went back to regular expressions after seeing @tjf's solution; now both parts are like 5 lines of code each, which I think is more elegant than having two screen-fulls of manual parsing...

        I actually went back to regular expressions after seeing @tjf's solution; now both parts are like 5 lines of code each, which I think is more elegant than having two screen-fulls of manual parsing...

  7. jzimbel
    (edited )
    Link
    Elixir I decided to solve this one with regular expressions even though I'll probably regret it if future puzzles keep iterating on this concept. I learned about the (?|...) capture group for this...

    Elixir

    I decided to solve this one with regular expressions even though I'll probably regret it if future puzzles keep iterating on this concept.

    I learned about the (?|...) capture group for this one. Shout out to my pal regex101.com.

    Elixir's regex implementation has this nice x modifier that makes it ignore all whitespace within the pattern, as well as anything after "#" on a line. This lets you break the pattern up into separate lines and add inline comments. Great for readability!

    Both parts
    defmodule AdventOfCode.Solution.Year2024.Day03 do
      use AdventOfCode.Solution.SharedParse
    
      @impl true
      def parse(input), do: String.trim(input)
    
      def part1(mem) do
        ~r/mul\((\d{1,3}),(\d{1,3})\)/
        |> Regex.scan(mem, capture: :all_but_first)
        |> sum_of_products()
      end
    
      def part2(mem) do
        # (?| ...) drops unmatched alternatives from the returned captures.
        # E.g. matching "don't()" will produce ["don't()"] instead of ["", "", "", "don't()"]
        ~r"""
        (?|
          (mul)\((\d{1,3}),(\d{1,3})\)
          |(do)\(\)
          |(don't)\(\)
        )
        """x
        |> Regex.scan(mem, capture: :all_but_first)
        |> apply_toggles()
        |> sum_of_products()
      end
    
      defp sum_of_products(factor_pairs) do
        factor_pairs
        |> Enum.map(fn [a, b] -> String.to_integer(a) * String.to_integer(b) end)
        |> Enum.sum()
      end
    
      defp apply_toggles(matches) do
        Enum.reduce(matches, %{on?: true, acc: []}, fn
          ["mul" | factor_pair], state when state.on? -> update_in(state.acc, &[factor_pair | &1])
          ["mul" | _factor_pair], state when not state.on? -> state
          ["do"], state -> %{state | on?: true}
          ["don't"], state -> %{state | on?: false}
        end).acc
      end
    end
    
    2 votes
  8. xavdid
    Link
    Python Step-by-step explanation | full code I love regex! Using capture groups and re.findall made part 1 a 1-liner, which is nice. For today's writeup, I ended up with a (very) basic explanation...

    Python

    Step-by-step explanation | full code

    I love regex! Using capture groups and re.findall made part 1 a 1-liner, which is nice.

    For today's writeup, I ended up with a (very) basic explanation of regex for people totally unfamiliar with it. So if it's something you've always been interested in, this might be a good place to jump in!

    2 votes
  9. tomf
    Link
    oh yeah, I almost forgot. I brought everything into A1 for this. This one was really quick and easy. I could probably cut these formulas down... but this year I have decided to not be consumed by...

    oh yeah, I almost forgot.

    I brought everything into A1 for this. This one was really quick and easy. I could probably cut these formulas down... but this year I have decided to not be consumed by AOC.

    Part 1
    =ARRAYFORMULA(
      SUM(
       BYROW(
        IFERROR(
         SPLIT(
          REGEXEXTRACT(
           TRANSPOSE(
            SPLIT(
             REGEXREPLACE(A1,"(mul)","|$1"),
             "|")),
           "(mul\(\d+,\d+\))"),
          "mul[](),")),
         LAMBDA(
          x,
          INDEX(x,0,1)*INDEX(x,0,2)))))
    
    Part 2
    =ARRAYFORMULA(
      SUM(
       LET(s,
        SPLIT(
         QUERY(
          TOCOL(
           SPLIT(
            REGEXREPLACE(
             QUERY(
              TRANSPOSE(
               SPLIT(
                REGEXREPLACE(A1,"(do)","|$1"),
                "|")),
              "where Col1 starts with 'do()'",0),
             "(mul\(\d+,\d+\))","|$1|"),
            "|",1,1),
           3),
          "where Col1 matches '^mul\(\d+,\d+\).*'"),
         "mul(),"),
        INDEX(s,0,1)*INDEX(s,0,2))))
    
    
    1 vote
  10. [2]
    kari
    Link
    I used regular expressions since that's the first thing I though of, though I'm sure I'll regret it and end up giving up early because of it. Even part 2 was confusing me for quite a while. Racket...

    I used regular expressions since that's the first thing I though of, though I'm sure I'll regret it and end up giving up early because of it. Even part 2 was confusing me for quite a while.

    Racket
    #! /usr/bin/env racket
    #lang racket
    
    (require math/base rackunit)
    
    ; TODO: Use #:match-select and capture groups
    (define (day03/run-part01 in)
      (sum (map (lambda (lst)
                  (foldl * 1 lst))
                (map (lambda (lst)
                       (map string->number (regexp-match* (pregexp "\\d{1,3}") lst)))
                     (regexp-match* (pregexp "mul\\(\\d{1,3},\\d{1,3}\\)")
                                    (read-line in))))))
    
    (define (day03/run-part02 in)
      (car (foldl (lambda (cur result)
                    ; we pass a pair along with the current result + status
                    (if (string? cur)
                        (if (equal? "don't()" cur)
                            (cons (car result) #f)
                            (cons (car result) #t))
                        (if (cdr result) ; should be 1
                            (cons (+ (* (car cur) (list-ref cur 1)) (car result)) #t)
                            result)))
                  (cons 0 #t)
                  (map (lambda (lst)
                         ; for "mul" operators, parse the strings into numbers and return as a list
                         (if (regexp-match #rx"mul" lst)
                             (map string->number (regexp-match* (pregexp "\\d{1,3}") lst))
                             lst))
                       ; match on any of the operators we know
                       (regexp-match* (pregexp "(mul\\((\\d{1,3}),(\\d{1,3})\\))|(don)'t\\(\\)|(do)\\(\\)")
                                      (read-line in))))))
    
    (let ([input (open-input-file "inputs/day03.in")])
      (printf "~a\n" (day03/run-part01 input)))
    (let ([input (open-input-file "inputs/day03.in")])
      (printf "~a\n" (day03/run-part02 input)))
    
    (check-equal? (day03/run-part01 (open-input-file "examples/day03_1.in")) 161 "Test part 1")
    (check-equal? (day03/run-part02 (open-input-file "examples/day03_2.in")) 48 "Test part 2")
    
    1 vote
    1. minion
      Link Parent
      Ahh, I quite like the approach here of parsing out all of the valid functions in a single regex and then looping through functions ... it seems more elegant than the string manipulation I did on...

      Ahh, I quite like the approach here of parsing out all of the valid functions in a single regex and then looping through functions ... it seems more elegant than the string manipulation I did on part 2 (and possibly scales better if this does turn out to be another intcode computer thing?)

      1 vote
  11. minion
    Link
    I, like many of you, used regex for this, however had to take a quick detour to get "quicklisp" - what appears to be a package manager for common lisp libraries - as common lisp doesn't have a...

    I, like many of you, used regex for this, however had to take a quick detour to get "quicklisp" - what appears to be a package manager for common lisp libraries - as common lisp doesn't have a regex library builtin.

    That said, part 1 was fairly simple once I'd done that, with the slight convolution that as there was no function to both get capture groups and get multiple matches at the same time I resorted to two passes with the same regex.

    Part 2 gave me more trouble, until I remembered that (loop) existed and things are mutable and I could just bang out something to get the active parts of the string in much the same way as I could python. I also looked at a pure regex solution and a range-based solution but this turned out to be a relatively easy implementation by comparison. I pity my friend who is writing this in nix for what I am certain must be a horrible recursive function.

    My code
    (require 'uiop)
    
    ; Please install quicklisp on your own time from https://common-lisp-libraries.readthedocs.io/quicklisp/
    (ql:quickload "cl-ppcre")
    
    (defun mul-instructions (str)
      (ppcre:all-matches-as-strings "mul\\(([0-9]+),([0-9]+)\\)" str))
    
    (defun get-enabled-portion (str)
      (let (
    	(disables (ppcre:all-matches "don't\\(\\)" str))
    	(enables (ppcre:all-matches "do\\(\\)" str))
    	(result "")
    	(enabled t))
        (progn
          (loop
    	for index from 0
            for c in (coerce str 'list)
            do (progn
    	     (when (equal (first disables) index)
    	       (setq enabled nil)
    	       (pop disables))
    	     (when (equal (first enables) index)
    	       (setq enabled t)
    	       (pop enables))
    	     (when enabled
    	       (setq result (concatenate 'string result (list c))))))
          result)))
    
    (defun numbers-to-multiply (instruction)
      (ppcre:register-groups-bind
        ((#'parse-integer num1 num2))
        ("mul\\(([0-9]+),([0-9]+)\\)" instruction :sharedp t)
        (list num1 num2)))
    
    (defun all-multiplications (str)
      (mapcar #'numbers-to-multiply (mul-instructions str)))
    
    (defun part1 (filename)
      (let* (
    	 (str (uiop:read-file-string filename))
    	 (multiplications (all-multiplications str))
    	 (multiplied (mapcar
    		       (lambda (nums)
    			 (reduce #'* nums))
    		       multiplications))
    	 (total (reduce #'+ multiplied)))
        total))
    
    (defun part2 (filename)
      (let* (
    	 (str (uiop:read-file-string filename))
    	 (active (get-enabled-portion str))
    	 (multiplications (all-multiplications active))
    	 (multiplied (mapcar
    		       (lambda (nums)
    			 (reduce #'* nums))
    		       multiplications))
    	 (total (reduce #'+ multiplied)))
        total))
    
    1 vote
  12. Halfloaf
    Link
    Hello there! This was a really easy one for me - I've used a bunch of regex in the past, but I did have to figure out some little details of how python's re library worked. I also tripped myself...

    Hello there!

    This was a really easy one for me - I've used a bunch of regex in the past, but I did have to figure out some little details of how python's re library worked.

    I also tripped myself up a little bit, and didn't realize that the example for the second part was slightly different than the example for the first part.

    Part 2 (and one) solution
    import re
    import click
    
    class Day3Solver():
        def __init__(self, filename):
            with open(filename) as txtfile:
                self.text = txtfile.read()
    
        def solve(self):
            pattern = r"mul\(\d\d?\d?,\d\d?\d?\)|do\(\)|don't\(\)"
            mul_matches = re.findall(pattern, self.text)
            out = 0
            calculate = True
            for mul in mul_matches:
                if 'mul' in mul:
                    if calculate:
                        values = mul[4:-1].split(',')
                        out = out + int(values[0])*int(values[1])
                elif 'do()' in mul:
                    calculate = True
                elif "don't()" in mul:
                    calculate = False
                # print(f"match: {mul}, calculate = {calculate}")
            return out
    
    @click.command()
    @click.argument('filename')
    def main(filename):
        p = Day3Solver(filename)
        print(p.solve())
    
    if __name__=="__main__":
        main()