Advent of Code 2023: Day 3, Part 2
I am doing Advent of Code 2023 in JavaScript/Node.js after using only Typescript for the past year just to remind myself of the differences. JSDoc has been really nice for bridging some of the gaps in my workflow that I had gotten used to with Typescript.
You can see all of my solutions at github.com/MildlyAggressiveFolk/AoC
My Solution
Part 2 of Day 3's challenge was to find all of the asterisks which had exactly 2 neighboring sets of numbers in any direction.
I began by finding all of the coordinates of the sets of numbers and the asterisks, then I looped through the asterisks returned coordinates of their neighbors. I cross referenced with the coordinates of the numbers, and if the neighbor was a number, I added its coordinates to a set (set chosen to avoid duplicates).
If the set had 2 entries, I knew that I had found one of the "gear ratios" so I grabbed the values, multiplied them together, and added them to an array, which I finally summed, giving me my answer.
const fs = require("fs")
const input = fs.readFileSync("./input.txt", "utf8")
const inputArr = input.split("\n")
const coords = getCoords(inputArr)
console.log(findAndSumGearRatios(...coords))
/**
* Takes array of gear coordinates and returns sum of gear ratios
* @param {[number, number][]} gearCoords
* @returns {number}
*/
function findAndSumGearRatios(numCoords, gearCoords) {
const gearRatios = []
for (let gearCoord of gearCoords) {
const neighborSet = new Set()
const [row, col] = gearCoord
const neighbors = getNeighbors(row, col)
for (let neighbor of neighbors) {
const numEntry = getNumEntry(...neighbor, numCoords)
if (numEntry) {
neighborSet.add(numEntry)
}
}
if (neighborSet.size === 2) {
const vals = []
neighborSet.forEach((numCoord) => {
vals.push(getValueFromNumCoord(numCoord))
})
gearRatios.push(vals.reduce((a, b) => a * b))
}
}
return gearRatios.reduce((a, b) => a + b)
}
function getValueFromNumCoord(numCoord) {
const [row, colArr] = numCoord
const val = []
for (let col of colArr) {
val.push(inputArr[row][col])
}
return Number(val.join(""))
}
/**
* Takes row and column and returns number entry if it exists
* @param {number} row
* @param {number} col
* @returns {[number, number[]] | false} numCoord | false
*/
function getNumEntry(row, col, numCoords) {
const numCoord = numCoords.find((entry) => entry[0] === row && entry[1].includes(col))
if (numCoord) return numCoord
return false
}
/**
* Returns all neighbors of a given coordinate
* @param {number} row
* @param {number} col
* @returns {[number, number][]}
*/
function getNeighbors(row, col) {
const neighbors = []
for (let i = -1; i <= 1; i++) {
for (let j = -1; j <= 1; j++) {
if (i !== 0 || j !== 0) neighbors.push([row + i, col + j])
}
}
return neighbors
}
/**
* Returns coordinates of all numbers and gears
* @param {string[]} lines
* @returns {[[number, number[]][], [number, number][]]} [numCoords, gearCoords]
*/
function getCoords(lines) {
const numCoords = []
const gearCoords = []
for (let [index, line] of lines.entries()) {
for (let i = 0; i < line.length; i++) {
if (isDigit(line[i])) {
const number = [i]
for (let j = i + 1; j < line.length; j++) {
if (isDigit(line[j])) {
number.push(j)
if (j === line.length - 1) {
i = j
}
} else {
i = j
break
}
}
numCoords.push([index, number])
}
if (line[i].charCodeAt(0) === 42) {
gearCoords.push([index, i])
}
}
}
return [numCoords, gearCoords]
}
/**
*
* @param {string} char
* @returns boolean
*/
function isDigit(char) {
return char.charCodeAt(0) >= 48 && char.charCodeAt(0) <= 57
}