using System.Collections.Concurrent; using System.Drawing; using Spectre.Console; namespace AdventOfCode.Days; public class SensorBeaconPair { public Point SensorPosition { get; } public Point BeaconPosition { get; } public int SensorRange => Math.Abs(SensorPosition.X - BeaconPosition.X) + Math.Abs(SensorPosition.Y - BeaconPosition.Y); public SensorBeaconPair(in Point sensorPosition, in Point beaconPosition) { SensorPosition = sensorPosition; BeaconPosition = beaconPosition; } } public class CoverRange { public int Start { get; } public int End { get; } public int Covered => End - Start + 1; public CoverRange(int start, int end) { Start = start; End = end; } public CoverRange(CoverRange coverRange) { Start = coverRange.Start; End = coverRange.End; } public CoverRange Subtract(CoverRange other) { // Overlap on left int newStart; if (other.Start <= Start && other.End >= Start) { newStart = other.End + 1; } else { newStart = Start; } // Overlap on right int newEnd; if (other.Start <= End && other.End >= End) { newEnd = other.Start - 1; } else { newEnd = End; } return new CoverRange(newStart, newEnd); } public static CoverRange operator -(CoverRange left, CoverRange right) { return left.Subtract(right); } } public class Day15 : Day { public override int Number => 15; public override string Name => "Beacon Exclusion Zone"; public override void RunPart1(bool display = true) { const int targetY = 2_000_000; // Parse pairs var pairs = ParsePairs(); var ranges = new List(pairs.Count); // Get which columns are included on line targetY, remove the ones which already contains a beacon var beaconsOnTargetY = new HashSet(); foreach (var sensorBeaconPair in pairs) { // Get covered columns on y line var xCenter = sensorBeaconPair.SensorPosition.X; var coveredColumns = sensorBeaconPair.SensorRange - Math.Abs(sensorBeaconPair.SensorPosition.Y - targetY); if (coveredColumns > 0) { ranges.Add(new CoverRange(xCenter - coveredColumns, xCenter + coveredColumns)); // Check if the beacon is on targetY if (sensorBeaconPair.BeaconPosition.Y == targetY) { beaconsOnTargetY.Add(sensorBeaconPair.BeaconPosition.X); } } } // Remove overlapped ranges => inner.Start >= outer.Start && inner.End <= outer.End)); ranges.RemoveAll(inner => ranges.Where(outer => outer != inner).Any(outer => inner.Start >= outer.Start && inner.End <= outer.End)); // Compute unique number of x columns long totalCovered = 0; for (int i = 0; i < ranges.Count; i++) { var range = ranges[i]; var coverRange = range; // Add number of covered columns and then subtract common ones for (int j = i + 1; j < ranges.Count; j++) { var exclude = ranges[j]; coverRange -= exclude; } totalCovered += coverRange.Covered; } // Remove lines which contains a beacon totalCovered -= beaconsOnTargetY.Count; // Print total covered columns on line Y if (display) { AnsiConsole.MarkupLine($"[green]Number of positions that cannot contain a beacon: [yellow]{totalCovered}[/][/]"); } } public override void RunPart2(bool display = true) { const int xMin = 0; const int xMax = 4_000_000; const int yMin = 0; const int yMax = 4_000_000; bool IsOutbound(Point point) => point.X is < xMin or > xMax || point.Y is < yMin or > yMax; // Parse pairs var pairs = ParsePairs(); // Find all points that are just outside a sensor range var possiblePoints = new ConcurrentBag(); Parallel.For(0, pairs.Count, i => { var pair = pairs[i]; var sensor = pair.SensorPosition; var distance = pair.SensorRange + 1; for (int x = -distance; x <= distance; x++) { var y = distance - Math.Abs(x); var positivePoint = new Point(sensor.X + x, sensor.Y + y); var negativePoint = new Point(sensor.X + x, sensor.Y - y); // If both points are outbound, just skip var positiveIsOutbound = IsOutbound(positivePoint); var negativeIsOutbound = IsOutbound(negativePoint); if (positiveIsOutbound && negativeIsOutbound) { continue; } // Check if this point is just outside at least one other sensor range for (int otherIndex = 0; otherIndex < pairs.Count; otherIndex++) { if (otherIndex == i) { continue; } var otherPair = pairs[otherIndex]; var otherSensor = otherPair.SensorPosition; var otherDistance = otherPair.SensorRange + 1; if (!positiveIsOutbound) { var distancePositive = Math.Abs(positivePoint.X - otherSensor.X) + Math.Abs(positivePoint.Y - otherSensor.Y); if (distancePositive == otherDistance) { possiblePoints.Add(positivePoint); } } if (!negativeIsOutbound) { var distanceNegative = Math.Abs(negativePoint.X - negativePoint.X) + Math.Abs(negativePoint.Y - negativePoint.Y); if (distanceNegative == otherDistance) { possiblePoints.Add(negativePoint); } } } } }); // Keep the only outside detection point var finalPoint = possiblePoints.First(f => pairs.All(p => Math.Abs(f.X - p.SensorPosition.X) + Math.Abs(f.Y - p.SensorPosition.Y) > p.SensorRange)); var tuningFrequency = finalPoint.X * (long) 4_000_000 + finalPoint.Y; if (display) { AnsiConsole.MarkupLine($"[green]Tuning frequency: [yellow]{tuningFrequency}[/][/]"); } } private static IList ParsePairs() { var pairs = new List(); foreach (var line in Input.ReadAllLines()) { var span = line.AsSpan(); // Parse sensor position int sensorXStart = span.IndexOf('=') + 1; int sensorXEnd = span.IndexOf(','); var sensorX = int.Parse(span[sensorXStart..sensorXEnd]); span = span[sensorXEnd..]; int sensorYStart = span.IndexOf('=') + 1; int sensorYEnd = span.IndexOf(':'); var sensorY = int.Parse(span[sensorYStart..sensorYEnd]); span = span[sensorYEnd..]; // Parse beacon int beaconXStart = span.IndexOf('=') + 1; int beaconXEnd = span.IndexOf(','); var beaconX = int.Parse(span[beaconXStart..beaconXEnd]); span = span[beaconXEnd..]; int beaconYStart = span.IndexOf('=') + 1; var beaconY = int.Parse(span[beaconYStart..]); pairs.Add(new SensorBeaconPair(new Point(sensorX, sensorY), new Point(beaconX, beaconY))); } return pairs; } #region Input public const string Input = """ Sensor at x=1384790, y=3850432: closest beacon is at x=2674241, y=4192888 Sensor at x=2825953, y=288046: closest beacon is at x=2154954, y=-342775 Sensor at x=3553843, y=2822363: closest beacon is at x=3444765, y=2347460 Sensor at x=2495377, y=3130491: closest beacon is at x=2761496, y=2831113 Sensor at x=1329263, y=1778185: closest beacon is at x=2729595, y=2000000 Sensor at x=2882039, y=2206085: closest beacon is at x=2729595, y=2000000 Sensor at x=3903141, y=2510440: closest beacon is at x=4006219, y=3011198 Sensor at x=3403454, y=3996578: closest beacon is at x=3754119, y=4475047 Sensor at x=3630476, y=1048796: closest beacon is at x=3444765, y=2347460 Sensor at x=16252, y=2089672: closest beacon is at x=-276514, y=2995794 Sensor at x=428672, y=1150723: closest beacon is at x=-281319, y=668868 Sensor at x=2939101, y=3624676: closest beacon is at x=2674241, y=4192888 Sensor at x=3166958, y=2890076: closest beacon is at x=2761496, y=2831113 Sensor at x=3758241, y=3546895: closest beacon is at x=4006219, y=3011198 Sensor at x=218942, y=3011070: closest beacon is at x=-276514, y=2995794 Sensor at x=52656, y=3484635: closest beacon is at x=-276514, y=2995794 Sensor at x=2057106, y=405314: closest beacon is at x=2154954, y=-342775 Sensor at x=1966905, y=2495701: closest beacon is at x=2761496, y=2831113 Sensor at x=511976, y=2696731: closest beacon is at x=-276514, y=2995794 Sensor at x=3094465, y=2478570: closest beacon is at x=3444765, y=2347460 Sensor at x=806671, y=228252: closest beacon is at x=-281319, y=668868 Sensor at x=3011731, y=1976307: closest beacon is at x=2729595, y=2000000 """; #endregion }