24
SOLUTION MEGATHREAD-š- 2018 Day 23 Solutions -š-(self.adventofcode)
submitted 7 years, 1 month ago* (edited 1 hour, 40 minutes after) by daggerdragon to /r/adventofcode (134.8k)
--- Day 23: Experimental Emergency Teleportation ---
Post your solution as a comment or, for ...
7 years ago
ā
7 years, 1 month ago36 of 36
Tip Reveddit Real-Time can notify you when your content is removed.
your account history
Tip Check if your account has any removed comments.
view my removed comments you are viewing a single comment's thread.
view all comments


39/166, Python
I spent way too long on the second part with crazy path finding trying to find the best location before I realized a simple binary search with big boxes down to small ones would solve the problem in almost no time, taking my run time from minutes to less than a second. Then I spent a long time in a debugger wondering why my solution skipped most of the last steps, thinking I had a logic error. Turns out I got lucky after only a few iterations.
EDIT: Fixed a couple of cases. Made the code work in python3, and deal with the case where all of the bots are on one side of the origin.
EDIT #2: Fixed some more edge cases where points where near the edge of the boxes
EDIT #3: Recursive search to fix some edge cases with how I was grouping bots together
Would you mind getting me a copy of your data set? Iād love to play with it.
That was cute. It worked for me with your sample input (and got the correct value), however, I think I know what happened here.
I've edited the post and you can grab the working version from here. If you're using python3 like a normal person, and not python2 like a silly person like me, my original version wouldn't work since it assumed integer math. But PEP-238 changed the rules a bit. I've changed it to use the integer divide operator so it works in both python2 and 3.
Thanks for the sample data! This is lesson #789345 that I need to move on to python3. One day I will.
I have never bought gold before, but I have been banging my head on this problem for the past two days and your solution was the first to work. So enjoy your gold.
Thanks for the gold!
I've been there before, so don't fret too much over it (Heck, I'm going through AoC 2016 right now and got stuck hard on day 11). Hopefully you can get past the annoyance of not being able to solve it and learn a thing or two. It's tough .. I know.
I know this is going to sound crazy but I literally solved 2016 day 11 by using OpenOffice Calc and moving cells around like in the example, by making sure I don't go too fast. I remember I took a look at the leaderboard first (I did 2016 in October-November 2018) and realized that it was a pretty long challenge, that's why I decided to play around with a spreadsheet first. Turned out the spreadsheet was sufficient for getting the stars and I spent less time than the 85th person or so for part 1 + part2. https://github.com/sebranly/advent-of-code#easy-challenges
ā
Of course, if I did it in 2016 (on time) I wouldn't have had access to the leaderboard and would have decided to implement it with some code and I wouldn't have had a spot in the worldwide leaderboard anyway.
For simple input
pos=<1,1,1>, r=3
pos=<2,2,2>, r=6
part 2 answer should be
0right? Your calc2 returns3...Right you are. I assume 0,0,0 is in the range of the sample inputs, but if it's not, the rules still say it's a valid option. I've fixed up my solution in the post above account for this case.
another edge case:
pos=<1,1,1>, r=1
pos=<1000,1000,1000>, r=9999
pos=<101,100,100>, r=1
pos=<100,101,100>, r=1
pos=<100,100,101>, r=1
best location should be <100,100,100> with 4 count. Or 3 count if r=9 instead of 9999.
Ahh, figured it out, fixed up my post. Please, feel free to keep posting edge cases if you find them!
pos=<1,1,1>, r=1
pos=<1000,1000,1000>, r=7
pos=<101,100,109>, r=1
pos=<100,101,103>, r=1
pos=<100,100,101>, r=1
should be <0,1,1> and best_value = 2, but it chooses best = <99,100,101> and best_value = 300.
fix one bug, another bug appears aha
And fixed. This is entertaining!
That's interesting. I'll take a deeper look to see what's going on.
It is easy to fix this, the first iteration should start with min X = 0, min Y = 0 and min Z = 0
Yes, you would be correct. Not sure why though.
I have been trying the posted solutions against my input, but I am not sure how to run this one. I am an admitted Python novice and I don't see where the main code is supposed to enter here. It looks like just a series of functions defined and I don't get any output.
I've actually created a little harness to run the puzzles in Advent of Code. It handles some of the boilerplate stuff, and does some stuff for me every day to make it easier to test code each day (or, in the case of today, test code all day long). This blob of code should run my function for you:
That is the part I am missing. I do something similar in C# but I wasn't quite sure how to do this setup in Python. I'll run my input though it tomorrow and see if it is correct. I have only found a couple posted samples that work on mine so far. It seems that most so far only work on a subset of inputs.
If you still wondering on how to do this in C# I ended up working on this and getting it to work for my input, here is the code: https://pastebin.com/djU4fY5J
That code actually works with my input. I'll take a closer look at it when I get some time.
If anyone would like to help me understand how this works, I'd appreciate it. I'm a fairly new coder, and I implemented this in javascript, and am looking at the outputs through the loop iterations, and have a rough idea of what's happening, but I don't quite understand why this works. That is, it seems to me that when dist is large and the search jumps are broad, it would be easy to get a very misleading "best" answer, and home in from then on towards a wrong value. So I must be conceiving of this incorrectly, and if anyone wants to direct me towards some reading on the matter (or give it a go themselves), I'd love that. Even keywords would help (or is "binary search" really all I need?).
It's a tree search, but not a traditional tree search, so the terms used here are a bit misleading.
If we approach the problem with 2 dimensions it's a bit easier to visualize. Also, really the bots are closer to a circle (or spheres in three dimensions) for the purposes of this discussion. It's not perfect, since the radius is expressed in units of Manhattan distance, not traditional Euclidean distance, but the idea is basically the same.
So, you have a bunch of circles (they're weird looking circles because of the Manhattan distance). The goal is to find the point on the grid that is inside of the most circles, and if there are more than one, there are some rules for which point to pick.
The first obvious solution to this is to just run through each point in turn, see how many circles it's inside of, and return the best point. That will work, but given the size of the grid, and the fact it's really 3 dimensions, it'll take slightly longer than forever to run.
So, instead of all of that work, leave all of the circles on the imaginary piece of of paper, but use bigger grid lines. The rules for finding how many circles intersect every point change slightly when you do this, since you need to take care that they intersect not each point, but with the little box of the grid you're using. Run through and do that for each box.
Once you've done that, you know the box where your target point is, but not what the point itself is. So, repeat the process with slightly a smaller grid size. In my case, I started with a giant grid, used a grid size that's a power of 2, and just kept halving the grid size each time, since the math is a little easier in that case. I just keep doing the work, knowing my search area the first time is the entire grid (and if you follow my code, the grid size I start with is actually bigger than the overall world grid, it's a bit wasteful). Then when I find the rectangle (or cube, really) that touches the most circles, I half the size of the grid, and search again just inside that point. (My code adds a bit to either side of search as I drill down, just to handle some edge cases).
I keep repeating the process, each time searching the same number of cubes, but they're smaller and smaller, till my grid size is one point big, at which point I switch to a simple point based search, knowing that my answer is somewhere in the very small search space.
This idea is similar in principle to how one searches a tree for a value, only the tree in this case has to have each node calculated as I run down the tree. Put another way, it's nearly just as costly to find out what the "value" of a point is, versus the max value for a range of points, so I use that to my advantage, searching a few ranges, and when I find a winner, I search again, with smaller ranges within the winning range, till my range size is as small as it needs to be for the puzzle.
I think this is what I was missing - that rather than checking against points you're checking against grid-resolution chunks? Is that right? I'll go back over it. Thanks!
Exactly.
There's an edge case that lordtnt pointed out: If one of the larger grids contains more circles than the smaller ones does, it can lead the solver down improper quests.
For instance, the top left bot (assume all have a radius of 1) in this grid should be picked, since it's closer to 0,0, but the solver I describe will pick one of the two in the bottom corner, since an early scan of the grid with low resolution will appear to have one grid cell contain two bots in it.
I'll take a stab at fixing that in my code when I have free time, but feel free to figure out a way to fix it yourself!
There's a much bigger edge case too
If you split this grid into four pieces evenly, the top left will have four bots while the bottom right only two. But assuming they all have
r=1, none of the top left bots actually overlap their circles, so the two in the bottom right were actually the right choice.True, both of these are fixed with the iterative approach I moved my code to in the edited post.
Thanks for sharing your cool solution. Piggybacking off of this comment thread as I'm trying to understand your code before I attempt my impl in js. I get your overarching explanation but the code seemed magical to me at first. After pouring over it, I think I get it now. Let me see if I understand it right...
The find function starts off with the largest grid size which is roughly based on the max distance separating the bots in any of the x,y,z axes. The function's job is to
find the points that 1) meet a minimum threshold of bots that are in range (forced_count) and based off those points2) find the point closest to the origin. The point closest to the origin is found by recursively halving the grid size (dist) as well as the range of points to be searched until it's narrowed it down to the actual closest point. This is the first application of binary search. But this only solves half of the problem which is 'find the closest point to the origin with N bots that are in range'. edit: I was way off base there. Missed the crucial part that isSo when the grid size halves, it chops the box into 8 smaller boxes and each sub-box counts the bot if its signal intersects with it. Still not sure what the -3 is doing in 'if calc //dist - 3 <= (bdist) // dist:'? edit2: seems this number is chosen to handle edge cases where the bot signal has a border/s lying on the edge of sub-boxes. Eg. a sub-box at 0,0,0 with dist=64 and a bot at 63,64,63 with signal radius=1. This bot may impact the sub-box when it has to calculate the bots that are in range, so it gets counted in it.
The calc2 function's while loop solves the rest of the problem which is 'find the closest point to the origin with the most bots within range' by starting with finding the points that have 1 bot in range then trying to see if there are points where all the bots are in range. If there aren't any, it asks whether there are any points that have half as many bots in range (by calling find() with forced_check set to this bot threshold). If it succeeds in finding a point, it picks a number halfway higher. If it fails, then it picks a number halfway lower. This is the second application of binary search.
I guess what was confusing at first was that there's actually 2 binary searches going on, one to narrow in on the closest point to origin and the second, to find the most bots in range. That and the variable names... :P
edit3: Figured it out and ported to JS :D
Thanks for the help and explanation on this one.
I took your idea and implemented it as a binary search.
At each stage, I get a point-bot intersection count for the center of the region and use that to keep a current best solution. I then divide the region into two disjoint halves along the longest dimension, and get the bot intersection count for each. If either, or both, of the halves intersect at least as many bots as my current best point, I push them onto a stack of regions to continue exploring. For the next iteration, I choose the region with the most intersecting bots, skipping any that have fewer than my current best point.
In Perl, that solved my data set in 1.2 seconds.
First solution from this thread that works for both of my inputs. Congrats!
This worked for my input, but I'm wondering if it's just another lucky solution that happens to be lucky for mine.
It does not work for my input :) [or AOC has it wrong]
I doubt that AOC has it wrong, although it has happened before (day 6). I get different answers when I run answers by different people, none of them ended up being the answer that was correct.
Yeah, they didn't, I just had a nasty edge case for the kind of solver I was writing, it had to be not-off-by-one at all to not get to the wrong local maxima. Another problem set helped resolve it!
I used this for my part2. It gave the correct answer though others have said it didn't work for them. Implemented in Rust. https://github.com/mfs/aoc-2018/blob/master/src/bin/p23.rs