9
votes
Day 17: Conway Cubes
Today's problem description: https://adventofcode.com/2020/day/17
Join the Tildes private leaderboard! You can do that on this page, by entering join code 730956-de85ce0c
.
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 B took a bit to calculate, I'm not really sure how I would even go about speeding it up. Maybe preallocating space? Or an improved search method?
As always, listening to eurobeat helps you be better at both code and video games. It's a scientifically proven fact
Day 17 Part A – Python
First I give myself 2 methods that help me deal with the neighbors. I only ever store the active cubes, since I didn't want to deal with a resizing grid.
Each generation of the simulation I go through each of the active cubes to find which of the neighbors I should care about checking. Then, I look at each of them and check their current status and how many active neighbors they have and update the new grid generation accordingly.
Once I'm done, the length of the active cube list is my answer
Day 17 Part B – Python
This is almost identical, except I added a 4th dimension 'w' per the instructions. And I added a nicer output at the end so that I could see the progress of the calculations. I was getting a bit nervous sitting ther waiting. I wasn't sure if it was going to take minutes or days.
Tips and Commentary
This has some similarities to Day 11. You might be able to use some of what you learned there. The rules for this are simpler, but the grid itself is much more complex
Part of the difficulty of this one is that you don't know how big your grid is going to be. Either you need to make it big enough to account for the entirety of the simulation's 6-step lifespan, or you need to come up with a way to resize it. Or you need to make it so you don't have a real grid at all. But the method you choose may dramatically affect your performance, which is especially important in part B
I was anxious about how long part B was taking, so I went ahead and gave myself periodic status updates. That helped me know my performance was at least good enough to wait for without burning my code to the ground in pursuit of better optimization. I'm afraid of how long gen 7 would take though.
This is based off of Conway's Game of Life which is a fairly cellular automata. Basically it simulates how various patterns propagate. People do really neat stuff with it
The example initial state is known as a 'glider' and it's a basic pattern that can be generated in Conway's Game of Life. When using the default rules of the game in a 2D space, that pattern will repeat forever, slowly moving forward as it does. It's also used sometimes as an unofficial symbol for hackers.
Python
Repo Link
I really didn't feel like doing this problem at first, but it was nice to get through it without much debugging at all. The hardest part was starting, though something even harder would be writing faster code! Behold my beautiful nested for loop code! Part 1 runs in about a second, while Part 2 runs in ~2 minutes.
Part 2
Part 1 can be seen in my repo link. Didn't post it because it would be redundant. I just made a dictionary where key values were tuples of the coordinates, and values were either inactive ('.') or active ('#'). That's how I inserted the input. For the function I just brute force iterated through all coordinates to find each of their neighbor counts and change their values accordingly to the rules. For the hard-coded ranges (-15 to 15), I was just too lazy to do the math to find the most appropriate bounds. So I picked a high-enough number that would simulate the infinite bounds given the number of cycles we had to run.Overall there's clearly a faster + smarter way to go about it. Like keeping track of only active cubes and their respective numbers of active neighbors, instead of iterating through all coordinates where I'm doing a lot of unnecessary comparisons. However, I just wanted to be done with this problem :)
I think the best feeling on an AoC problem is when you just know that your part 2 code is going to be a snap once you read the next half of the problem.
C#
Part 1
Part 2
Helpers
Commentary
Modifying part 1 for part 2 is literally just changing the Vec3s to Vec4s and adjusting the bounds-check.I should probably refactor some of this into an AoC toolbox - both the VecN code and some of the automata stuff too. I also need to get with the times on C# and make the VecN types into structs - that would cut down some of the boilerplate, at least.
Python
Repo Link
Part 1
This was similar to the previous grid problem we did (which I am not a fan of), except instead of actually storing a grid, I maintained a set of only the active cubes. From there, it was just a matter of generating each of their neighbors and then checking them according to the rules specified in the problem.
My biggest hangup in working on the problem were my debugging prints after each cycle did not match their examples. I was trying to debug it cycle by cycle and comparing my print out to their example and it didn't match, so I spent a lot of time trying to figure out what was wrong. Eventually, I just let it run for all six cycles and realized my counts were the same (even if the visualization was different). This was annoying, but c'est la vie.
Once again, I made use of comprehensions and
itertools
.Part 2 (diff)
Part 2 was the same as Part 1 but with an extra dimension, so I've only provided a small diff here. I was pleasantly surprised to see my code finish only in about 5 seconds.
Python
I really had fun with today's. I first naively checked all cubes in a 40x40x40 space (initially just 20x20x20 which apparently wasn't large enough after 6 cycles), which worked for part 1 but got way too slow.
For part 2, I first created a list of cubes near already active ones and only checked those. This certainly is not the most efficient way to do this but after 10 minutes or so, it spit out the correct 4D solution. I'm kinda tempted to revisit this later and look into a multi-threaded solution but for now it's a slow but working compromise.
Sidenote: I've also switched to lower-case-and-underscore naming for variables and functions since that seems to be more popular than camel case for Python.
Part 1+2
This one was neat. Using some careful typing I was able to reuse the majority of my code from part 1, and along with memoization, keep the run time very short. A first run including compilation is about 8 seconds, with subsequent timed runs taking advantage of the cache completing in about half a second. I chose to use a dictionary to connect the locations with their values, which I think made things much easier, no worrying about preventing negative values or allocating extra space.
Part 1
Part 2 diff
Elixir
I get some kind of perverse enjoyment out of defining fully fleshed-out data structures for these grid problems rather than just attacking the problem in a quick-and-dirty way, so I created two new, fully unit-tested,
CharSpace
andCharHyperspace
modules to handle creating and updating the grid of cells for each part. This made my actual solution code very concise. It also runs... decently fast? Around 80ms for part1, 3.4s for part 2.I'm sure it would be possible to generalize the infinite grid concept for an arbitrary number of dimensions using macros (rather than making separate, nearly identical modules for 3D, 4D, etc), but I need some more practice with simpler macro-writing problems before trying this one.
CharSpace module (3D infinite grid)
CharHyperspace module (4D infinite grid)
Parts 1 and 2