diff --git a/Days/Day14.cs b/Days/Day14.cs index e8bc57f..383964b 100644 --- a/Days/Day14.cs +++ b/Days/Day14.cs @@ -118,7 +118,7 @@ public class Day14 : Day public override int Number => 14; public override string Name => "Regolith Reservoir"; - private Point _sandSource = new(500, 0); + private readonly Point _sandSource = new(500, 0); private Point _offsetSandSource; public override void RunPart1() @@ -161,7 +161,7 @@ public class Day14 : Day AnsiConsole.MarkupLine($"[green]Total sand blocks: [yellow]{sandBlocksCount}[/][/]"); } - private bool GenerateSand(CellType[,] map, Canvas canvas, in Point sandSource) + private static bool GenerateSand(CellType[,] map, Canvas canvas, in Point sandSource) { // Stop if source is blocked if (map[sandSource.X, sandSource.Y] == CellType.Sand) @@ -230,7 +230,7 @@ public class Day14 : Day return true; } - private bool GenerateSand(IDictionary map, in Point sandSource, int groundY) + private static bool GenerateSand(IDictionary map, in Point sandSource, int groundY) { // Stop if source is blocked if (map.GetValueOrDefault(sandSource, CellType.Air) == CellType.Sand) @@ -374,7 +374,7 @@ public class Day14 : Day }; } - private IDictionary GenerateDictionaryMap() + private static IDictionary GenerateDictionaryMap() { var paths = Input.ReadAllLines() .Select(line => new MapPath(line.Split(" -> ") diff --git a/Days/Day15.cs b/Days/Day15.cs new file mode 100644 index 0000000..993e2d7 --- /dev/null +++ b/Days/Day15.cs @@ -0,0 +1,286 @@ +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() + { + 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 + AnsiConsole.MarkupLine($"[green]Number of positions that cannot contain a beacon: [yellow]{totalCovered}[/][/]"); + } + + public override void RunPart2() + { + 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; + + 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 +} \ No newline at end of file