// 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 Sensors { get; set; } = new Dictionary(); public Dictionary Beacons { get; set; } = new Dictionary(); 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 searchSegments = new List(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 coverage = new Dictionary(); 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 coverage = new Dictionary(); 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(); } }