8
votes
Day 23: Crab Cups
Today's problem description: https://adventofcode.com/2020/day/23
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>
So part one was rather straightforward:
Part 1
But part two...
spoiler?
it'll take about 66h for me to get the result, according to my simple calculations. I've tried to figure out whether something repeats, but it doesn't seem to be the case. Taking only small slices also doesn't work, because you have to put stuff at the very end of the array rather early on, when cup '1' gets chosen, and the destination cup wraps around to the end... I'm not sure how to do this right.Spoiler: hint
As I described in my solution, the main bottleneck is in the linear search to locate the destination cup. So the line you want to address is
dest_index = cups.index(dest_label)
which searches the whole list each time to find the destination. You can speed this up by keeping a dictionary that tells you the index of a particular label{label: index}
. This would be a constant time lookup and should speedup your code significantly.Thank you!
That definitely helped, but it would still have taken too long.
So I did that and then some. xD
Spoiler
Slicing and dicing arrays is not good for performance, when we're talking 1M arrays, because i'd have to update the mapping every iteration: I'm shifting around most of the array by three positions. So I ended up writing a doubly-linked list, and that did the trick. Still not super fast, and definitely not pretty, but it works.I'll clean it up a bit, then post it too.
Edit: I saw that you used a singly linked ring-buffer, which is even cleaner, and definitely less code to get wrong. xD
Python
Repo Link
Part 1
The problem for today wasn't too bad... but it still took me a while because I had to implement a circularly linked list in Python, which I've never done before (I have in C/C++, but never in Python). Once I had the data structure working though, it was pretty straightforward to implement the rules of the game.
Part 2 (diff)
The second part was also straightforward. The main changes were adding more cups to the initial list and using a hash table / map to keep track of where the cups are. The reason for this is that you need to locate the destination cup in order to insert the three cups you extracted. In Part 1, I used a simple linear search that scanned the whole list to find this cup. With one million cups, however, a linear search is really slow, so I used a dictionary to map the cup's label to its actual object. With this dictionary in place, I could find the destination cup in constant time.
I could have also optimized
find_max
, which is still linear, but since it wasn't called often, I decided to leave it be. In the end, my second part took about one minute to run (after I removed all print statements).Rust
Hint (SPOILERS)
The trick to this one is setting up your cups in a data structure that facilitates quick lookups of the cups and fast changes to the order. I did a similar problem one AoC year using linked lists. This year I'm just using a regular Vector, but rather than reordering the elements in the vector each iteration, I just keep track of which elements are CW and CCW of each other element.Code
Crab game go brrrrrrr
C#
Part 1
Part 2
Helper
Commentary
The circularly-linked list has some horrendous software engineering in terms of defining clean API boundaries, but it allowed me to do initialization easily, so /shrug. The neat trick was realizing that while we need constant-time insert/delete and find operations, we don't actually need random-access at all - so a linked list is a perfect solution because we also know where each element will be.
I'm pretty sure you could optimize this further and remove the hashtable by just using the position in the backing array as your lookup, but it'd be an extra step to wire it up correctly to initialize
My part1 solution run against part2 would have taken ~3.5hr. The version shown here gets through it in 5.59s. Not a bad speedup.