Part One

Easy peasy.

import fs from 'fs'
 
interface Row {
  winning: string[],
  hand: string[]
}
 
fs.readFile('puzzle.txt', 'utf-8', (err, data) => {
// fs.readFile('test.txt', 'utf-8', (err, data) => {
  if (err){
    throw new Error('Error reading data:', data)
  }
  const cleanedData = cleanData(data)
  let totalScore = 0
  for (const line of cleanedData){
    let lineScore = 0
    for (const winner of line.winning){
      if (line.hand.includes(winner)){
        lineScore = lineScore === 0 ? 1 : lineScore * 2
      }
    }
    totalScore += lineScore
  }
  // console.log(totalScore === 13)
  console.log(totalScore)
})
 
function cleanData(input: string){
  const rows: Row[] = []
  for (const line of input.split('\n')){
    const content = line.split(':')[1]
    const [left, right] = content.split('|')
    rows.push({
      winning: left.match(/\d+/g) ?? [],
      hand: right.match(/\d+/g) ?? []
    })
  }
  return rows
}
 

Part Two

Definitely fleeced me a bit. I think I spent an hour and a half on this or something.

Originally, though I thought I read the question very carefully, I made a really big error.

The way I originally added up the cards was like this. Let’s say the number of matches/wins we have per card is [2, 1, 0, 0, 0, 0]

I iterated through the cards this way:

starting state
[2, 1, 0, 0, 0, 0]

Card 1
[2, 2, 1, 0, 0, 0]

Card 2
[2, 2, 2, 1, 0, 0]

Card 3
[2, 2, 2, 2, 1, 0]

Card 4
[2, 2, 2, 2, 2, 1]

See this pattern? I am conflating the matches with the copies, and using the same data structure to represent them. But they really aren’t the same.

MATCHES determine how far the additionally copies are distributed farther down the array. By default, they distribute 1 additionally copy for every match that exists, even if that current card doesn’t have any copies to its name.

COPIES on the other hand are a totally different species with different implications. They also add additional cards down the stack, but they don’t do so in ‘distance’ but rather in quantity. The same cards will be getting copies distributed to them, but the # of copies +1 of the current card determines how many copies get distributed to them (where 1 is the default that still gets distributed, even if there are no copies, only the original).

I completely conflated these two data structures into a single one, which really didn’t work.

There is also sort of a third data structure, which is the total number of instances of cards we get in the end, but it’s debatable whether this really gets its own data structure, as it is simply the same as if you took the copies data structure and added 1 to every value.

I am getting some code smells that we really only may need one data-structure to handle all of this, but that might be a trap of perfectionism. I don’t think more data structures is worse, as long as we are reasoning through and trying to understand the problem - it’s probably better.

// no points
// scratchcards cause you to win MORE SCRATCHCARDS (= to # of winning numbers you have)
// BELOW the winning card
// as we go down the cards, copies compound - like card 4 might have 8 copies from previous cards, so if it has 5 matches, then it might produce
 
// ambiguities / ecs
// are duplicate hand matches scored?
 
// Improvements
// rename rows and or lines to "cards"
// duplaci
 
// terminology ideas
// copies
 
// ideas
// start backwards (no)
// idea two, is find matches for all decks FIRST
// then we could do something like BFS or DFS
 
import fs from 'fs'
 
interface Card {
  winning: string[],
  hand: string[]
}
 
fs.readFile('puzzle.txt', 'utf-8', (err, data) => {
  // fs.readFile('test.txt', 'utf-8', (err, data) => {
 
  const cards = cleanData(data)
  const wins: number[] = []
 
  for (let c = 0; c < cards.length; c++){
    let winsForThisCard = 0
    const card = cards[c]
    for (const winner of card.winning){
      if (card.hand.includes(winner)){
        winsForThisCard++
      }
    }
    wins.push(winsForThisCard)
  }
 
  const copies = Array(wins.length).fill(0)
  const totalNumberOfCards = Array(wins.length).fill(1)
 
  for (let w = 0; w < wins.length; w++){
 
    const winsForThisCard = wins[w]
    let iterator = 1
    while (iterator <= winsForThisCard){
      const nextIndex = w + iterator
      copies[nextIndex] = copies[nextIndex] + (copies[w] + 1)
      iterator++
    }
  }
 
  console.log(copies.map(c=>c+1).reduce((acc, curr)=>{
    return acc + curr
  }, 0))
  
})
 
function cleanData(input: string){
  const rows: Card[] = []
  for (const card of input.split('\n')){
    const content = card.split(':')[1]
    const [left, right] = content.split('|')
    rows.push({
      winning: left.match(/\d+/g) ?? [],
      hand: right.match(/\d+/g) ?? []
    })
  }
  return rows
}

Meta Gotcha

I think the main gotcha I experienced was not respecting the natural distinctions of data that were given to me. The words copy and card were not conflated by the example, and were delivered to me as two distinct entities. But my silly human brain thought of them as the same thing, as the same information, and that very conflation led to a state of collapsed of information was not a foundation that a working algo could stand on.