Advent of Code Day Two Paired with: Ngina Code we ended up with after 50 minutes Experience: Reminded that my string manipulation abilities in TS have room for improvement. Regex could have been helpful.

Typescript surprised us with something, which is that the return type of a function wasn’t enforced, which is exactly what I thought it should do.

import fs from 'fs'
 
// Input is a list of game IDs with a list of the cubes that are available
// a list of game ids with draws of tiles, that may or may not be possible
// What is the sum of the IDs of the the possible games
 
// interface with each color
// iterate through each of the games, and see if there are enough tiles in our bag, and if there are after this loop, then add
// The game ID to some single integer
 
interface CubeSet {
  red: number,
  blue: number,
  green: number
}
 
interface Game {
  id: number,
  cubeSet: CubeSet
}
 
 
fs.readFile('input_1.txt', "utf-8", ((err, data) => {
 
  let gameSum: number = 0
 
  const gameStrings = data.split("\n")
  const games: Game[] = []
  for (const gameIndex in gameStrings){
 
    const game = gameStrings[gameIndex]
    const beforeColon = game.split(":")[0]
    const gameId = beforeColon.split("Game ")[1]
 
    const cubeSet: CubeSet = {red: 0, green: 0, blue: 0}
    games.push({id: gameId, cubeSet: cubeSet})
 
    const tileReadings = game.split(':')[1]
    const colorsAndTiles = tileReadings.split(';')
    for (const draw of colorsAndTiles){
      const tiles = draw.split(',')
      for (const tile of tiles){
        const trimmedTile =tile.trim()
        if (trimmedTile.includes('blue')){
          cubeSet.blue += parseInt(trimmedTile.split(" ")[0])
        }
        if (trimmedTile.includes('red')){
          cubeSet.red += parseInt(trimmedTile.split(" ")[0])
        }
        if (trimmedTile.includes('green')){
          cubeSet.green += parseInt(trimmedTile.split(" ")[0])
        }
      }
    }
 
  }
 
 
  const availableCubes: CubeSet = {
    red: 12,
    green: 13,
    blue: 14
  }
 
  // parse a game string into a cubeset
  // function that has access to available cubes, takes a cubeset, checks each
  const checkIfGameIsPossible = (game: Game): number => {
    for (const [key, value] of Object.entries(availableCubes)){
      if (game.cubeSet[key] > value){
        return 0
      }
    }
    return Number(game.id)
  }
 
  for (const game of games){
    gameSum += checkIfGameIsPossible(game)
  }
 
  console.log(gameSum)
}))

Turns out I had an initial logic issue, was that for each game, I incremented the number of cubes of that color I drew as I went. But of course, there could be three total blue squares and I could draw 3 squares each time—that doesn’t meant there are 9 blue tiles total! So I changed this to only update the max draw to be the largest number drawn like this:

      for (const tile of tiles){
        const trimmedTile =tile.trim()
        if (trimmedTile.includes('blue')){
          let newBlue = parseInt(trimmedTile.split(" ")[0])
          if (newBlue > cubeSet.blue) cubeSet.blue = newBlue
        }
        if (trimmedTile.includes('red')){
          let newRed = parseInt(trimmedTile.split(" ")[0])
          if (newRed > cubeSet.red) cubeSet.red = newRed
        }
        if (trimmedTile.includes('green')){
          let newGreen = parseInt(trimmedTile.split(" ")[0])
          if (newGreen > cubeSet.green) cubeSet.green = newGreen
        }
      }

Not DRY at all, but it worked!

My answer of 149 increased to 2278, which ended up being the correct one for me.

I asked perplexity to refactor my code for me and got this:

import fs from 'fs/promises';
 
interface CubeSet {
  red: number;
  blue: number;
  green: number;
}
 
interface Game {
  id: number;
  cubeSet: CubeSet;
}
 
const AVAILABLE_CUBES: CubeSet = {
  red: 12,
  green: 13,
  blue: 14
};
 
async function main() {
  try {
    const data = await fs.readFile('input_2.txt', 'utf-8');
    const games = parseGames(data);
    const possibleGamesSum = calculatePossibleGamesSum(games);
    console.log(possibleGamesSum);
  } catch (err) {
    console.error('Error:', err);
  }
}
 
function parseGames(data: string): Game[] {
  return data.split('\n').map(parseGame);
}
 
function parseGame(gameString: string): Game {
  const [idPart, drawsPart] = gameString.split(':');
  const id = parseInt(idPart.split(' ')[1]);
  const cubeSet = parseDraws(drawsPart);
  return { id, cubeSet };
}
 
function parseDraws(drawsString: string): CubeSet {
  const cubeSet: CubeSet = { red: 0, green: 0, blue: 0 };
  const draws = drawsString.split(';');
 
  for (const draw of draws) {
    const tiles = draw.trim().split(', ');
    for (const tile of tiles) {
      const [count, color] = tile.split(' ');
      cubeSet[color] = Math.max(cubeSet[color], parseInt(count));
    }
  }
 
  return cubeSet;
}
 
function calculatePossibleGamesSum(games: Game[]): number {
  return games.reduce((sum, game) => sum + (isGamePossible(game) ? game.id : 0), 0);
}
 
function isGamePossible(game: Game): boolean {
  return Object.entries(AVAILABLE_CUBES).every(([color, count]) => game.cubeSet[color] <= count);
}
 
main();

‘Plex broke things down into a bunch of smaller, functional pieces. I particularly like this:

function parseGames(data: string): Game[] {
  return data.split('\n').map(parseGame);
}

Using map, we have a function that returns a list of lines of games right off of the data.split function, and then uses parseGame to process each of those lines. Very fancy, but nice.

function parseGame(gameString: string): Game {
  const [idPart, drawsPart] = gameString.split(':');
  const id = parseInt(idPart.split(' ')[1]);
  const cubeSet = parseDraws(drawsPart);
  return { id, cubeSet };
}

parseGame looks like it splits each game into a first and second half. I like saving this value, and the way it is de-structured. It’s just very clear to see okay, we’re not done cleaning the data, but at least we have these two sections. Good clear string manipulation.

I think at around line 3 though, things are getting a bit complicated to have these successively nested functions. I think I would maybe skip this.

My takeaway from this exercise it’s nice to have less nesting, and to be saving values at each step of the string manipulation in an increasingly better form until everything is structured exactly how you’d want it.

Okay with those takeaways, let’s move on to part II.