248 lines
6.6 KiB
Markdown
248 lines
6.6 KiB
Markdown
# Advent of Code 2024: Day 4
|
|
|
|
## Problem 1:
|
|
|
|
```
|
|
--- Day 4: Ceres Search ---
|
|
"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 .:
|
|
|
|
|
|
..X...
|
|
.SAMX.
|
|
.A..A.
|
|
XMAS.S
|
|
.X....
|
|
|
|
The actual word search will be full of letters instead. For example:
|
|
|
|
MMMSXXMASM
|
|
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 .:
|
|
|
|
....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?
|
|
```
|
|
|
|
## Solution 1:
|
|
|
|
Warning. My solution for part one is ugly. REAL UGLY!
|
|
|
|
```javascript
|
|
#!/usr/bin/env node
|
|
|
|
|
|
var input = require('fs').readFileSync(process.argv[2]||'input.txt','utf8').split('\n').filter(i=>i);
|
|
//Make many versions of the data in different orientations
|
|
//All modes are an array of buffers. We will turn them into arrays of strings soon.
|
|
var mode0 = input.map(i=>Buffer.from(i));
|
|
//Width and height happen to be equal.. 140
|
|
var width = mode0[0].length;
|
|
var height = mode0.length;
|
|
//Transpose mode1
|
|
var mode1 = [];
|
|
for(var i=0;i<width;++i) { //Allocate
|
|
mode1.push(Buffer.alloc(height));
|
|
}
|
|
for(var i=0;i<height;++i) {
|
|
for(var j=0;j<width;++j) {
|
|
mode1[j][i]=mode0[i][j];
|
|
}
|
|
}
|
|
//Diaganal mode2
|
|
var mode2 = [];
|
|
for(var i=1;i<=width;++i) { //Allocate half
|
|
mode2.push(Buffer.alloc(i));
|
|
}
|
|
for(var i=width-1;i>0;--i) { //Allocate second half
|
|
mode2.push(Buffer.alloc(i));
|
|
}
|
|
var ri=0;
|
|
var rj=0;
|
|
var wi=0;
|
|
var wj=0;
|
|
try {
|
|
while(true) {
|
|
mode2[wi][wj]=mode0[ri][rj];
|
|
--ri;
|
|
++rj;
|
|
++wj;
|
|
if(mode2[wi].length==wj) { //This bit is simple
|
|
++wi;
|
|
wj=0;
|
|
}
|
|
if(ri<0) {
|
|
ri=rj
|
|
rj=0;
|
|
}
|
|
if(rj>=mode0.length) {
|
|
++ri; //Reverse
|
|
--rj; //Reverse
|
|
++ri; //Go down one
|
|
//Swap ri and rj
|
|
var temp=ri;
|
|
ri=rj;
|
|
rj=temp;
|
|
}
|
|
if(ri>=mode0.length) {
|
|
rj=(ri-(mode0.length-1));
|
|
ri=mode0.length-1;
|
|
}
|
|
}
|
|
} catch(e) {
|
|
//console.log('error',{ri,rj,wi,wj});
|
|
//Let it run. When we have an error we did the job
|
|
}
|
|
|
|
//Diagonal mode3
|
|
var mode3 = [];
|
|
for(var i=1;i<=width;++i) { //Allocate half
|
|
mode3.push(Buffer.alloc(i));
|
|
}
|
|
for(var i=width-1;i>0;--i) { //Allocate second half
|
|
mode3.push(Buffer.alloc(i));
|
|
}
|
|
var ri=0;
|
|
var rj=width-1;
|
|
var wi=0;
|
|
var wj=0;
|
|
try {
|
|
while(true) {
|
|
//console.log({ri,rj});
|
|
mode3[wi][wj]=mode0[ri][rj];
|
|
++ri;
|
|
++rj;
|
|
++wj;
|
|
if(mode3[wi].length==wj) { //This bit is simple
|
|
++wi;
|
|
wj=0;
|
|
}
|
|
if(rj>=width) {
|
|
--ri;//Reverse
|
|
--rj;//Reverse
|
|
++ri;//Get on new diaganol
|
|
rj=width-ri-1; // Go to other side of diaganol
|
|
ri=0; // Go to other side of diaganol
|
|
}
|
|
if(rj<0) {
|
|
ri=-rj;
|
|
rj=0;
|
|
}
|
|
if(ri>=width) {
|
|
--ri;//Reverse
|
|
--rj;//Reverse
|
|
--rj;//Get on new diaganol
|
|
ri=width-1-rj;
|
|
rj=0;
|
|
}
|
|
if(ri<0 || ri>=width || rj<0 || rj>=width) throw 'Out of bounds';
|
|
}
|
|
} catch(e) {
|
|
//console.log('Error mode3:',{ri,rj,wi,wj});
|
|
//Continue, it is filled;
|
|
}
|
|
|
|
|
|
var modes = [mode0,mode1,mode2,mode3].map(i=>i.map(i=>i.toString())); //Convert all into strings
|
|
var regex1 = /XMAS/g
|
|
var regex2 = /SAMX/g
|
|
|
|
var sum = modes.reduce((acc1,grid)=>acc1+grid.reduce((acc2,line)=>{
|
|
var match1 = line.match(regex1);
|
|
var match2 = line.match(regex2);
|
|
return acc2 + (match1?match1.length:0) + (match2?match2.length:0);
|
|
},0),0);
|
|
|
|
console.log('Result:',sum);
|
|
```
|
|
|
|
## Problem 2
|
|
|
|
```
|
|
--- Part Two ---
|
|
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:
|
|
|
|
M.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-MASes have been kept instead:
|
|
|
|
.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?
|
|
|
|
```
|
|
|
|
## Solution 2:
|
|
|
|
```javascript
|
|
#!/usr/bin/env node
|
|
|
|
|
|
var input = require('fs').readFileSync(process.argv[2]||'input.txt','utf8').split('\n').filter(i=>i);
|
|
|
|
var width = input.length;
|
|
|
|
var sum=0;
|
|
for(var i=1;i<width-1;++i) {
|
|
for(var j=1;j<width-1;++j) {
|
|
var isXMAS = input[i][j]=='A' && ((input[i-1][j-1]=='M'&&input[i+1][j+1]=='S') || (input[i-1][j-1]=='S'&&input[i+1][j+1]=='M')) && ((input[i-1][j+1]=='M'&&input[i+1][j-1]=='S') || (input[i-1][j+1]=='S'&&input[i+1][j-1]=='M'))
|
|
if(isXMAS) ++sum;
|
|
}
|
|
}
|
|
|
|
console.log('Result:',sum);
|
|
|
|
```
|
|
|
|
## Notes:
|
|
|
|
For part2.js, it would likely run faster if we did convert to buffers instead of strings like we did for part of part1.js. We would just have to compare against the charCodes instead. But those could be bound to variables, and the bool logic later would actually be cleaner-ish.
|
|
|
|
```javascript
|
|
const M = 'M'.charCodeAt(0);
|
|
.
|
|
.
|
|
.
|
|
input[i-1][j-1]==M
|
|
```
|
|
|
|
For part1 if we were doing it in C the most typical approach would be easier. For each mode we would just allocate the same size of memory as mode0 in one chunk. Then for the diagonals we could just use a zig-zag path like is common in a lot of image and video encoding. Because we are going to regex in both directions being consistent with the text direction doesn't matter. Then when we build our char** calculating the offsets is as easy as calculating the alloc sizes we did in the javascript version. One could argue that we could have borrowed some of those approaches we would have used for C in javascript, and that would be correct. In that case the code may have ended up cleaner. Likely you would use sub-buffers from a larger one instead of allocating many of them in an array. |