12
votes
Day 9: Smoke Basin
Today's problem description: https://adventofcode.com/2021/day/9
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
my main stumbling block here was self-inflicted. I thought I'd be clever, consider the 4 neighboring squares without regard to overflowing the edges, and use Python's "easier to ask forgiveness than permission" approach and just catch
IndexError
and ignore it as a way of handling falling off the side of the map.of course, what I didn't realize at first is that when I'm at
x=0
ory=0
, the neighbor computation will generate-1
as an index, which is perfectly valid in Python, and means the other edge of the map. so I was inadvertently wrapping around in cases where I shouldn't have been.and frustratingly, this bug caused no problems at all with the example input, only with my actual input. so I played some printf debugging games of having it print out what it thought were the local minima, and then comparing that by eyeball against the map. once I realized the bug I added the explicit check for
if neighbor_x < 0 or neighbor_y < 0
and the rest Just Worked.Part 2
I ended up needing "what are the neighbors of this cell?" in two places, so I split that out into its own function, and this time implemented my own bounds checking for the 4 edges of the map rather than using
IndexError
s. this means that callers ofget_neighbors
can use the return value as-is without needing bounds-checking of their own.the basin idea made sense to me as a recursive definition - a basin is the low point, plus all the points around it that aren't 9s, plus all the points around them that aren't 9s, and so on.
any recursive algorithm can be implemented in a non-recursive / iterative fashion, and I often find these implementations to be clearer / more understandable. so that's what I did.
my
find_basin
function keeps a queue ofpending
points to be considered, starting with the singlebottom
point. any time I find a neighbor point that belongs to the current basin, I add it to the set of basin points, as well as the queue of pending points whose neighbors should be considered. when I run out of pending points, I know I've explored the entire basin.I hope it didn't cause you too much frustration and this doesn't seem mean spirited, but that bug in particular is really, really funny.
Python golf, day 9. Tips/improvements always welcome.
Part 1
Recursion came in handy for part 2.
Part 2
Edit: Today is day 9, not day 8 lol. I am tired.
Day 09 (Rust)
Looks like Eric is giving us a simple one to relax after yesterday's tough nut.
That said, I'm sure there is some advanced trickery that makes it possible to speed things up, but today I solved everything in the most-obvious way possible.
Imports & Parsing
Helper functions
Solutions
Hey it's the first map puzzle!
Since I'm using Ruby again, I went ahead and pulled in a utility class for working with two-dimensional grids that I made for previous years.
The first part was pretty straight forward, but I lost a bit of time in the second part just being a little rusty on exactly how I needed the algorithm to behave.
Part 1 Ruby
Part 2 Ruby
I ran into a minor hitch in that I initially wanted to use an actual
Set
object for the visited/open sets, but the default implementation in the stdlib doesn't really support easily removing an arbitrary value the way I can withArray#shift
/Array#pop
(since a set is unordered by definition, and shift/pop rely on the collection having an order). Arrays work fine, but I have to go a bit out of the way to make sure I don't end up with duplicate entries.With problems like these I swear that my biggest problem is always keeping X and Y straight.
Day 9 - Part A - Python
Part A wasn't too bad, just built a grid with defaultdicts and searched it to find the low points. Mostly just keeping track of the boundaries and what-not. At some point I managed to swap my x and y axes, but I just ran with it.
Interestingly, using the default dict grid instead of my normal stacked lists meant that going out of bounds would automatically generate a new entry into the dictionary and then crash because the dictionary size changed mid-loop, instead of just crashing due to an array out of bounds exception
Day 9 - Part B - Python
This was a bit more difficult. I used the low points I generated in part A to build a list of starting points. Then I recursively searched outward from those points, adding any nearby locations that weren't already part of the basin. When there was nothing left to find I just took the size of the basin and shoved it into a list.
Tips
Make sure to try to keep X and Y straight. It's alright if you flip them from what the input is, but you need to be consistent about how YOU use them
There are only 4 directions neighboring each point, but sometimes those checks will send you out of bounds of your grid. Think about how to stop your program from crashing or giving strange results when that happens
If you're not careful about how you search for the basins, you might end up in an infinite loop of checking for locations that are already stored in your basin
Nothing fancy here.
Part 1
Split the input with
then pop this in and drag it around for the 100x100
I hate formulas like this
I shit you not. I looked at part two and figured it was impossible with a spreadsheet. I figured I'd just eyeball it --- so I styled the 9s as black and looked for what I thought were the largest areas. I selected a bunch of cells, documented the count... after five of these I was over it. I took the top three, multiplied, and bam -- topaz liked it. Not as fulfilling as sorting out yesterday's, but I'll take it.
JavaScript
Repo Link
Part 1
This was pretty straightforward. The only trick I used was to pad the heatmap to avoid bounds checking.
Part 2
For this part, once you have the low points, all you need to do is perform a walk or traversal from each low point until you run out of locations (ie. no more
9
s). For this, I used a traditional iterative frontier based approach.In terms of JavaScript there were still a few gotchas that I am still learning. For instance, adding an Array to a Set works, but it compares the reference/pointer instead of the contents, so you can never find the Array again. To work around this, I gave each location or node an integer identifier based on its row and column.
Classic flood fill.
POSIX AWK doesn't have
asort()
so getting the highest 3 numbers was jankier than it had any right to be.Part 1
Part 2
Rust
Rust
Point2D
I didn't even bother posting my code for yesterday ( I didn't finish part 2 until this morning) since my code was so gross, but I enjoyed today's problems!
Rust (both parts)
I probably could've used structs but I think my code is pretty readable and reasonably concise as it is :)
Data Structure
Today is the 2nd day I've had prior engagements which mean I was very late starting. I'm never going to compete for the top 100 but I'm not going to do well on any of the private leaderboards I joined now. Oh well.
I converting every digit of my input into a number and made a hash table to map point -> height. It's actually not a graph.
Part 1
The strategy is to find points lower than all their neighbours. Don't be stupid like me and think there must be 4 regions in your input because the example had 4.
I didn't bother with bounds checking generating neighbours because height is 9 or less, so I used a default height of 10 for missing neighbours.
Part 2
I generated a list of all basin sizes starting from each of the lowest points. Basin size is the number of visited points using a basic DFS. I ignored any points over 8 (which excludes the default of 10 for missing points again.)
Python
What problem I had.
First. From yesterday second half-day to today firth half-day I was without enthernet because one clever man somewhere cut enthernet wire.
Second. I somewhy thought that side cell must +1, not just bigger. I found out it only after vizualization which is included here.
Well, I think I know a way I can solve part 2, and I am going to have to resist looking up anything about flood filling now that I've scrolled through here because I'd like to do it the hard way first. I did part 1 very naively, which worked okay, but I am going to have to come back to part 2 tomorrow evening and reply with it then. Very interested to see what the actually good solutions are when I can let myself read them!
Part 1
Okay I did it! I realized throughout that it could be better done and so could all the code I did for the first part but I did it!
Part 2 diff
This problem, in my opinion, has a simplistic elegance to it, in that it can be done in many different ways. Recursion, using a fill algorithm, or even just multi-pass scanning. All just a matter of optimization, making this problem accessible to all skill levels of programmer.
Mine was probably not the best solution, but I had a pretty proud moment when I realized that this type of solution would have been pretty much completely out-of-reach for me two years ago.
Part 1
Part 2
I really enjoyed this one! The way part one dovetailed into part two was very satisfying. I think I ended up doing depth-first search, though I'm curious to try a breadth first approach on part 2.
https://github.com/bcliden/advent-of-code-2021/blob/main/day-09-smoke-basin/src/main.rs
I've been learning Rust this year, and AoC seemed like a great way to practice. I wish I'd done it in past years!
Would love any feedback from Rust gurus (if you're around ☺). It was very nice using
Option<T>
to get grid squares without obsessing over what is/isn't in bounds.Elixir
I'm using a project that I started last year, so I took advantage of a
CharGrid
module that I previously defined to parse and work with a grid of characters. It still took a while because 1) I was too stubborn to look up and implement a flood-fill algorithm, and 2) iteratively building a collection while simultaneously changing the collection you're pulling values from does not lend itself to a FP/immutable data structures approach. I had to write two custom recursive functions to accomplish this.The first time I ran it, partial basins were getting merged together when they shouldn't, and it took me a while to realize I was doing a set union where I should have been doing an intersection. It solved the puzzle successfully after that.
Part 2 runs in about 1.3s. I wonder how much faster it would be if I'd used a real flood-fill algorithm...
Both parts
CharGrid
moduleThis is me LARPing as a core library contributor 🤓