I was expecting a bit of a difficulty curve up for today and while we did get one, it happened to hit my skills very well so it didn’t feel like such. As soon as I read the puzzle description, I knew I’d be reaching for regular expressions. The link is to a blog post of mine where I go through how regular expressions work in a bit more detail.
Reading input, part 1
This time I’ve split the input reading to two parts because they require different treatment. It’s also important to note that this time, the example input is different in the two parts. I did not expect that so I spent a good chunk of time debugging my regular expression in the second part.
For the first part, our example input looks like this:
It’s a bunch of multiplication clauses, some of which are corrupted. We need to find all clauses that match mul([int], [int])
, for example mul(2,4)
near the beginning.
I created a regular expression that matches a string of the given format and I capture both of the numbers inside. The (\d+)
that’s there twice means: 1. find all numbers (\d+
) and 2. capture them (the parenthesis around it).
Once that’s run against the line, it will create a list of tuples where the first item in the tuple is the first number and the second item is the second number. When run against the example input, the result is
Part 1: Multiplications
Regular expression in the data parsing does most of the heavy lifting in this solution.
Once we’ve read in all the lines, we loop over them, then loop over all the instructions (pairs of numbers), cast them to integers, multiply together and add to our result.
Reading input, part 2
In the second part, we also care about do()
and don't()
instructions that either enable or disable multiplication readings.
Now the example input has changed to:
I adjusted the regular expression pattern:
Here, we capture either mul(a,b)
, do()
or don't()
. The |
character between them in the pattern means OR
.
When we use re.findall
with this one, it will capture the entire set (everything is wrapped inside parentheses) into the first tuple item and then the two numbers into the next two items.
With the new example input, it results in:
Part 2: Conditional multiplications
Part 2’s code is a bit more complex.
In this part, we need to keep track of whether our multiplication is enabled or not and we do it with a boolean variable enabled
.
Our main logic happens inside pattern matching flow. In the pattern matching, we start with a match
statement declaring which variable are we matching against. In our case, it’s the entire instruction (which is in form (operator, a, b)
).
In each case
statement, we match against a specific “blueprint”:
case ("do()", _, _)
matches any tuple where the first item is literal "do()"
and the other two can be anything. Similarly with the second case with "don't()"
.
The third case
matches anything else and it maps the first item into variable operator
, second into a
and third into b
. We then check if the enabled
flag is set and if the operator includes the word “mul”.
Today’s puzzle was fun. I made couple of silly mistakes (like writing 2
instead of 3
into my read input arguments and wondering for a long time why my regular expression didn’t work) and then not noticing the example input changed between the parts.
I got to use two of the functionalities I really like: regular expressions and pattern matching. I’m sure we get to see a whole lot more of them in the future as well. In terms of this digital garden experiment, if I were new to either of them, I would have written separate notes about them in my Knowledge folder.