## [Problem](https://adventofcode.com/2023/day/2)
The problem is to determine which games would be possible with 12 red cubes, 13 green cubes, and 14 blue cubes. Each game is represented as a line in the input with multiple cube reveals separated by [semicolons](https://youtu.be/M94ii6MVilw). If the game is possible then add its ID to our answer.
Part 2 involves determining the fewest cubes of each colour required for each game. Essentially just counting the maximum count of each colour seen. Add the product of these counts for each game and that is our answer.
### Input
- 100 lines
- Maximum line length is 164
- Each game has between 3 and 6 reveals
> [!example] Example line of input
> `Game 3: 10 blue; 7 blue, 1 green; 19 blue, 1 green, 9 red`
## [Solution](https://github.com/mic-max/advent/blob/master/2023/d02.py)
### Simple
I'm not sure if there's some super fast way to do this? I just did the basics for this day.
To parse each line I simply `enumerate`d over them starting at `1` which I use for the game number instead of parsing that from the input. A potential risky move and not the safest way to skin this cat. I then split the everything after the first colon by semicolons, which represents each game. Then further splitting on commas to get a the count and the colour which I, you guessed it, split once again on the space character. Making sure to strip any whitespace I convert the first element to the int count and the second element is the colour.
Then simply checking if the count is greater than the 12 red, 13 green or 14 blue cubes depending on what colour we're dealing with. Sum up the game ID and that's all we need for part 1.
```python
res = 0
max_counts = { 'red': 12, 'green': 13, 'blue': 14 }
for i, line in enumerate(L, 1):
skip_game = False
for setx in line[line.index(':') + 1:].strip().split(';'):
if skip_game:
break
for pair in setx.strip().split(','):
parts = [x.strip() for x in pair.split()]
count, colour = int(parts[0]), parts[1]
if count > max_counts[colour]:
skip_game = True
break
if not skip_game:
res += i
return res
```
> [!Python Loop Breaking]
> Python doesn't let you label loops to facilitate breaking or continue from an outer loop so I use a boolean flag to do so. Which gets a little messy in the name of keeping a small code footprint and being slightly more efficient by avoiding redundant checks if the game is already deemed impossible. I'm not happy about it either...
To solve part 2 we'll also count the most of each colour we've seen in a game. Using a `defaultdict(int)` is just a bit easier than manually setting each colour to zero, not a huge deal with 3 elements but not a bad idea in my opinion. Then when processing each count and colour set update the `min_counts` if the current count is greater than what's in the dictionary.
```python
res = 0
for idx, line in enumerate(L, 1):
counts = collections.defaultdict(int)
for setx in line[line.index(':') + 1:].strip().split(';'):
for pair in setx.strip().split(','):
parts = [x.strip() for x in pair.split()]
count, colour = int(parts[0]), parts[1]
counts[colour] = max(count, counts[colour])
res += functools.reduce(operator.mul, counts.values(), 1)
return res
```
Some might say `functools.reduce` is too much, I somewhat agree but I am using it so I don't assume the colours are fixed in stone. If someone asked me next year to add 50 new colours this code would remain as-is.
## Performance
| Part Number | Time |
| ----------- | -------- |
| Part 1 | 0.000544 |
| Part 2 | 0.000962 |
I shave off ~40% of part 1's runtime by breaking out of loops early. Even more could be saved if I didn't preemptively split the whole line and only iterated through it as needed.
## Conclusion
Sadly, there's not much else to say about this one...