Given a pretty much 12-hour beer and rally session, neither of us rose early the next day.
Cue coffee, fry-up and hair of the dog (for me anyway, as I was already home and didn't have to drive anywhere.)
Didn't progress much as I was just enjoying catching up with my old friend, but all good things come to an end (like weekends) and he had to go.
So, on with the puzzle...
This is one that I think would have really benefited from a visualisation to help me reason about it, but I hadn't yet progressed to using MonoGame, and there was no chance realistically of doing this in the console.
There was a well telegraphed clue in the puzzle though:
I don't know whether there is a mathematic reason that 12 is the magic number, or whether that is just the way the puzzle was designed, and frankly I can't be arsed to find out, but it was a clue.
After a while, it dawned on me that I could use the distances between pairs of beacons detected by a scanner to determine overlaps.
If a pair of beacons detected by scanner A are the same distance apart as a pair of beacons detected by scanner B, then it makes sense that those are the same beacons.
Right, so now go through each scanner's data, and calculate each beacon's distance from every other beacon's distance:
_distances = new List<Distance>(); for (var ob = 0; ob < Beacons.Count; ob++) { for (var b = ob + 1; b < Beacons.Count; b++) { var outerBeacon = Beacons[ob]; var beacon = Beacons[b]; var distance = new Point(Math.Abs(outerBeacon.X - beacon.X), Math.Abs(outerBeacon.Y - beacon.Y), Math.Abs(outerBeacon.Z - beacon.Z)); _distances.Add(new Distance(distance, outerBeacon, beacon)); } }
Okay, so what next?
Part 1 expects the total number of beacons as the answer.
Looking at my GitHub history, it took me a couple of days to realise this, but once you have the identified beacons that are the same between scanners, it's quite simple. It is basically a union of the distances.
If scanner A sees 20 beacons and scanner B sees 18 beacons and 12 of them are the same beacon, then there must be 26 unique beacons.
I actually achieved this removing matching beacons from one of a pair of scanners, so we are left with a unique set of beacons. For instance, as stated above, if scanner A has 20 beacons, scanner B has 18 and 12 are the same, I'll remove that 12 from scanner B. When this is done, I can just count each scanner's beacons to get the answer.
for (var s1 = 0; s1 < Scanners.Count; s1++) { var originScanner = Scanners[s1]; for (var s2 = s1 + 1; s2 < Scanners.Count; s2++) { originScanner.RemoveMatchingBeacons(Scanners[s2]); } } var beaconCount = 0; for (var s = 0; s < Scanners.Count; s++) { beaconCount += Scanners[s].BeaconCount; }
RemoveMatchingBeacons involves a little more than just finding each pair of beacons.
The method looks like this.
public void RemoveMatchingBeacons(Scanner origin) { var matchingBeacons = GetMatchingDistances(origin); var beaconPairs = ResolveMatchingBeacons(matchingBeacons); Beacons.RemoveAll(b => beaconPairs.Any(p => p.Beacon2.Equals(b))); }
GetMatchingDistances is fairly straightforward, the meat of it being a method that finds matching beacons regardless off their orientation in 3D space.
return left.X == right.X && left.Y == right.Y && left.Z == right.Z || left.X == right.Y && left.Y == right.X && left.Z == right.Z || left.X == right.Z && left.Y == right.X && left.Z == right.Y || left.X == right.X && left.Y == right.Z && left.Z == right.Y || left.X == right.Y && left.Y == right.Z && left.Z == right.X || left.X == right.Z && left.Y == right.Y && left.Z == right.X;
If we know a pair of beacons detected by scanner A are the same distance apart as a pair of beacons detected by scanner B, we assume they are the same beacons. We now need to figure out how the pair from one scanner maps to the pair from the other, i.e. which are the same beacons? This is done in the method ResolveMatchingBeacons:
private static List<Pair> ResolveMatchingBeacons(List<DistancePair> matchingBeacons) { var pairs = new List<Pair>(); var distinctOrigins = matchingBeacons.Select(p => p.Origin.Beacon1) .Distinct() .Union(matchingBeacons.Select(p => p.Origin.Beacon2).Distinct()); foreach (var originBeacon in distinctOrigins) { var candidates = new List<Point>(); foreach (var candidatePair in matchingBeacons) { if (! (originBeacon.Equals(candidatePair.Origin.Beacon1) || originBeacon.Equals(candidatePair.Origin.Beacon2))) { continue; } if (candidates.Any(c => Equals(c, candidatePair.Target.Beacon1))) { pairs.Add(new Pair(originBeacon, candidatePair.Target.Beacon1)); break; } if (candidates.Any(c => Equals(c, candidatePair.Target.Beacon2))) { pairs.Add(new Pair(originBeacon, candidatePair.Target.Beacon2)); break; } candidates.Add(candidatePair.Target.Beacon1); candidates.Add(candidatePair.Target.Beacon2); } } return pairs; }
If I'm honest, I'm struggling to remember my thought process when implementing that particular routine. I know what it is doing, I know it works so I'll leave it as-is!
With this done, we are now able to count the number of unique scanners and answer the puzzle.
Phew, part 1 done.
What's part 2 asking? Let's see. Okay, seems reasonable...
This turned out to be a descent into madness of sorts. I didn't end up solving it until January. And, it turns out, I was pretty close most of the time but for a one line bug. Fudge.
