from misc.helper import verify_answer
= """
tiny_example ..X...
.SAMX.
.A..A.
XMAS.S
.X....
"""
= 4
tiny_example_answer
= """
my_example XMXMMX
ASMMMM
SMAAAA
AMSMMS
MSAMXM
XMASMS
"""
= 8
my_example_answer
= """
example_input MMMSXXMASM
MSAMXMSMSA
AMXSXMAAMM
MSAMASMSMX
XMASAMXAMM
XXAMMXXAMA
SMSMSASXSS
SAXAMASAAA
MAMMMXMMMM
MXMXAXMASX
"""
= 18 example_answer_p1
Day 4: Ceres Search
Today, we’re searching the Ceres monitoring station and we’re helping adorable little Elf with their word search.
Do check out the full details of the task on the official Day 4 of Advent of Code 2024 website – the puzzles come with a cute story.
And as per tradition, let’s set the set the scene with ChatGPT illustration of the day.
Part 1: Searching for XMAS
The challenge is to find occurrences of the word XMAS
in a given letter grid. The twist is that the word can appear in any direction: horizontally, vertically, or diagonally, and even in reverse order. This adds a layer of complexity as we must account for all possible orientations.
Here’s an example of a grid with all letters not spelling XMAS
removed:
Our task is to count how many times XMAS
appears in a provided grid, considering all the possible directions.
For example, in the grid below:
The word XMAS
appears 7 times.
My approach
For this one, I want to solve it in a single pass through the grid. Here’s my plan:
I’ll go through each character in the grid.
If I encounter an
X
or anS
, I’ll add it to a dictionary to track potential matches. For each match, I’ll:- Note the directions to explore (
right
,diagonal left
,diagonal right
, anddown
).
- Keep track of whether I’m spelling the word forward or backward.
- Record the next letter I’m expecting to find.
- Note the directions to explore (
For each position, I’ll check:
- If it’s already being tracked in the dictionary.
- If so, I’ll see if the letter matches what I’m expecting. If it does, I’ll update the dictionary with the next position to check.
- If it’s already being tracked in the dictionary.
By doing this, I ensure:
- Each position is only processed once.
- I avoid any redundant checks or unnecessary traversals.
- Each position is only processed once.
This approach keeps things efficient and clean while letting me track progress dynamically.
from pathlib import Path
def part_one(input: str) -> int:
= "XMAS"
xmas
if Path(input).exists():
with open(input) as f:
input = f.read()
= {}
check_position = 0
count_words = [(0, 1), (1, -1), (1, 0), (1, 1)]
directions
for i, line in enumerate(input.strip().splitlines()):
for j, char in enumerate(line):
= check_position.pop((i, j), [])
to_be_checked
for schar, coord, dir in to_be_checked:
if schar == char:
if char in "XS":
+= 1
count_words else:
= (i + coord[0], j + coord[1])
next_pos = xmas[xmas.find(char) + dir]
next_char
check_position.setdefault(next_pos, []).append(dir)
(next_char, coord,
)
if char in "XS":
= 1 if char == "X" else -1
direction_shift for dx, dy in directions:
= (i + dx, j + dy)
next_pos = xmas[xmas.find(char) + direction_shift]
next_char
check_position.setdefault(next_pos, []).append(
(next_char, (dx, dy), direction_shift)
)
= {
check_position for key, val in check_position.items() if key[0] > i
key: val
}
return count_words
verify_answer(part_one, tiny_example, tiny_example_answer)
✔️ That's right! The answer is 4.
verify_answer(part_one, my_example, my_example_answer)
✔️ That's right! The answer is 8.
verify_answer(part_one, example_input, example_answer_p1)
✔️ That's right! The answer is 18.
%time
"../inputs/day_04.txt") part_one(
CPU times: user 1 μs, sys: 0 ns, total: 1 μs
Wall time: 4.05 μs
2560
That’s the right answer! You are one gold star ⭐ closer to finding the Chief Historian.
Part 2: Searching for X-MAS
In this task, we need to find the shape X
created by words MAS
, like this:
For example, in the grid below there are 5 X-MAS
shapes:
My approach
Instead of trying to traverse the input in one pass, I’ll keep things simple by reading it to a matrix and checking each 3x3 submatrix for the X-MAS
shape. Here’s my plan:
I’ll first parse the grid into a matrix format for easy access.
Then, I’ll loop through all possible 3x3 submatrices in the grid and check each submatrix for the
X-MAS
shape:For a valid shape, the following conditions must hold:
- The center of the submatrix must be
A
.
- The upper-right and lower-left corners must be either:
M
andS
, or
S
andM
.
- Similarly, the upper-left and lower-right corners must be either:
M
andS
, or
S
andM
.
- The center of the submatrix must be
Finally, if all these conditions are satisfied, I’ll increment a counter.
This approach allows me to focus on identifying the pattern in localized regions of the grid, keeping the logic straightforward and easy to implement.
= 9
example_answer_p2
= """
small_x_mas_example MASMSSM
MAMAAAS
MSSSSMM
MAMMMSM
MAAAAAM
SMSSSSS
"""
= 5 small_x_mas_example_answer
def read_input_to_matrix(input: str) -> list:
if Path(input).exists():
with open(input) as f:
input = f.read()
= []
output
for line in input.strip().splitlines():
for c in line.strip()])
output.append([c
return output
def part_two(input: str) -> int:
= read_input_to_matrix(input)
mat
= 0
count_mats
for i in range(len(mat[0]) - 2):
for j in range(len(mat) - 2):
= mat[j + 1][i + 1]
center = mat[j][i], mat[j + 2][i + 2]
top_left, bottom_right = mat[j][i + 2], mat[j + 2][i]
top_right, bottom_left
= center == "A"
cond_center = (top_left == "M" and bottom_right == "S") or (
cond_lr == "S" and bottom_right == "M"
top_left
)= (top_right == "M" and bottom_left == "S") or (
cond_rl == "S" and bottom_left == "M"
top_right
)
if cond_center and cond_lr and cond_rl:
+= 1
count_mats
return count_mats
verify_answer(part_two, small_x_mas_example, small_x_mas_example_answer)
✔️ That's right! The answer is 5.
verify_answer(part_two, example_input, example_answer_p2)
✔️ That's right! The answer is 9.
%time
"../inputs/day_04.txt") part_two(
CPU times: user 1e+03 ns, sys: 0 ns, total: 1e+03 ns
Wall time: 2.15 μs
1910
That’s the right answer! You are one gold star ⭐ closer to finding the Chief Historian.