292 lines
9.7 KiB
C#
292 lines
9.7 KiB
C#
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<CoverRange>(pairs.Count);
|
|
|
|
// Get which columns are included on line targetY, remove the ones which already contains a beacon
|
|
var beaconsOnTargetY = new HashSet<int>();
|
|
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<Point>();
|
|
|
|
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<SensorBeaconPair> ParsePairs()
|
|
{
|
|
var pairs = new List<SensorBeaconPair>();
|
|
|
|
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
|
|
} |