"Looks like the Chief's not here. Next!" One of The Historians pulls out a device and pushes the only button on it. After a brief flash, you recognize the interior of the Ceres monitoring station!
As the search for the Chief continues, a small Elf who lives on the station tugs on your shirt; she'd like to know if you could help her with her word search (your puzzle input). She only has to find one word: XMAS
.
This word search allows words to be horizontal, vertical, diagonal, written backwards, or even overlapping other words. It's a little unusual, though, as you don't merely need to find one instance of XMAS
- you need to find all of them. Here are a few ways XMAS
might appear, where irrelevant characters have been replaced with .
:
12345..X...
.SAMX.
.A..A.
XMAS.S
.X....
The actual word search will be full of letters instead. For example:
12345678910MMMSXXMASM
MSAMXMSMSA
AMXSXMAAMM
MSAMASMSMX
XMASAMXAMM
XXAMMXXAMA
SMSMSASXSS
SAXAMASAAA
MAMMMXMMMM
MXMXAXMASX
In this word search, XMAS
occurs a total of 18
times; here's the same word search again, but where letters not involved in any XMAS
have been replaced with .
:
12345678910....XXMAS.
.SAMXMS...
...S..A...
..A.A.MS.X
XMASAMX.MM
X.....XA.A
S.S.S.S.SS
.A.A.A.A.A
..M.M.M.MM
.X.X.XMASX
Take a look at the little Elf's word search. How many times does XMAS
appear?
I ended up creating an array of all positions within the matrix, then brute-force checking each position and the surrounding coordinates for the matching characters.
This was very straightforward to implement, if a bit tedious.
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283const parser = z.array(z.string().transform((value) => value.split("")));
const values = parser.parse(input.split("\n"));
const positions: Array<[number, number]> = [];
values.forEach((value, i) => {
value.forEach((_, j) => {
positions.push([i, j]);
});
});
function matchLeftRight(x: number, y: number) {
return (
values[x]?.[y] === "X" &&
values[x + 1]?.[y] === "M" &&
values[x + 2]?.[y] === "A" &&
values[x + 3]?.[y] === "S"
);
}
function matchRightLeft(x: number, y: number) {
return (
values[x]?.[y] === "X" &&
values[x - 1]?.[y] === "M" &&
values[x - 2]?.[y] === "A" &&
values[x - 3]?.[y] === "S"
);
}
function matchUpDown(x: number, y: number) {
return (
values[x]?.[y] === "X" &&
values[x]?.[y + 1] === "M" &&
values[x]?.[y + 2] === "A" &&
values[x]?.[y + 3] === "S"
);
}
function matchDownUp(x: number, y: number) {
return (
values[x]?.[y] === "X" &&
values[x]?.[y - 1] === "M" &&
values[x]?.[y - 2] === "A" &&
values[x]?.[y - 3] === "S"
);
}
function matchUpLeftDownRight(x: number, y: number) {
return (
values[x]?.[y] === "X" &&
values[x + 1]?.[y + 1] === "M" &&
values[x + 2]?.[y + 2] === "A" &&
values[x + 3]?.[y + 3] === "S"
);
}
function matchUpRightDownLeft(x: number, y: number) {
return (
values[x]?.[y] === "X" &&
values[x - 1]?.[y + 1] === "M" &&
values[x - 2]?.[y + 2] === "A" &&
values[x - 3]?.[y + 3] === "S"
);
}
function matchDownRightUpLeft(x: number, y: number) {
return (
values[x]?.[y] === "X" &&
values[x - 1]?.[y - 1] === "M" &&
values[x - 2]?.[y - 2] === "A" &&
values[x - 3]?.[y - 3] === "S"
);
}
function matchDownLeftUpRight(x: number, y: number) {
return (
values[x]?.[y] === "X" &&
values[x + 1]?.[y - 1] === "M" &&
values[x + 2]?.[y - 2] === "A" &&
values[x + 3]?.[y - 3] === "S"
);
}
The Elf looks quizzically at you. Did you misunderstand the assignment?
Looking for the instructions, you flip over the word search to find that this isn't actually an XMAS
puzzle; it's an X-MAS
puzzle in which you're supposed to find two MAS
in the shape of an X
. One way to achieve that is like this:
123M.S
.A.
M.S
Irrelevant characters have again been replaced with .
in the above diagram. Within the X
, each MAS
can be written forwards or backwards.
Here's the same example from before, but this time all of the X-MAS
es have been kept instead:
12345678910.M.S......
..A..MSMS.
.M.S.MAA..
..A.ASMSM.
.M.S.M....
..........
S.S.S.S.S.
.A.A.A.A..
M.M.M.M.M.
..........
In this example, an X-MAS
appears 9
times.
Flip the word search from the instructions back over to the word search side and try again. How many times does an X-MAS
appear?
Very similar to Part 1, but adjust the coordinates we're checking and the characters we're checking against.
This was actually a bit simpler becuase I didn't need to consider diagonals.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051const parser = z.array(z.string().transform((value) => value.split("")));
const values = parser.parse(input.split("\n"));
const positions: Array<[number, number]> = [];
values.forEach((value, i) => {
value.forEach((_, j) => {
positions.push([i, j]);
});
});
function matchLeftRight(x: number, y: number) {
return (
values[x]?.[y] === "A" &&
values[x - 1]?.[y - 1] === "M" &&
values[x + 1]?.[y - 1] === "S" &&
values[x - 1]?.[y + 1] === "M" &&
values[x + 1]?.[y + 1] === "S"
);
}
function matchRightLeft(x: number, y: number) {
return (
values[x]?.[y] === "A" &&
values[x - 1]?.[y - 1] === "S" &&
values[x + 1]?.[y - 1] === "M" &&
values[x - 1]?.[y + 1] === "S" &&
values[x + 1]?.[y + 1] === "M"
);
}
function matchUpDown(x: number, y: number) {
return (
values[x]?.[y] === "A" &&
values[x - 1]?.[y - 1] === "M" &&
values[x + 1]?.[y - 1] === "M" &&
values[x - 1]?.[y + 1] === "S" &&
values[x + 1]?.[y + 1] === "S"
);
}
function matchDownUp(x: number, y: number) {
return (
values[x]?.[y] === "A" &&
values[x - 1]?.[y - 1] === "S" &&
values[x + 1]?.[y - 1] === "S" &&
values[x - 1]?.[y + 1] === "M" &&
values[x + 1]?.[y + 1] === "M"
);
}
Part 1 Time | Part 1 Rank | Part 2 Time | Part 2 Rank |
---|---|---|---|
00:16:41 | 3,254 | 00:20:01 | 1,717 |
I considered two options for this:
- Create new strings that were forwards, backwards, up, down, and diagonals, then do simple searches across those strings for matching text
- Iterate over coordinates within the string matrix and check surrounding coordinates
After a few minutes of trying the first idea, I gave up and went for the second one, which was very straightforward to implement. Should've just done that from the start, but oh well.