Advent of Code 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:
[
'Game 2',
'1 blue, 2 green; 3 green, 4 blue, 1 red; 1 green, 1 blue'
]
split(';')
gets me:
[
'Game 2',
[
'1 blue, 2 green',
'3 green, 4 blue, 1 red',
'1 green, 1 blue'
]
]
split(',')
gets me:
[
'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:
['3 green', '4 blue', '1 red']
Into this:
{ 'green': 3, 'blue': 4, 'red': 1 }
I can use reduce()
to build a dictionary from scratch:
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:
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:
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
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!
Top comments (0)