5
votes
Day 12: Garden Groups
Today's problem description: https://adventofcode.com/2024/day/12
Please post your solutions in your own top-level comment. Here's a template you can copy-paste into your comment to format it nicely, with the code collapsed by default inside an expandable section with syntax highlighting (you can replace python
with any of the "short names" listed in this page of supported languages):
<details>
<summary>Part 1</summary>
```python
Your code here.
```
</details>
Part 1 was just a flood fill to get sets of coordinates for separate regions. Area is just the number of coordinates in each region, and perimeter is taking each coordinate in the region, checking the four directions around it, and adding 1 for each adjacent coordinate not in the region.
Part 2 borrowed the perimeter method from part 1, but collapsed adjacent coordinate/direction pairs together into equivalence classes.
Part 1
Part 2
It took me some time to understand how your Part 2 solution worked.
My solution involved blowing up the figure, taking its outline, and then counting the corners in a very particular way.
Yours is much cleaner although I took a while to understand because I wasn't expecting to use complex numbers for directions. Maybe that could come in handy to other grid problems. :) Thanks for sharing!
I've been posting heavily over on /r/adventofcode, but figure I can start sharing here too. I'm pretty happy with how clean my solution ended up, especially for Part 2.
Explanation
For the first step, segmentation into regions, I used a disjoint-set structure. (I borrowed the implementation from SciPy, though I've written my own a few times.) I add all the individual cells to it as single-element sets, then make a pass over the them and wherever a cell has the same letter as one of its neighbors to the right or below, I merge the two subsets they belong to. After that, the disjoint set contains one subset per region and the elements of those subsets are the cells of that region.
Then I do a scan over each cell in each region:
For area, I obviously just count one per cell.
For the perimeter, I count one each if the cell above, to the left, to the right, or below isn't in the region.
For the sides, the key observation that I eventually realized is that there is a one-to-one correspondence between sides and corners. So I can just count the corners. There are two classes of corners:
Outer corners. If the cells above and to the left aren't in the region, then I have an upper-left outer corner. Picture it like this with
X
for cells in the region,O
for cells outside the region, and?
for don't-care cells.And of course I do the same for the other three outer corners by symmetry.
Inner corners. If the cells above and to the left are in the region, but the diagonal cell isn't, then I have an upper-left inner corner:
And of course, here too, I do the same for the other three inner corners by symmetry.
So it's possible to count the area, perimeter, and sides just by scanning over the cells in each region and looking at the 3✕3 neighborhood around each cell.
I posted a visualization of this in action over on Reddit.
Code
Used a trick I didn't see elsewhere that simplified things. When trying to decide to increment the wall count, check if a neighbor in a direction parallel to the wall also has a wall in that direction. So for walls running left and right, check that the cell to the left of the current cell does not have a wall. Only one cell per left to right wall segment will be leftmost.
Part 1 and 2
Really interesting part 2 on this one! I solved the puzzle last night, but my solution was extremely slow; I took the time today to speed it up a little, and while it's still not especially fast, it is a lot better. I think the way I'm solving part 2 is pretty naïve and ugly, but it was all I could think of.
Solution (Jai)
Ugh, first day truly annoyed by Pharo... I had the logic quickly, but ran into so many issues just trying to store a grid. There's a built in
Array2D
, but it doesn't have a neat way to create it from the original data and is also deprecated. I found Containers-Array2D, which hasCTArray2D
withfromRows
, but I spent far too long before realizing that it uses points as x-y instead of row-column... but it does includeCTAlternateArray2D
which uses row-column, but doesn't havefromRows
!I like a lot of what Pharo has as far as tools, but to have something as basic as a 2D array have 3+ inconsistent versions is just ridiculous. I haven't even looked at
CTNewArray2D
,CTNewArray2DRowsAndColumns
, orCTNewArray2DXAndY
...Pharo Smalltalk Solution
This one was extremely frustrating. I had all the examples for Part 2 passing hours ago, but somehow the actual input did not work, so I had to scrap my initial approach and try something else.
Part 1 (Rust)
My solution to Part 1 relies on a UnionFind data structure, which I had prepared in advance of AoC. UnionFind allows you to efficiently find the area for each plant in the input by 'merging' adjacent plants.
Then it's a simple matter of finding the border tiles around a set of plants and counting them up.
Part 2 (Rust)
I initially tried to walk along the edges of the plots. Doing so naively doesn't work with plots contained in other plots, but eventually I got all the test inputs passing. It just didn't work on the actual input.
So after hours of frustration, I instead fell back on UnionFind again to merge adjacent border tiles (which is really the more obvious way to do this, I guess, but it didn't work for me initially so I abandoned the idea). This needs to be done separately for horizontal and vertical borders, because those 'sides' are counted separately.
Helpers
I'm omitting the UnionFind code. It's easy to write the data structure by following the Wikipedia article.