This year my nemesis are these geometry puzzles. They are not usually difficult to code but I have pretty much no applicable knowledge of how to calculate certain things based on other properties of geometrical shapes.
First part I managed to do nicely today but the second part took me a good 3.5 hours to write and another 30 minutes to clean up so it made some sense.
Read input
Our input is once again a grid so let’s build one.
If you’re just joining in with my explanations, see for example Day 04 where I wrote more about parsing grids to dictionaries.
Part 1
Our grid is a map of garden plots.
Each connected area of same letters is one plot. In the above example, we have the following plots:
Our job is to calculate the area and the length of the perimeter for each garden plot.
I used a recursive approach for this one:
For any given position, we find all the neighbours that share the same letter and once there are no more neighbours, we return all the coordinates for that plot. The area of that plot is then its length.
To calculate the perimeter, I did a bit of double work as I ran through the garden again and calculated the perimeter of that plot:
To wrap it together, I go through every position in the grid and if we haven’t been there yet, we find the plot it belongs to, mark all positions of that plot as visited and add the plot to our books:
To calculate the full cost of fencing the garden, we find all the plots and for each plot, we multiply its area with its perimeter:
Part 2
The second part added a twist that almost broke me.
Instead of the full perimeter, we’re now interested in just how many sides there are in each plot. It sounds like a simple change but it was everything but.
I don’t know how to calculate the amount of sides in a shape like these plots. I tried so many approaches: at one point I tried to calculate the outer sides and then remove the amount of sides any adjacent plot had touching but that became a recursive problem where to count the sides, I needed to know how to count the sides.
I then decided to count the corners instead. It took me quite a long time to figure out how to count corners for any non-square shape. Once I figured that out, I then needed to figure out how to calculate the amount of squares of shapes inside those plots and how to combine them in a way that produces the right result.
Let’s dig in.
Once again, this final code is a result of going deep and dirty and then refactoring it to make more sense. I did not solve this in the same order as I’m explaining it. Especially the helper functions almost always arise from refactoring rather than writing them up front.
Corner detection
Let’s start with a simplified version of my final code
and let’s imagine we have a plot of
If we take 1
, we check if it’s a corner by checking its neighbours in a specific way:
- If
up
+left
are not part of the plot, we have one corner - If
up
+right
are not part of the plot, we have another - if
left
+down
are not, a third corner - and if
left
+up
are neither, a fourth corner
In our example above, only the left
+ up
is empty so 1
is one corner. In a 2x2 grid like this, we have four spots that are each 1 corner.
For a shape that is behaving nicely, that’s a good start.
For a more complicated shape like
we have to account for the corners that are created by that empty space.
What I ended up doing is I took the plot and then I check for all the other, imaginary plots that fit inside the rectangle boundaries of the original shape. For example in the above example:
I’ve marked another shape as e
.
Or we could have
where we have two imaginary plots that fill in the rectangle.
To calculate these, I added a bunch of variables to help calculate it:
The rev
flag signifies if we’re dealing with these imaginary plots. For them, we want to know if they are the reverse of a corner, ie both positions are inside the original real plot:
Note
I’m sweating trying to explain my convoluted code and we’re not even done yet…
We still have one corner case left. Eric was kind enough to point that out directly in the puzzle description or otherwise I would have never even thought about it being a case.
For a plot like these, we have a position where two B
plots touch each other only diagonally.
The current code will count these as double corners because if we look at it from the perspective of the A
plot, there are two A
s that are in a corner that also are corners of the B
plots.
To check for these, I have a beautifully named
That checks if any corner check lands into a situation where both of them are inside our sub grid (neither coordinate results in None
) and there coordinates belong to different plots.
Let’s put all of these together for our corner counter:
To then calculate how many sides a plot has, we create a new sub grid that is the rectangle area around a plot like I showed above.
We then find all the plots in this sub grid that are not our main plot.
We calculate the corners for our main area and count the “reverse corners” for all the other positions and sum them up.
We finally calculate the total cost by multiplying sides with areas:
I’m happy I finally reached the solution and just in the nick of time as after finishing writing this piece, I’m heading out as we host an Advent of Code sprint with our local Python community archipylago today.
Being able to finish my own solution before the event frees me up to host the event and help new people get into Advent of Code instead of banging my head against the wall all night.