from misc.helper import verify_answer
= """xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))"""
example_input
= 161 example_answer_one
Day 3: Mull It Over
The next place we search for the Chief Historian is the North Pole Toboggan Rental Shop. While here, we are tasked with fixing the computers – we need to clean the input for the multiplication.
For the full details of the task, you can visit the official Advent of Code 2024 website.
To set the scene, I again asked ChatGPT to generate an image of the shop. Here’s the result:
Part 1: Extracting multiplication instructions
The program the shopkeeper is using is supposed to perform multiplication, but it’s not working as expected. The input is a string that contains a series of multiplication instructions, but since it is corrupted there are also many invalid characters. The goal is to extract these instructions and calculate the product of the two numbers in each instruction.
A correct multiplication instruction is formatted as follows: mul(X,Y)
, where X
and Y
are 1
to 3
digit integers. The goal is to extract these instructions and calculate the sum of the products of the two numbers in each instruction.
For the example:
xmul(2,4)%&mul[3,7]!@^do_not_mul(5,5)+mul(32,64]then(mul(11,8)mul(8,5))
The correct instructions are mul(2,4)
, mul(5,5)
, mul(11,8)
, and mul(8,5)
. The sum of the products of the two numbers in each instruction is 8 + 25 + 88 + 40 = 161
.
Today, I will use a consise Python code to solve this problem using the re
module for regular expressions.
import re
from pathlib import Path
def part_one(input: str | Path):
if Path(input).exists():
with open(input, "r") as file:
input = file.read()
def extract_matches(input):
return map(multiply, re.findall(r"mul\((\d{1,3}),(\d{1,3})\)", input))
def multiply(tup: tuple[str, str]) -> int:
return int(tup[0]) * int(tup[1])
return sum(extract_matches(input))
verify_answer(part_one, example_input, example_answer_one)
✔️ That's right! The answer is 161.
%time
"../inputs/day_03.txt") part_one(
CPU times: user 2 μs, sys: 0 ns, total: 2 μs
Wall time: 8.82 μs
196826776
That’s the right answer! You are one gold star ⭐ closer to finding the Chief Historian.
Part 2: Extracting multiplication instructions with a twist
This time, we are also looking out for do()
and don't()
instructions that modify the next valid multiplication instruction. If we encounter a do()
instruction, we should multiply the two numbers in the next valid multiplication instruction. If we encounter a don't()
instruction, we should skip the next valid multiplication instruction. At the start of the seuence, the multiplications are enabled.
For example:
xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))
We have the following sequence:
.mul(2,4)...........don't().mul(5,5)............mul(11,8).do().mul(8,5).
Which means, that:
- we multiply
2*4 = 8
,
- we encounter
don't()
, so we skip the next multiplicationsmul(5,5)
andmul(11,8)
,
- until we again encounter
do()
, so we multiply8*5 = 40
.
The sum of the products of the two numbers in each instruction is 8 + 40 = 48
.
= """xmul(2,4)&mul[3,7]!^don't()_mul(5,5)+mul(32,64](mul(11,8)undo()?mul(8,5))"""
example_input_two
= 48 example_answer_two
def part_two(input: str | Path):
if Path(input).exists():
with open(input, "r") as file:
input = file.read()
def extract_matches(input):
return re.findall(
r"(do\(\))|mul\((\d{1,3}),(\d{1,3})\)|(don't\(\))", input
)
def multiply(tup: tuple[str, str]) -> int:
return int(tup[0]) * int(tup[1])
= True
go sum = 0
for match in extract_matches(input):
if match[0]:
= True
go elif match[3]:
= False
go else:
if go:
sum += multiply(match[1:3])
return sum
Now, let’s test the code on the examples.
verify_answer(part_two, example_input_two, example_answer_two)
✔️ That's right! The answer is 48.
verify_answer(part_two, example_input, example_answer_one)
✔️ That's right! The answer is 161.
Since both test cases work, I will run the code on the actual input.
%time
"../inputs/day_03.txt") part_two(
CPU times: user 0 ns, sys: 0 ns, total: 0 ns
Wall time: 1.91 μs
106780429
That’s the right answer! You are one gold star ⭐ closer to finding the Chief Historian.
Bonus Bash solutions
I wanted to see how the same problems can be solved in Bash. The full script is available in the Day03.sh
file in the repository. Here’s a brief overview of the Bash solutions:
In part 1, I extract only the multiplication instructions and calculate the sum of the products.
part_one() {
local input="${1:-/dev/stdin}"
cat "${input}" |
grep -o 'mul([0-9]\{1,3\},*[0-9]\{1,3\})' |
sed 's/mul(//g;s/)//g;s/,/*/g' |
xargs |
sed 's/ /+/g' |
bc
}
I use grep
to extract the multiplication instructions, sed
to format them as num1*num2
, xargs
to join them into a single line, and bc
to evaluate the sum.
The second part is a bit more complex, as it requires handling the do()
and don't()
instructions.
part_two() {
local input="${1:-/dev/stdin}"
cat "${input}" |
grep -o "mul([0-9]\{1,3\},*[0-9]\{1,3\})\|don't()\|do()" |
sed 's/mul(//g;s/)//g;s/,/*/g;s/(//g' |
awk '
BEGIN { go = 1 }
{
if ($1 == "don'\''t") {
go = 0
} else if ($1 == "do") {
go = 1
} else if (go) {
print $1
}
}' |
xargs |
sed 's/ /+/g' |
bc
}
Here, on top of grep
and sed
to extract the instructions, I use awk
to handle the do()
and don't()
instructions.
In the script I also added functions to check the answers and print them with cowsay
for some extra flavor. Like this:
_
|_) _. ._ _|_ /|
| (_| | |_ |
_______________________________
< Part 1 - example test passed! >
-------------------------------
\
\
.--.
|o_o |
|:_/ |
// \ \
(| | )
/'\_ _/`\
\___)=(___/
_____________________________
< Part 1 answer is: 196826776 >
-----------------------------
\ ^__^
\ (oo)\_______
(__)\ )\/\
||----w |
|| ||