Files
AdventOfCode/2022/Day15CSharp/Program.cs
2025-11-30 20:28:10 -05:00

413 lines
14 KiB
C#

// See https://aka.ms/new-console-template for more information
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography.X509Certificates;
Console.WriteLine("!! === Part 01 === !!");
Console.WriteLine("=== Example Data ===");
var exampleData = new DataSet("example-input.txt");
//exampleData.Print();
Console.WriteLine("Coverage on row {0}: {1}", 10, exampleData.CalculateCoverage(10));
Console.WriteLine();
Console.WriteLine("=== Puzzle Data ===");
var puzzleData = new DataSet("puzzle-input.txt");
Console.WriteLine("Coverage on row {0}: {1}", 2000000, puzzleData.CalculateCoverage(2000000));
Console.WriteLine("!! === Part 02 === !!");
Console.WriteLine("Example Tuning Frequency: " + exampleData.GetTuningFrequencyQuadSearch(20,1));
Console.WriteLine("Example Tuning Frequency: " + exampleData.GetTuningFrequencyQuadSearch(20, 4));
Console.WriteLine();
Stopwatch sw = Stopwatch.StartNew();
var result = puzzleData.GetTuningFrequencyQuadSearch(4000000, 10);
sw.Stop();
Console.Write("Puzzle Tuning Frequency: {0} in {1} milliseconds ", result, sw.ElapsedMilliseconds);
class Beacon
{
public Beacon(int x, int y)
{
Point = new Point(x, y);
}
public Point Point { get; }
}
class Sensor
{
public Sensor(int x, int y, Beacon closestBeacon)
{
Point = new Point(x, y);
ClosestBeacon = closestBeacon;
MaxDistance = ManhattanDistance(ClosestBeacon.Point, this.Point);
}
public Point Point { get; }
public Beacon ClosestBeacon { get; }
public int MaxDistance { get; }
public bool Covers(Rectangle r)
{
Point topLeft = new Point(r.X, r.Y);
Point topRight = new Point(r.Right, r.Y);
Point bottomLeft = new Point(r.X, r.Bottom);
Point bottomRight = new Point(r.Right, r.Bottom);
return Covers(topLeft) && Covers(topRight) && Covers(bottomLeft) && Covers(bottomRight);
}
public bool Covers(Point p)
{
return ManhattanDistance(p, this.Point) <= MaxDistance;
}
private int ManhattanDistance(Point l, Point r)
{
return Math.Abs(l.X - r.X) + Math.Abs(l.Y - r.Y);
}
}
class DataSet
{
public Dictionary<Point, Sensor> Sensors { get; set; } = new Dictionary<Point, Sensor>();
public Dictionary<Point, Beacon> Beacons { get; set; } = new Dictionary<Point, Beacon>();
public Rectangle Bounds { get; set; }
private int ManhattanDistance(Point l, Point r)
{
return Math.Abs(l.X - r.X) + Math.Abs(l.Y - r.Y);
}
public BigInteger GetTuningFrequencyFromX(int x, int y)
{
return ((BigInteger)x * (BigInteger)4000000) + y;
}
public BigInteger GetTuningFrequencyQuadSearch(int maxValue, int minSearchSize)
{
BigInteger toRet = -1;
Rectangle searchSpace = new Rectangle(0, 0, maxValue, maxValue);
var result = GetMissingXQuadSearch(searchSpace, minSearchSize);
if (result.X == -1) throw new InvalidDataException();
return GetTuningFrequencyFromX(result.X, result.Y);
}
private Point GetMissingXQuadSearch(Rectangle searchSpace, int minSearchSize, bool printProgress = false)
{
if (printProgress) Console.WriteLine("Searching searchspace: " + searchSpace);
bool covered = false;
foreach (var sensor in Sensors.Values)
{
if (sensor.Covers(searchSpace))
{
covered = true;
break;
}
}
if (covered)
{
if (printProgress) Console.WriteLine("Searchspace is covered: " + searchSpace);
return new Point(-1,-1);
}
if (searchSpace.Height > minSearchSize)
{
var result = new Point(-1,-1);
// Split the rectangle into 4 and search that
// topleft
Rectangle newSearchSpace = new Rectangle(searchSpace.X, searchSpace.Y, searchSpace.Width / 2, searchSpace.Height / 2);
result = GetMissingXQuadSearch(newSearchSpace, minSearchSize);
if (result.X != -1) return result;
// shift right
newSearchSpace.Offset(searchSpace.Width / 2, 0);
result = GetMissingXQuadSearch(newSearchSpace, minSearchSize);
if (result.X != -1) return result;
// shift down
newSearchSpace.Offset(0, searchSpace.Height / 2);
result = GetMissingXQuadSearch(newSearchSpace, minSearchSize);
if (result.X != -1) return result;
// shift left
newSearchSpace.Offset(-1*(searchSpace.Width / 2), 0);
result = GetMissingXQuadSearch(newSearchSpace, minSearchSize);
if (result.X != -1) return result;
}
else
{
if (printProgress) Console.WriteLine("Brute searching space " + searchSpace);
for (int row = searchSpace.Top; row <= searchSpace.Bottom; row++)
{
int result = GetMissingBeaconX(row, searchSpace.Left, searchSpace.Right, false);
if (result != -1) return new Point(result, row);
}
}
return new Point(-1, -1);
}
public int GetMissingBeaconX(int row, int minX = int.MinValue, int maxX = int.MaxValue, bool useSegments = true)
{
const int numberOfSegments = 10;
List<int> searchSegments = new List<int>(numberOfSegments);
int segmentLength = maxX / numberOfSegments;
if (useSegments)
{
for (int i = 0; i < numberOfSegments; i++)
{
searchSegments.Add(i * segmentLength);
}
if (searchSegments[searchSegments.Count - 1] + segmentLength != maxX) throw new InvalidDataException();
// Cut down the search space first
foreach (var sensor in Sensors.Values)
{
int maxDistance = ManhattanDistance(sensor.ClosestBeacon.Point, sensor.Point);
for (int i = 0; i < searchSegments.Count; i++)
{
Point left = new Point(searchSegments[i], row);
Point right = new Point(searchSegments[i] + segmentLength, row);
if (ManhattanDistance(left, sensor.Point) <= maxDistance && ManhattanDistance(right, sensor.Point) <= maxDistance)
{
//Console.WriteLine("skipping {0} to {1}", left.X, right.X);
searchSegments.RemoveAt(i);
}
}
if (searchSegments.Count == 0)
return -1;
}
}
else
{
searchSegments.Add(minX);
segmentLength = maxX - minX;
}
foreach (var searchSegment in searchSegments)
{
Dictionary<int, bool> coverage = new Dictionary<int, bool>();
int leftX = searchSegment;
int rightX = searchSegment + segmentLength;
foreach (var sensor in Sensors.Values)
{
int startingX;
if (sensor.Point.X <= rightX && sensor.Point.X >= leftX)
{
startingX = sensor.Point.X;
}
else if (sensor.Point.X >= rightX)
{
startingX = rightX;
}
else
{
startingX = leftX;
}
Point startingPoint = new Point(startingX, row);
int maxDistance = ManhattanDistance(sensor.ClosestBeacon.Point, sensor.Point);
// From start, go left
Point currentPoint = new Point(startingPoint.X, row);
while (currentPoint.X >= leftX && ManhattanDistance(currentPoint, sensor.Point) <= maxDistance)
{
coverage[currentPoint.X] = true;
currentPoint.X--;
if (coverage.Count == (rightX - leftX) + 1)
{
break;
}
}
// From start, go right
currentPoint = new Point(startingPoint.X, row);
while (currentPoint.X <= rightX && ManhattanDistance(currentPoint, sensor.Point) <= maxDistance)
{
coverage[currentPoint.X] = true;
currentPoint.X++;
if (coverage.Count == (rightX - leftX) + 1)
{
break;
}
}
if (coverage.Count == (rightX - leftX) + 1)
{
break;
}
}
if (coverage.Count != (rightX - leftX) + 1)
{
for (int i = leftX; i <= rightX; i++)
{
if (!coverage.ContainsKey(i))
return i;
}
throw new InvalidDataException();
}
}
return -1;
}
public int CalculateCoverage(int row, int minX = int.MinValue, int maxX = int.MaxValue)
{
Dictionary<Point, bool> coverage = new Dictionary<Point, bool>();
foreach (var sensor in Sensors.Values)
{
// Check straight down
Point startingPoint = new Point(sensor.Point.X, row);
int maxDistance = ManhattanDistance(sensor.ClosestBeacon.Point, sensor.Point);
// From start, go left
Point currentPoint = new Point(startingPoint.X, startingPoint.Y);
while (currentPoint.X >= minX && ManhattanDistance(currentPoint, sensor.Point) <= maxDistance)
{
if (!Beacons.ContainsKey(currentPoint))
{
coverage[currentPoint] = true;
}
currentPoint.X--;
}
// From start, go right
currentPoint = new Point(startingPoint.X, startingPoint.Y);
while (currentPoint.X <= maxX && ManhattanDistance(currentPoint, sensor.Point) <= maxDistance)
{
if (!Beacons.ContainsKey(currentPoint))
{
coverage[currentPoint] = true;
}
currentPoint.X++;
}
}
return coverage.Count;
}
public void Print()
{
int x_min = Bounds.Left, x_max = x_min + Bounds.Width, y_min = Bounds.Top, y_max = y_min + Bounds.Height;
for (int y = y_min; y <= y_max; y++)
{
for (int x = x_min; x <= x_max; x++)
{
var point = new Point(x, y);
if (Sensors.ContainsKey(point))
{
Console.Write("S");
}
else if (Beacons.ContainsKey(point))
{
Console.Write("B");
}
else
{
Console.Write('.');
}
}
Console.WriteLine();
}
}
private void FindBounds()
{
int x_min = int.MaxValue;
int x_max = int.MinValue;
int y_min = 0; // sand always starts at 0
int y_max = int.MinValue;
foreach (var item in Sensors)
{
x_min = Int32.Min(item.Key.X, x_min);
x_max = Int32.Max(item.Key.X, x_max);
y_min = Int32.Min(item.Key.Y, y_min);
y_max = Int32.Max(item.Key.Y, y_max);
}
foreach (var item in Beacons)
{
x_min = Int32.Min(item.Key.X, x_min);
x_max = Int32.Max(item.Key.X, x_max);
y_min = Int32.Min(item.Key.Y, y_min);
y_max = Int32.Max(item.Key.Y, y_max);
}
Bounds = new Rectangle(x_min, y_min, x_max - x_min, y_max - y_min);
}
public DataSet(string filename)
{
using (StreamReader reader = System.IO.File.OpenText(filename))
{
while (!reader.EndOfStream)
{
string? line = reader.ReadLine();
if (line == null) throw new InvalidDataException();
Beacon? beacon = null;
// Beacon
{
string right = line.Split(':')[1];
string x_str = right.Split("x=")[1].Substring(0, right.Split("x=")[1].IndexOf(','));
string y_str = right.Split("y=")[1];
beacon = new Beacon(Int32.Parse(x_str), Int32.Parse(y_str));
}
if (!Beacons.ContainsKey(beacon.Point))
{
Beacons[beacon.Point] = beacon;
}
// Sensor
{
string left = line.Split(':')[0];
string x_str = left.Split("x=")[1].Substring(0, left.Split("x=")[1].IndexOf(','));
string y_str = left.Split("y=")[1];
Sensor sensor = new Sensor(Int32.Parse(x_str), Int32.Parse(y_str), beacon);
Sensors[sensor.Point] = sensor;
}
}
}
FindBounds();
}
}