Advent of Code 2023 Day 3 Got it! Geez that was hard. This was my winning algo without any cleanup after the first successful submission:

import fs from 'fs'
 
// fs.readFile("testinput.txt", 'utf-8', (err, data) => {
fs.readFile("puzzleinput.txt", 'utf-8', (err, data) => {
 
  const lines = data.split('\n')
  const lineLength = lines.length
  const compass = [-lineLength - 1, -lineLength, -lineLength + 1, -1, +1, lineLength -1, lineLength, lineLength+1]
  let result = 0
  let gearRatios = 0
 
    data = data.replace(/\s+/g, '')
 
    const parts = Array.from(data.matchAll(/\d+/g))
    const symbols = Array.from(data.matchAll(/[^0-9.]/g))
    const digitIndexes: Map<number, string> = new Map() // map of indexes corresponding to the value of an entire part number with the convention `${index-of-first-char}-${value}`
    const partSet: Map<string, number> = new Map() // a set of every unique part number, which can be split apart to get a value
    const gearMap: Map<number, Set<string>> = new Map() // a map of every gear to the number of all parts it is adjacent to
 
    function createPartId(partIndex: number, partValue: string){
      return `${partIndex}-${partValue}`
    }
 
 
    // Iterate through every part match
    // For each digit it has, record it in the digitIndexes map with it's unique partId
    for (let i = 0; i < parts.length; i++){
      const part = parts[i]
 
      for (let offset = 0;  offset <part[0].length; offset++){
        const newIndex = part.index + offset
        const partId = createPartId(part.index, part[0])
        digitIndexes.set(newIndex, partId)
      }
    }
 
    // Iterate through every symbol match
    for (let s = 0; s < symbols.length; s++){
 
      const symbol= symbols[s]
      // For every symbol match, iterate through all 8 potential neighboring squares
      for (const direction of compass){
        const searchIndex = symbol.index + direction
 
        if (searchIndex < 0 && searchIndex >= data.length) continue // search index is out of bounds
        
        if (digitIndexes.has(searchIndex)){
          // We have a match! We have captured a relationship between a symbol and the digit of a part
          const partId = digitIndexes.get(searchIndex)!
          partSet.set(partId, parseInt(partId.split("-")[1]))
 
          if (symbol[0] === "*"){
            gearMap.set(symbol.index,
              (gearMap.get(symbol.index) ?? new Set()).add(partId)
            )
          }
        }
      }
    }
 
    console.log({partSet})
    console.log("Parts number answer 1 is", [...partSet].reduce((acc, current) => {
      const value = current[1]
      return acc + value
    }, 0))
 
    console.log({gearMap})
    
    for (let [_, value] of gearMap){
      if (value.size === 2){
        let ratio = 1
        for (const partId of value){
          ratio *= partSet.get(partId) ?? 1
        }
        gearRatios += ratio
      }
    }
 
    console.log(gearRatios)
  
})
 
/// Gotchas
// I had the indexes I was iterating through and then I had these regex indexes, and the combination really messed me up

Let me clean it up a bit:

Things I learned

1) A for...of loop can be used to iterate through a Set, but not for...in

const someSet = new Set(1, 2, 3)
for (const val of someSet) console.log(val)
// 1
// 2
// 3
for (const in of someSet) console.log(val)
// undefined <- No error though 🤔

2) for...of also works for maps

const someMap = new Map(["Shrek", 4], ["Shrek2", 2.4], ["Shrek3", 1])
for (const val of someMap) console.log(val)
// ["Shrek", 4]
// ["Shrek2", 2.4]
// ["Shrek3", 1]

3) Variable naming really matters The second biggest issue I ran into here was that I had two index values that pertained to the index a regex match found in the larger string, and I also had two index values that pertained to which match I was on as I looped through those matches.

This was really confusing and hard to catch because they either accidentally shadowed each other by both being named partIndex OR their names contained no meaningfully different information to me, like partIndex and partMatchIndex.

Maybe what I learned was that index isn’t a very variable name? I’m still not totally sure what names I should use in some of this algo.

4) Data structures containing other data structures are powerful but dangerous The hardest part about this particular challenge seems to be that it forces you to either

  1. Nest data structures within datastrctures or
  2. Reference one datastructure from another

And to be totally honest, I had a lot of trouble figuring out how to design these relationships so they did what I wanted. I did a lot of flailing around and changing data structures that aren’t present here.

If I’m honest, I also made some mistakes reading the question, which had devastating consequences to my data structure design. But I think that the question itself had some major hints as to how to design the data structures and their relationships with one another.