413 lines
14 KiB
C#
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();
|
|
}
|
|
} |