Them pesky elephants have stolen all the mathematical operators the engineers need to calculate calibrations for a rope bridge safety.
We gotta find those operators and see if we can put them back in the right order.
Read input
Today our input is a series of equations (missing operators) and their results:
To parse these in, I’m splitting at :
to separate result from the equation, split the equation part by white space and convert everything to integers:
Part 1
In the first part, we have two mathematical operations: +
for addition and *
for multiplication. For each equation, we need to figure out if there’s a way to put those between the numbers to reach the correct result.
Python’s wonderful itertools module has a great function product that can help create all the possible combinations we need. We give the repeat
keyword argument as one less than the amount of numbers (as there are one less empty spot as there are numbers) and it will happily churn out a ton of possible operation orders.
I then go through number by number and based on the operation, calculate the next total.
If at any point, the current total is larger than the desired result, we bail out. This is why I also put multiplication first in the operators because it results in higher numbers faster and thus we could save a couple of iterations down the line.
To put everything together, I read in the input and go through each equation. If it is solvable, I add the result to the total and we get the result!
Part 2
The second part offers a classic Advent of Code twist: adding a third operator which suddenly increases the amount of the possible orders of operators to so high that using the same approach as in the first part, things slow down significantly.
Let’s start with that anyway since that’s how I did it.
As a code change, this one is very small. We add the ||
operator that concatenates the two numbers together and add the corresponding calculation into the if clauses.
By running this the same way we did with part 1 takes the time spent from 0.32 seconds to a whopping 28 seconds.
I figured one way to optimise it a bit is to have the first part return all the solved equations and then skip their calculations in the second part as we already know them.
I changed the original part to:
and then the second part to
Once I run them together, feeding the output of part 1 to the part 2 function, the time spent drops a few seconds to roughly 24 seconds.
But I didn’t originally solve it quite like that. Instead of storing a tuple of result, numbers
, I stored only the result
because I had ran a bash script to examine the input and had come to the incorrect conclusion that each result
would only appear once. But there was one tiny equation worth 241 that was skipped by my initial code and caused such a nasty and hard to debug bug. Once I figured that out, the above code was the one that brought me the second star.
I normally never look into the input file for these kinds of tricks and today taught me the hard way that I should not try that again either.
Anyway, my solution is very slow because creating all the operator orders and applying them all to all equations just takes a lot of time.
As I then looked at others’ solutions after finishing mine, I learned that a recursive solution to this would have been way more efficient, dropping the run time to just a couple of seconds. Having seen and tested the solution, I can’t claim I would have reached that conclusion myself. Had I thought of recursion, I may have been able to come up with a solution but I poisoned that well already.
Here’s one recursive solution by Toni, adapted to match the variable names that I used above so you can see how it would work.
Today was one of them “I don’t recognise the right trick” puzzles for me. I can cook up a naive solution that takes time but probably would have never found the right direction for an efficient one.
Luckily the naive solution was still under half a minute which is still wait-able time and is within the realms of “fast enough that I can run it a few times to check my refactoring still returns the right result”.