Cube Conundrum
Tags: adventofcode, algorithms, programming, javascript
Published: 2023-12-02 18:23:06

## [Advent of Code 2023 Day 2](https://adventofcode.com/2023/day/2)

### Part 1

#### Surgically splitting a string
From a string like this:
```
Game 2: 1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue
```

I need to construct this data:
```
id: 2
set:
  blue: 1
  green: 2
set:
  green: 3
  blue: 4
  red: 1
set:
  green: 1
  blue: 1
```

Thankfully:
- A colon separates the id
- Semicolons separate the sets
- Commas separate the cube types and amounts

Sounds like an overly-complicated series of `split()` and conditions will help me complete this task.

Let's give it a try!

##### `split()`s
`split(':')` gets me:
```js
[
  'Game 2',
  '1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue'
]
```

`split(';')` gets me:
```js
[
  'Game 2',
  [
    '1 blue, 2 green',
    '3 green, 4 blue, 1 red',
    '1 green, 1 blue'
  ]
]
```

`split(',')` gets me:
```js
[
  'Game 2',
  [
    ['1 blue', '2 green'],
    ['3 green', '4 blue', '1 red'],
    ['1 green', '1 blue']
  ]
]
```

That should be all the `split()`s needed, aside from the one in the next step.

##### Using `reduce()` to convert arrays into dictionaries
I want to turn this:
```js
['3 green', '4 blue', '1 red']
```

Into this:
```
{ 'green': 3, 'blue': 4, 'red': 1 }
```

I can use `reduce()` to build a dictionary from scratch:
```js
game.reduce((dict, cube) => {
  const [amount, type] = cube.split(' ')
  dict[type] = +amount
  return dict
}, {})
```

It's probably time to put this code in a text editor and check my work!

##### Checkpoint 1: was surgery successful?
With a few edits, my JavaScript for parsing the input into `id`s and set `dictionaries` looks like this:

```js
input.split('\n').reduce((idSums, game) => {
  let [id, sets] = game.split(':')
  id = +id.match(/\d+/)
  sets = sets
    .split(';')
    .map(el => el.split(', '))
    .map(el => el.reduce((dict, set) => {
            const [amount, type] = set.trim().split(' ')
            dict[type] = +amount
            return dict
          }, {})
        )
  // To-do: check sets for possible cube totals
}, 0)
```

#### Examining each set for possibilities
The current criteria for possible match is:
```
red: 12,
green: 13,
blue: 14
```

My thought process for constructing an algorithm:
- Each game consists of a series of sets
- I must check each set for cube amounts less than or equal to each color's respective total
- Any sets that exceed the total should flag the whole set as invalid
- Any games with a single invalid set should not count toward the sum of `id`s

For example, Game 3's sets are:
```
8 green, 6 blue, 20 red
5 blue, 4 red, 13 green
5 green, 1 red
```

Checking each one results in:
```
valid, valid, invalid
valid, valid, valid
valid, valid
```

Making each set:
```
invalid
valid
valid
```

Making the game `invalid` due to the single invalid cube amount.

Of course, `valid` will be `true` and `invalid` will be `false`.

And each check will compare the set dictionary with the `totals` corresponding value.

##### Checkpoint 2: was my thinking valid?
Yes, it was! Although, my algorithm got a bit more complicated:
```js
input.split('\n').reduce((idSums, game) => {
  let [id, sets] = game.split(':')
  id = +id.match(/\d+/)
  sets = sets
    .split(';')
    .map(set => set.split(', ')
          .reduce((dict, set) => {
            const [amount, type] = set.trim().split(' ')
            dict[type] = +amount
            return dict
          }, {})
        )
    .reduce((flags, cubes) => {
      for (let cube in cubes) {
        flags.push(cubes[cube] <= totals[cube] ? true : false)
      }
      return flags
    }, [])
  return idSums += sets.every(el => el == true) ? id : 0
}, 0)
```

#### Correct, or not?
My algorithm generates the correct answer for the example input.

And for my puzzle input!!!

Woohoo!

### Part 2

#### First, a hypothesis
- The game ids and sets can't change: they're in the input
- Part 1 had fairly low values as criteria
- No digits in my puzzle input are over 20

My hunch:
- Part 2 will exponentially increase the criteria values
- And ask the solver to see which game would last the longest if each set subtracted from the new values

Let's see!

#### Confusion, then full understanding and confidence
- Well, I was way off in my hunch
- Minimum amount?
- How is the author determining this?

...

Ohhhh, it's just the largest number across all sets of each cube color!

That seems fairly straightforward to calculate.

#### My approach
- Instead of accumulating boolean values as I visit each cube...
- I'll compare each amount to a running tally of the current highest value per color
- By the time I visit the last cube in the last set of each game, I should have the winning numbers for all three colors!

#### My JavaScript algorithm
```js
input.split('\n').reduce((powerSums, game) => {
  let [, sets] = game.split(':')
  powers = sets
    .split(';')
    .map(set => set.split(', ')
          .reduce((dict, set) => {
            const [amount, type] = set.trim().split(' ')
            dict[type] = +amount
            return dict
          }, {})
        )
    .reduce((tallies, cubes) => {
      for (let color in cubes) {
        tallies[color] = tallies[color] >= cubes[color] ? tallies[color] : cubes[color]
      }
      return tallies
    }, { red: 0, green: 0, blue: 0 })
  return powerSums += Object.values(powers)
    .reduce((power, min) => power *= min)
}, 0)
```

Gosh, isn't `reduce()` so versatile?

I just used it to keep track of my cube color tallies...without needing a separate variable!

#### Checking, submitting and celebrating
It generated the correct answer for the example input.

And for my puzzle input!

I love these earlier Days because there are rarely many intentionally hidden 'gotcha' edge cases in the puzzle input.

Get it to work no the example, and odds are it will work on my puzzle input.

Not the case come Days...well...7 and up.

Bring on Day 3!