Advent of Code 2024 Day 4
Part 1
X
marks the (hundreds of?) spots
I'm surprised there hasn't been a literal word search puzzle like this until now.
It seems daunting, but my strategy will be:
Find the index of each X in the grid For each X Check the next three letters in a straight path in each of the eight directions If the path ends up spelling XMAS Add one to a running total
Checking this strategy on the example leads me to believe it's a winning approach.
Now for the exciting part: coding this whole thing from scratch!
Find the index of each X in a grid...eventually
First, I have to parse the input into a 2D array of characters:
let grid = input.split('\n').map(line => line.split(''))
An obstacle I often face in grid puzzles is accounting for out-of-bounds indices.
If I start from a border cell - or a cell close to the border - and walk so far in a direction toward the edge, I'm eventually going to encounter a row or column that is out-of-bounds.
I have two strategies for dealing with this:
- Add checks to my conditions for non-existent rows or columns
- Pad the grid with enough rows and columns that there is no risk of going out-of-bounds
For this challenge, I opt for #2.
Padding my grid with a 3-cell thick border looks like this:
grid = grid.map(line => ['.','.','.',...line,'.','.','.']) grid = [ new Array(grid[0].length).fill('.'), new Array(grid[0].length).fill('.'), new Array(grid[0].length).fill('.'), ...grid, new Array(grid[0].length).fill('.'), new Array(grid[0].length).fill('.'), new Array(grid[0].length).fill('.') ]
The example grid now looks like this:
................ ................ ................ ...MMMSXXMASM... ...MSAMXMSMSA... ...AMXSXMAAMM... ...MSAMASMSMX... ...XMASAMXAMM... ...XXAMMXXAMA... ...SMSMSASXSS... ...SAXAMASAAA... ...MAMMMXMMMM... ...MXMXAXMASX... ................ ................ ................
Now I'm ready to catalogue the coordinates of each X
in the padded grid:
let Xs = [] for (let row = 0; row < grid.length; row++) { for (let col = 0; col < grid[0].length; col++) { if (grid[row][col] == "X") { Xs.push([row, col]) } } }
Success: it found all 19 of the Xs in the example grid!
Walking three steps in eight directions from each X
All eight relative coordinates are coded as an 8-element array:
let dirs = [ [-1,-1], [-1,0], [-1,1], [0,-1], [0,1], [1,-1], [1,0], [1,1] ]
Now for the main algorithm:
For each X For each direction Create an array that starts with X Do 3 times Move one cell in this direction Add the value of that cell to the array Check whether the concatenation of all four values is "XMAS" If it is, increment a tally
And in JavaScript:
Xs.reduce((total, coord) => { dirs.forEach((dir) => { let [row, col] = coord; let [y, x] = dir; let word = ["X"]; for (let i = 0; i < 3; i++) { row += y; col += x; word.push(grid[row][col]); } if (word.join("") == "XMAS") { total++; } }); return total; }, 0);
It generates the correct answer for the example input!
What will happen when I run it on my puzzle input??!!
I get a number: a couple thousand 'XMAS's
Is it the correct answer?
IT IS!!!
Woohooo!!!
Can't wait to see what Part 2 has up its sleeve...
Part 2
Ohhh my. This got a bit more complicated. But doable!
In Part 1 I was looking for Xs.
Now, I am looking for Ms.
In Part 1 I recorded letters in a straight line to make a word.
Now, I am looking for four configurations of a 5-cell phrase:
M S M M S M S S A A A A M S S S S M M M
A single M
could be part of several X-MAS's.
By checking every M, I'm likely to encounter several multiple times.
I'll need to build a Set()
of stringified coordinates for each match. That way, I only account for an X-MAS instance once.
A sudden - brilliant! - idea
I'm not going to check every M
.
I'll check every A
.
And I'll check the four cells diagonally adjacent, in clockwise order.
X-MAS matches will fit one of these four patterns:
MSSM MMSS SMMS SSMM
`
Phew! This will be a lot less tedious than my original idea.
And I should be able to repurpose most of my Part 1 code!
Copy-Paste-Tweak
Finding all A
s in the grid:
js
let As = [];
for (let row = 0; row < grid.length; row++) {
for (let col = 0; col < grid[0].length; col++) {
if (grid[row][col] == "A") {
As.push([row, col]);
}
}
}
Establishing the order of relative coordinates to check:
js
let Adirs = [
[-1, -1],
[-1, 1],
[1, 1],
[1, -1],
];
Adding up the tally of matches:
js
let part2 = As.reduce((total, coord) => {
let clockwise = Adirs.map((dir) => {
let [row, col] = coord;
let [y, x] = dir;
return grid[row + y][col + x];
});
if (["MSSM", "MMSS", "SMMS", "SSMM"].includes(clockwise.join(""))) {
total++;
}
return total;
}, 0);
It generates the correct answer for the example input!
Now to check on my puzzle input...
Indeed!!! The correct answer!!!
I'm so glad it struck me to use the A
s instead of the M
s.
Saved me hours of troubleshooting headaches, I'm sure.
That was another fun and accessible puzzle!
I wonder what Day 5 has in store.
Top comments (1)
Smart, but not the way I did. On my side I used vectors to adapt to any word for the first part. i am planning the second part actually, using cross vectors.
pastebin.com/5k4yCdFK
Enjoy the part one, for the part two all I have to do is to restrict vectors, only X, record all them when matching and guessing wich ones cross in the middle. I will divide the sum by two, job done.
The advantage is the algorithm I do use is not restricted to a single word, it adapts to any word, which is to me the real purpose of a such challenge ;).