[2k25] Add day 1
This commit is contained in:
77
Days/Day1.cs
77
Days/Day1.cs
@@ -5,60 +5,83 @@ namespace AdventOfCode.Days;
|
||||
public class Day1 : Day
|
||||
{
|
||||
public override int Number => 1;
|
||||
public override string Name => "Historian Hysteria";
|
||||
public override string Name => "Secret Entrance";
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
List<int> leftNumbers = [];
|
||||
List<int> rightNumbers = [];
|
||||
var password = 0;
|
||||
var dialPosition = 50;
|
||||
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
foreach (var line in Input.EnumerateLines())
|
||||
{
|
||||
var separatorIndex = line.IndexOf(' ');
|
||||
var direction = line[0];
|
||||
var offset = int.Parse(line[1..]);
|
||||
|
||||
leftNumbers.Add(int.Parse(line[..separatorIndex]));
|
||||
rightNumbers.Add(int.Parse(line[(separatorIndex + 1)..]));
|
||||
var sign = direction is 'L'
|
||||
? -1
|
||||
: +1;
|
||||
|
||||
dialPosition = MathMod(dialPosition + (offset * sign), 100);
|
||||
|
||||
if (dialPosition is 0)
|
||||
{
|
||||
password++;
|
||||
}
|
||||
}
|
||||
|
||||
leftNumbers.Sort();
|
||||
rightNumbers.Sort();
|
||||
|
||||
var totalDistance = leftNumbers.Zip(rightNumbers, (a, b) => Math.Abs(a - b)).Sum();
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Total distance is: [yellow]{totalDistance}[/][/]");
|
||||
AnsiConsole.MarkupLine($"[green]Safe password is: [yellow]{password}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
List<int> leftNumbers = [];
|
||||
Dictionary<int, int> rightNumbersCount = [];
|
||||
var similarityScore = 0;
|
||||
var password = 0;
|
||||
var dialPosition = 50;
|
||||
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
foreach (var line in Input.EnumerateLines())
|
||||
{
|
||||
var separatorIndex = line.IndexOf(' ');
|
||||
var direction = line[0];
|
||||
var offset = int.Parse(line[1..]);
|
||||
|
||||
leftNumbers.Add(int.Parse(line[..separatorIndex]));
|
||||
var originalSign = Math.Sign(dialPosition);
|
||||
|
||||
var rightNumber = int.Parse(line[(separatorIndex + 1)..]);
|
||||
var sign = direction is 'L'
|
||||
? -1
|
||||
: +1;
|
||||
|
||||
if (!rightNumbersCount.TryAdd(rightNumber, 1))
|
||||
dialPosition = dialPosition + (offset * sign);
|
||||
|
||||
int clickCount;
|
||||
if (dialPosition is 0)
|
||||
{
|
||||
rightNumbersCount[rightNumber]++;
|
||||
clickCount = 1;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
clickCount = Math.Abs(dialPosition) / 100; // Number of times the dial passed by 0
|
||||
|
||||
foreach (var leftNumber in leftNumbers)
|
||||
{
|
||||
similarityScore += leftNumber * rightNumbersCount.GetValueOrDefault(leftNumber);
|
||||
// If we did a loop around it counts as a "click" too
|
||||
if (originalSign != 0 && originalSign != Math.Sign(dialPosition))
|
||||
{
|
||||
clickCount++;
|
||||
}
|
||||
}
|
||||
|
||||
dialPosition = MathMod(dialPosition, 100);
|
||||
|
||||
password += clickCount;
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Similarity score is: [yellow]{similarityScore}[/][/]");
|
||||
AnsiConsole.MarkupLine($"[green]Safe password is: [yellow]{password}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
private static int MathMod(int left, int right)
|
||||
{
|
||||
return (Math.Abs(left * right) + left) % right;
|
||||
}
|
||||
}
|
||||
169
Days/Day10.cs
169
Days/Day10.cs
@@ -1,169 +0,0 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Numerics;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day10 : Day
|
||||
{
|
||||
public override int Number => 10;
|
||||
public override string Name => "Hoof It";
|
||||
|
||||
private const int MapSize = 60;
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var hikingTrailsScores = 0;
|
||||
|
||||
var (heightMap, trailHeads) = ParseMap();
|
||||
|
||||
var hikingTrails = new HashSet<Point>();
|
||||
|
||||
foreach (var trailHead in trailHeads)
|
||||
{
|
||||
hikingTrails.Clear();
|
||||
|
||||
GetHikingTrailScore(trailHead, -1);
|
||||
|
||||
hikingTrailsScores += hikingTrails.Count;
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Sum of scores of hiking trails: [yellow]{hikingTrailsScores}[/][/]");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void GetHikingTrailScore(Point position, int previousHeight)
|
||||
{
|
||||
// Out of bounds
|
||||
if (position is { X: < 0 or >= MapSize } or { Y: < 0 or >= MapSize })
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var height = heightMap[position.X, position.Y];
|
||||
|
||||
if (height - previousHeight is not 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (height == 9)
|
||||
{
|
||||
hikingTrails.Add(position);
|
||||
}
|
||||
|
||||
|
||||
GetHikingTrailScore(position + (1, 0), height);
|
||||
GetHikingTrailScore(position + (-1, 0), height);
|
||||
GetHikingTrailScore(position + (0, 1), height);
|
||||
GetHikingTrailScore(position + (0, -1), height);
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
var (heightMap, trailHeads) = ParseMap();
|
||||
|
||||
var totalRating = 0;
|
||||
|
||||
foreach (var trailHead in trailHeads)
|
||||
{
|
||||
GetHikingTrailRating(trailHead, -1);
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Total rating of hiking trails: [yellow]{totalRating}[/][/]");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void GetHikingTrailRating(Point position, int previousHeight)
|
||||
{
|
||||
// Out of bounds
|
||||
if (position is { X: < 0 or >= MapSize } or { Y: < 0 or >= MapSize })
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var height = heightMap[position.X, position.Y];
|
||||
|
||||
if (height - previousHeight is not 1)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (height == 9)
|
||||
{
|
||||
totalRating++;
|
||||
}
|
||||
|
||||
GetHikingTrailRating(position + (1, 0), height);
|
||||
GetHikingTrailRating(position + (-1, 0), height);
|
||||
GetHikingTrailRating(position + (0, 1), height);
|
||||
GetHikingTrailRating(position + (0, -1), height);
|
||||
}
|
||||
}
|
||||
|
||||
private (byte[,] HeightMap, List<Point> TrailHeads) ParseMap()
|
||||
{
|
||||
var heightMap = new byte[MapSize, MapSize];
|
||||
var trailHeads = new List<Point>();
|
||||
|
||||
var y = 0;
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
var x = 0;
|
||||
foreach (var height in line)
|
||||
{
|
||||
var heightValue = height - '0';
|
||||
|
||||
if (heightValue is 0)
|
||||
{
|
||||
trailHeads.Add(new Point(x, y));
|
||||
}
|
||||
|
||||
heightMap[x, y] = (byte)heightValue;
|
||||
|
||||
x++;
|
||||
}
|
||||
|
||||
y++;
|
||||
}
|
||||
|
||||
return (heightMap, trailHeads);
|
||||
}
|
||||
|
||||
|
||||
private readonly record struct Point(int X, int Y)
|
||||
: IAdditionOperators<Point, Point, Point>,
|
||||
ISubtractionOperators<Point, Point, Point>,
|
||||
IMultiplyOperators<Point, int, Point>
|
||||
{
|
||||
public static Point operator +(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X + right.X, left.Y + right.Y);
|
||||
}
|
||||
|
||||
public static Point operator -(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X - right.X, left.Y - right.Y);
|
||||
}
|
||||
|
||||
public static Point operator *(Point left, int right)
|
||||
{
|
||||
return new Point(left.X * right, left.Y * right);
|
||||
}
|
||||
|
||||
public static Point operator *(int left, Point right)
|
||||
{
|
||||
return new Point(right.X * left, right.Y * left);
|
||||
}
|
||||
|
||||
public static implicit operator Point((int X, int Y) point)
|
||||
=> new(point.X, point.Y);
|
||||
}
|
||||
}
|
||||
127
Days/Day11.cs
127
Days/Day11.cs
@@ -1,127 +0,0 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Numerics;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day11 : Day
|
||||
{
|
||||
public override int Number => 11;
|
||||
public override string Name => "Plutonian Pebbles";
|
||||
|
||||
private const int BlinkIterations = 25;
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var stones = ParseStones();
|
||||
|
||||
for (var iteration = 0; iteration < BlinkIterations; iteration++)
|
||||
{
|
||||
var index = 0;
|
||||
|
||||
while (index < stones.Count)
|
||||
{
|
||||
var stone = stones[index];
|
||||
var stoneString = stone.ToString();
|
||||
|
||||
if (stone is 0)
|
||||
{
|
||||
stones[index] = 1;
|
||||
}
|
||||
else if (stoneString.Length % 2 == 0)
|
||||
{
|
||||
var splitIndex = stoneString.Length / 2;
|
||||
|
||||
var leftStone = long.Parse(stoneString[..splitIndex]);
|
||||
var rightStone = long.Parse(stoneString[splitIndex..]);
|
||||
|
||||
stones[index] = leftStone;
|
||||
stones.Insert(index + 1, rightStone);
|
||||
|
||||
index++;
|
||||
}
|
||||
else
|
||||
{
|
||||
stones[index] = stone * 2024;
|
||||
}
|
||||
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Number of stones after blinking {BlinkIterations} times: [yellow]{stones.Count}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
var stones = ParseStones().ToDictionary(s => s, _ => 1L);
|
||||
|
||||
for (var iteration = 0; iteration < (BlinkIterations * 3); iteration++)
|
||||
{
|
||||
var stonesToIterate = stones.ToList();
|
||||
|
||||
foreach (var (stone, count) in stonesToIterate)
|
||||
{
|
||||
var stoneString = stone.ToString();
|
||||
|
||||
if (stone is 0)
|
||||
{
|
||||
SafeAdd(stones, 1, count);
|
||||
}
|
||||
else if (stoneString.Length % 2 == 0)
|
||||
{
|
||||
var splitIndex = stoneString.Length / 2;
|
||||
|
||||
var leftStone = long.Parse(stoneString[..splitIndex]);
|
||||
var rightStone = long.Parse(stoneString[splitIndex..]);
|
||||
|
||||
SafeAdd(stones, leftStone, count);
|
||||
SafeAdd(stones, rightStone, count);
|
||||
}
|
||||
else
|
||||
{
|
||||
SafeAdd(stones, stone * 2024, count);
|
||||
|
||||
}
|
||||
|
||||
SafeAdd(stones, stone, -count);
|
||||
}
|
||||
}
|
||||
|
||||
var stonesCount = stones.Sum(p => p.Value);
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Number of stones after blinking {BlinkIterations} times: [yellow]{stonesCount}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
private List<long> ParseStones()
|
||||
{
|
||||
var stones = new List<long>();
|
||||
|
||||
var span = Input.AsSpan();
|
||||
|
||||
foreach (var stoneRange in span.Split(' '))
|
||||
{
|
||||
stones.Add(long.Parse(span[stoneRange]));
|
||||
}
|
||||
|
||||
return stones;
|
||||
}
|
||||
|
||||
private void SafeAdd(Dictionary<long, long> dictionary, long key, long valueToAdd)
|
||||
{
|
||||
if (dictionary.TryGetValue(key, out var currentValue))
|
||||
{
|
||||
dictionary[key] = currentValue + valueToAdd;
|
||||
}
|
||||
else
|
||||
{
|
||||
dictionary[key] = valueToAdd;
|
||||
}
|
||||
}
|
||||
}
|
||||
155
Days/Day12.cs
155
Days/Day12.cs
@@ -1,155 +0,0 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Numerics;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day12 : Day
|
||||
{
|
||||
public override int Number => 12;
|
||||
public override string Name => "Garden Groups";
|
||||
|
||||
private const int GridSize = 140;
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var areas = ParseAreas();
|
||||
|
||||
var visited = new HashSet<Point>();
|
||||
var totalPrice = 0;
|
||||
|
||||
var currentArea = 0;
|
||||
var currentPerimeter = 0;
|
||||
|
||||
for (var y = 0; y < GridSize; y++)
|
||||
{
|
||||
for (var x = 0; x < GridSize; x++)
|
||||
{
|
||||
// Skip already visited points
|
||||
if (visited.Contains(new Point(x, y)))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var id = areas[x, y];
|
||||
|
||||
ComputeAreaPrice(new Point(x, y), id, ref currentArea, ref currentPerimeter);
|
||||
|
||||
// Add price of fencing this area
|
||||
totalPrice += currentArea * currentPerimeter;
|
||||
|
||||
currentArea = 0;
|
||||
currentPerimeter = 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Total pricing: [yellow]{totalPrice}[/][/]");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void ComputeAreaPrice(Point position, byte id, ref int area, ref int perimeter)
|
||||
{
|
||||
if (!visited.Add(position))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Update area and perimeter
|
||||
area++;
|
||||
perimeter += 4;
|
||||
|
||||
var left = position with { X = position.X - 1 };
|
||||
var right = position with { X = position.X + 1 };
|
||||
var up = position with { Y = position.Y - 1 };
|
||||
var down = position with { Y = position.Y + 1 };
|
||||
|
||||
if (left is { X: >= 0 and < GridSize } && areas[left.X, left.Y] == id)
|
||||
{
|
||||
perimeter--;
|
||||
|
||||
ComputeAreaPrice(left, id, ref area, ref perimeter);
|
||||
}
|
||||
|
||||
if (right is { X: >= 0 and < GridSize } && areas[right.X, right.Y] == id)
|
||||
{
|
||||
perimeter--;
|
||||
|
||||
ComputeAreaPrice(right, id, ref area, ref perimeter);
|
||||
}
|
||||
|
||||
if (up is { Y: >= 0 and < GridSize } && areas[up.X, up.Y] == id)
|
||||
{
|
||||
perimeter--;
|
||||
|
||||
ComputeAreaPrice(up, id, ref area, ref perimeter);
|
||||
}
|
||||
|
||||
if (down is { Y: >= 0 and < GridSize } && areas[down.X, down.Y] == id)
|
||||
{
|
||||
perimeter--;
|
||||
|
||||
ComputeAreaPrice(down, id, ref area, ref perimeter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private byte[,] ParseAreas()
|
||||
{
|
||||
var areas = new byte[GridSize, GridSize];
|
||||
|
||||
int y = 0;
|
||||
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
int x = 0;
|
||||
|
||||
foreach (var area in line)
|
||||
{
|
||||
areas[x, y] = (byte)(area - 'A');
|
||||
|
||||
x++;
|
||||
}
|
||||
|
||||
y++;
|
||||
}
|
||||
|
||||
return areas;
|
||||
}
|
||||
|
||||
private readonly record struct Point(int X, int Y)
|
||||
: IAdditionOperators<Point, Point, Point>,
|
||||
ISubtractionOperators<Point, Point, Point>,
|
||||
IMultiplyOperators<Point, int, Point>
|
||||
{
|
||||
public static Point operator +(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X + right.X, left.Y + right.Y);
|
||||
}
|
||||
|
||||
public static Point operator -(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X - right.X, left.Y - right.Y);
|
||||
}
|
||||
|
||||
public static Point operator *(Point left, int right)
|
||||
{
|
||||
return new Point(left.X * right, left.Y * right);
|
||||
}
|
||||
|
||||
public static Point operator *(int left, Point right)
|
||||
{
|
||||
return new Point(right.X * left, right.Y * left);
|
||||
}
|
||||
|
||||
public static implicit operator Point((int X, int Y) point)
|
||||
=> new(point.X, point.Y);
|
||||
}
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day13 : Day
|
||||
{
|
||||
public override int Number => 13;
|
||||
public override string Name => "Claw Contraption";
|
||||
|
||||
private const int MaxButtonPress = 100;
|
||||
private const int ButtonACost = 3;
|
||||
private const int ButtonBCost = 1;
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var lines = Input.AsSpan().EnumerateLines();
|
||||
|
||||
var minimumTokensToWin = 0;
|
||||
|
||||
while (lines.MoveNext())
|
||||
{
|
||||
var (aX, aY, bX, bY, goalX, goalY) = ParseMachine(ref lines);
|
||||
|
||||
for (var i = MaxButtonPress; i >= 0; i--)
|
||||
{
|
||||
var xOffset = bX * i;
|
||||
|
||||
if (xOffset > goalX)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if ((goalX - xOffset) % aX != 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var timesAPressed = (goalX - xOffset) / aX;
|
||||
|
||||
if (bY * i + aY * timesAPressed != goalY)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
minimumTokensToWin += i * ButtonBCost + timesAPressed * ButtonACost;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Minimum tokens to win all prizes: [yellow]{minimumTokensToWin}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private (int aX, int aY, int bX, int bY, int goalX, int goalY) ParseMachine(ref SpanLineEnumerator lines)
|
||||
{
|
||||
var lineA = lines.Current;
|
||||
lines.MoveNext();
|
||||
|
||||
var lineB = lines.Current;
|
||||
lines.MoveNext();
|
||||
|
||||
var lineGoal = lines.Current;
|
||||
lines.MoveNext();
|
||||
|
||||
return (
|
||||
int.Parse(lineA[(lineA.IndexOf('+') + 1)..lineA.IndexOf(',')]),
|
||||
int.Parse(lineA[(lineA.LastIndexOf('+') + 1)..]),
|
||||
|
||||
int.Parse(lineB[(lineB.IndexOf('+') + 1)..lineB.IndexOf(',')]),
|
||||
int.Parse(lineB[(lineB.LastIndexOf('+') + 1)..]),
|
||||
|
||||
int.Parse(lineGoal[(lineGoal.IndexOf('=') + 1)..lineGoal.IndexOf(',')]),
|
||||
int.Parse(lineGoal[(lineGoal.LastIndexOf('=') + 1)..])
|
||||
);
|
||||
}
|
||||
}
|
||||
151
Days/Day14.cs
151
Days/Day14.cs
@@ -1,151 +0,0 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day14 : Day
|
||||
{
|
||||
public override int Number => 14;
|
||||
public override string Name => "Restroom Redoubt";
|
||||
|
||||
private const int Height = 103;
|
||||
private const int HeightCenter = Height / 2;
|
||||
|
||||
private const int Width = 101;
|
||||
private const int WidthCenter = Width / 2;
|
||||
|
||||
private const int Iterations = 100;
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var robots = ParseRobots();
|
||||
|
||||
for (var i = 0; i < Iterations; i++)
|
||||
{
|
||||
foreach (var robot in robots)
|
||||
{
|
||||
robot.Position = new Point(
|
||||
Math.Abs((Width + robot.Position.X + robot.Velocity.X) % Width),
|
||||
Math.Abs((Height + robot.Position.Y + robot.Velocity.Y) % Height)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute safety factor
|
||||
var safetyFactor =
|
||||
robots.Count(r => r.Position is { X: < WidthCenter, Y: < HeightCenter }) *
|
||||
robots.Count(r => r.Position is { X: > WidthCenter, Y: < HeightCenter }) *
|
||||
robots.Count(r => r.Position is { X: < WidthCenter, Y: > HeightCenter }) *
|
||||
robots.Count(r => r.Position is { X: > WidthCenter, Y: > HeightCenter });
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Safety factor: [yellow]{safetyFactor}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
var robots = ParseRobots();
|
||||
|
||||
var canvas = new Canvas(Width, Height);
|
||||
|
||||
for (var i = 0; i < int.MaxValue; i++)
|
||||
{
|
||||
for (var x = 0; x < Width; x++)
|
||||
{
|
||||
for (var y = 0; y < Height; y++)
|
||||
{
|
||||
canvas.SetPixel(x, y, Color.Grey23);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var robot in robots)
|
||||
{
|
||||
robot.Position = new Point(
|
||||
Math.Abs((Width + robot.Position.X + robot.Velocity.X) % Width),
|
||||
Math.Abs((Height + robot.Position.Y + robot.Velocity.Y) % Height)
|
||||
);
|
||||
|
||||
canvas.SetPixel(robot.Position.X, robot.Position.Y, Color.Green1);
|
||||
}
|
||||
|
||||
// Display grid
|
||||
if ((i + 1 - 46) % 101 == 0)
|
||||
{
|
||||
AnsiConsole.Write(canvas);
|
||||
AnsiConsole.WriteLine();
|
||||
|
||||
AnsiConsole.Write($"Seconds elapsed: {i + 1}");
|
||||
|
||||
var key = Console.ReadKey(true).Key;
|
||||
|
||||
if (key is ConsoleKey.Enter)
|
||||
{
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.WriteLine();
|
||||
AnsiConsole.MarkupLine($"[green]Image at instant: [yellow]t={i + 1}s[/][/]");
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
AnsiConsole.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Robot> ParseRobots()
|
||||
{
|
||||
var robots = new List<Robot>();
|
||||
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
var positionX = int.Parse(line[(line.IndexOf('=') + 1)..line.IndexOf(',')]);
|
||||
var positionY = int.Parse(line[(line.IndexOf(',') + 1)..line.IndexOf(' ')]);
|
||||
|
||||
var velocityX = int.Parse(line[(line.LastIndexOf('=') + 1)..line.LastIndexOf(',')]);
|
||||
var velocityY = int.Parse(line[(line.LastIndexOf(',') + 1)..]);
|
||||
|
||||
robots.Add(new Robot(new Point(positionX, positionY), new Point(velocityX, velocityY)));
|
||||
}
|
||||
|
||||
return robots;
|
||||
}
|
||||
|
||||
private class Robot(Point position, Point velocity)
|
||||
{
|
||||
public Point Position { get; set; } = position;
|
||||
public Point Velocity { get; } = velocity;
|
||||
}
|
||||
|
||||
private readonly record struct Point(int X, int Y)
|
||||
: IAdditionOperators<Point, Point, Point>,
|
||||
ISubtractionOperators<Point, Point, Point>,
|
||||
IMultiplyOperators<Point, int, Point>
|
||||
{
|
||||
public static Point operator +(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X + right.X, left.Y + right.Y);
|
||||
}
|
||||
|
||||
public static Point operator -(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X - right.X, left.Y - right.Y);
|
||||
}
|
||||
|
||||
public static Point operator *(Point left, int right)
|
||||
{
|
||||
return new Point(left.X * right, left.Y * right);
|
||||
}
|
||||
|
||||
public static Point operator *(int left, Point right)
|
||||
{
|
||||
return new Point(right.X * left, right.Y * left);
|
||||
}
|
||||
|
||||
public static implicit operator Point((int X, int Y) point)
|
||||
=> new(point.X, point.Y);
|
||||
}
|
||||
}
|
||||
193
Days/Day15.cs
193
Days/Day15.cs
@@ -1,193 +0,0 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Numerics;
|
||||
using System.Text;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day15 : Day
|
||||
{
|
||||
public override int Number => 15;
|
||||
public override string Name => "Warehouse Woes";
|
||||
|
||||
private const int Size = 50;
|
||||
|
||||
private const char Wall = '#';
|
||||
private const char Box = 'O';
|
||||
private const char Empty = '.';
|
||||
|
||||
private const char InstructionLeft = '<';
|
||||
private const char InstructionRight = '>';
|
||||
private const char InstructionUp = '^';
|
||||
private const char InstructionDown = 'v';
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var (map, position, instructions) = ParseMap();
|
||||
|
||||
foreach (var instruction in instructions)
|
||||
{
|
||||
var direction = instruction switch
|
||||
{
|
||||
InstructionLeft => new Point(-1, 0),
|
||||
InstructionRight => new Point(1, 0),
|
||||
InstructionUp => new Point(0, -1),
|
||||
InstructionDown => new Point(0, 1),
|
||||
_ => throw new ArgumentException($"Invalid instruction: {instruction}")
|
||||
};
|
||||
|
||||
var destination = position + direction;
|
||||
|
||||
// Destination is free, just move
|
||||
if (map[destination.X, destination.Y] is not (Wall or Box))
|
||||
{
|
||||
position = destination;
|
||||
}
|
||||
// Destination is a box, check if there is a free space further up and move accordingly
|
||||
else if (map[destination.X, destination.Y] is Box)
|
||||
{
|
||||
var pushTarget = destination + direction;
|
||||
|
||||
while (map[pushTarget.X, pushTarget.Y] is Box)
|
||||
{
|
||||
pushTarget += direction;
|
||||
}
|
||||
|
||||
// Cannot move anything since there is a wall
|
||||
if (map[pushTarget.X, pushTarget.Y] is Wall)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Move robot and boxes (only need to move first and last box of a chain)
|
||||
position = destination;
|
||||
|
||||
map[destination.X, destination.Y] = Empty;
|
||||
map[pushTarget.X, pushTarget.Y] = Box;
|
||||
}
|
||||
// Else destination is a wall, do nothing
|
||||
}
|
||||
|
||||
var coordinatesSum = 0;
|
||||
|
||||
// Count gps coordinates
|
||||
for (var x = 0; x < Size; x++)
|
||||
{
|
||||
for (var y = 0; y < Size; y++)
|
||||
{
|
||||
if (map[x, y] is Box)
|
||||
{
|
||||
coordinatesSum += 100 * y + x;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Sum of boxes GPS coordinates: [yellow]{coordinatesSum}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
private static void DisplayGrid(char[,] map)
|
||||
{
|
||||
for (var y = 0; y < Size; y++)
|
||||
{
|
||||
for (var x = 0; x < Size; x++)
|
||||
{
|
||||
AnsiConsole.Write(map[x, y]);
|
||||
}
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
}
|
||||
|
||||
AnsiConsole.WriteLine();
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private (char[,] Map, Point Start, List<char> Instructions) ParseMap()
|
||||
{
|
||||
var readingMap = true;
|
||||
|
||||
var map = new char[Size, Size];
|
||||
|
||||
Point start = default;
|
||||
|
||||
var instructions = new List<char>();
|
||||
|
||||
var y = 0;
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
if (line.IsWhiteSpace())
|
||||
{
|
||||
readingMap = false;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (readingMap)
|
||||
{
|
||||
var x = 0;
|
||||
foreach (var symbol in line)
|
||||
{
|
||||
map[x, y] = Empty;
|
||||
|
||||
if (symbol is Wall or Box)
|
||||
{
|
||||
map[x, y] = symbol;
|
||||
}
|
||||
else if (symbol is '@')
|
||||
{
|
||||
start = new Point(x, y);
|
||||
}
|
||||
|
||||
x++;
|
||||
}
|
||||
|
||||
y++;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var instruction in line)
|
||||
{
|
||||
instructions.Add(instruction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (map, start, instructions);
|
||||
}
|
||||
|
||||
private readonly record struct Point(int X, int Y)
|
||||
: IAdditionOperators<Point, Point, Point>,
|
||||
ISubtractionOperators<Point, Point, Point>,
|
||||
IMultiplyOperators<Point, int, Point>
|
||||
{
|
||||
public static Point operator +(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X + right.X, left.Y + right.Y);
|
||||
}
|
||||
|
||||
public static Point operator -(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X - right.X, left.Y - right.Y);
|
||||
}
|
||||
|
||||
public static Point operator *(Point left, int right)
|
||||
{
|
||||
return new Point(left.X * right, left.Y * right);
|
||||
}
|
||||
|
||||
public static Point operator *(int left, Point right)
|
||||
{
|
||||
return new Point(right.X * left, right.Y * left);
|
||||
}
|
||||
|
||||
public static implicit operator Point((int X, int Y) point)
|
||||
=> new(point.X, point.Y);
|
||||
}
|
||||
}
|
||||
289
Days/Day16.cs
289
Days/Day16.cs
@@ -1,289 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day16 : Day
|
||||
{
|
||||
public override int Number => 16;
|
||||
public override string Name => "Reindeer Maze";
|
||||
|
||||
private const int Size = 141;
|
||||
|
||||
private const char Wall = '#';
|
||||
private const char Empty = '.';
|
||||
private const char Start = 'S';
|
||||
private const char End = 'E';
|
||||
|
||||
private static readonly Point DirectionUp = new(0, -1);
|
||||
private static readonly Point DirectionDown = new(0, 1);
|
||||
private static readonly Point DirectionLeft = new(-1, 0);
|
||||
private static readonly Point DirectionRight = new(1, 0);
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var (maze, start, end) = ParseMaze();
|
||||
|
||||
var visited = new Dictionary<(Point Position, Point Direction), int>();
|
||||
|
||||
var minimumScore = int.MaxValue;
|
||||
|
||||
var toVisit = new Queue<(Point Position, Point Direction, int Score)>();
|
||||
|
||||
toVisit.Enqueue((start, DirectionRight, 0));
|
||||
|
||||
while (toVisit.Count > 0)
|
||||
{
|
||||
var (position, direction, score) = toVisit.Dequeue();
|
||||
|
||||
if (visited.TryGetValue((position, direction), out var savedScore) && savedScore <= score)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
visited[(position, direction)] = score;
|
||||
|
||||
// Reached end
|
||||
if (position == end)
|
||||
{
|
||||
if (score < minimumScore)
|
||||
{
|
||||
minimumScore = score;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try forward if there is no wall
|
||||
var destination = position + direction;
|
||||
|
||||
if (maze[destination.X, destination.Y] is not Wall)
|
||||
{
|
||||
toVisit.Enqueue((destination, direction, score + 1));
|
||||
}
|
||||
|
||||
// Also try changing direction
|
||||
toVisit.Enqueue((position, NextDirection(direction), score + 1000));
|
||||
toVisit.Enqueue((position, PreviousDirection(direction), score + 1000));
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Lowest score: [yellow]{minimumScore}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
var (maze, start, end) = ParseMaze();
|
||||
|
||||
var visited = new Dictionary<(Point Position, Point Direction), int>();
|
||||
|
||||
var minimumScore = int.MaxValue;
|
||||
|
||||
var toVisit = new Queue<(Point Position, Point Direction, int Score)>();
|
||||
|
||||
toVisit.Enqueue((start, DirectionRight, 0));
|
||||
|
||||
// First we need to find minimum score
|
||||
while (toVisit.Count > 0)
|
||||
{
|
||||
var (position, direction, score) = toVisit.Dequeue();
|
||||
|
||||
if (visited.TryGetValue((position, direction), out var savedScore) && savedScore <= score)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
visited[(position, direction)] = score;
|
||||
|
||||
// Reached end
|
||||
if (position == end)
|
||||
{
|
||||
if (score < minimumScore)
|
||||
{
|
||||
minimumScore = score;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try forward if there is no wall
|
||||
var destination = position + direction;
|
||||
|
||||
if (maze[destination.X, destination.Y] is not Wall)
|
||||
{
|
||||
toVisit.Enqueue((destination, direction, score + 1));
|
||||
}
|
||||
|
||||
// Also try changing direction
|
||||
toVisit.Enqueue((position, NextDirection(direction), score + 1000));
|
||||
toVisit.Enqueue((position, PreviousDirection(direction), score + 1000));
|
||||
}
|
||||
|
||||
// Now that we have minimum score, we need to find all the paths that lead to this score
|
||||
var toVisitPath = new Stack<(Point Position, Point Direction, int Score, List<Point> Path)>();
|
||||
toVisitPath.Push((start, DirectionRight, 0, []));
|
||||
|
||||
var minimumScorePaths = new List<List<Point>>();
|
||||
|
||||
while (toVisitPath.Count > 0)
|
||||
{
|
||||
var (position, direction, score, path) = toVisitPath.Pop();
|
||||
|
||||
if (score > minimumScore)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (visited.TryGetValue((position, direction), out var savedScore) && savedScore < score)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
path.Add(position);
|
||||
|
||||
// Reached end
|
||||
if (position == end)
|
||||
{
|
||||
minimumScorePaths.Add(path);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try forward if there is no wall
|
||||
var destination = position + direction;
|
||||
|
||||
if (maze[destination.X, destination.Y] is not Wall)
|
||||
{
|
||||
toVisitPath.Push((destination, direction, score + 1, path));
|
||||
}
|
||||
|
||||
// Also try changing direction
|
||||
toVisitPath.Push((position, NextDirection(direction), score + 1000, path.ToList()));
|
||||
toVisitPath.Push((position, PreviousDirection(direction), score + 1000, path.ToList()));
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Unique positions in optimal paths: [yellow]{minimumScorePaths.SelectMany(l => l).ToHashSet().Count}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
private (char[,] Maze, Point Start, Point end) ParseMaze()
|
||||
{
|
||||
var maze = new char[Size, Size];
|
||||
Point start = default;
|
||||
Point end = default;
|
||||
|
||||
var y = 0;
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
var x = 0;
|
||||
foreach (var symbol in line)
|
||||
{
|
||||
if (symbol is Start)
|
||||
{
|
||||
start = new Point(x, y);
|
||||
|
||||
maze[x, y] = Empty;
|
||||
}
|
||||
else if (symbol is End)
|
||||
{
|
||||
end = new Point(x, y);
|
||||
|
||||
maze[x, y] = Empty;
|
||||
}
|
||||
else
|
||||
{
|
||||
maze[x, y] = symbol;
|
||||
}
|
||||
|
||||
x++;
|
||||
}
|
||||
|
||||
y++;
|
||||
}
|
||||
|
||||
return (maze, start, end);
|
||||
}
|
||||
|
||||
private static Point NextDirection(Point direction)
|
||||
{
|
||||
if (direction == DirectionUp)
|
||||
{
|
||||
return DirectionRight;
|
||||
}
|
||||
|
||||
if (direction == DirectionRight)
|
||||
{
|
||||
return DirectionDown;
|
||||
}
|
||||
|
||||
if (direction == DirectionDown)
|
||||
{
|
||||
return DirectionLeft;
|
||||
}
|
||||
|
||||
if (direction == DirectionLeft)
|
||||
{
|
||||
return DirectionUp;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Invalid direction", nameof(direction));
|
||||
}
|
||||
|
||||
private static Point PreviousDirection(Point direction)
|
||||
{
|
||||
if (direction == DirectionUp)
|
||||
{
|
||||
return DirectionLeft;
|
||||
}
|
||||
|
||||
if (direction == DirectionLeft)
|
||||
{
|
||||
return DirectionDown;
|
||||
}
|
||||
|
||||
if (direction == DirectionDown)
|
||||
{
|
||||
return DirectionRight;
|
||||
}
|
||||
|
||||
if (direction == DirectionRight)
|
||||
{
|
||||
return DirectionUp;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Invalid direction", nameof(direction));
|
||||
}
|
||||
|
||||
private readonly record struct Point(int X, int Y)
|
||||
: IAdditionOperators<Point, Point, Point>,
|
||||
ISubtractionOperators<Point, Point, Point>,
|
||||
IMultiplyOperators<Point, int, Point>
|
||||
{
|
||||
public static Point operator +(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X + right.X, left.Y + right.Y);
|
||||
}
|
||||
|
||||
public static Point operator -(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X - right.X, left.Y - right.Y);
|
||||
}
|
||||
|
||||
public static Point operator *(Point left, int right)
|
||||
{
|
||||
return new Point(left.X * right, left.Y * right);
|
||||
}
|
||||
|
||||
public static Point operator *(int left, Point right)
|
||||
{
|
||||
return new Point(right.X * left, right.Y * left);
|
||||
}
|
||||
|
||||
public static implicit operator Point((int X, int Y) point)
|
||||
=> new(point.X, point.Y);
|
||||
}
|
||||
}
|
||||
247
Days/Day17.cs
247
Days/Day17.cs
@@ -1,247 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day17 : Day
|
||||
{
|
||||
public override int Number => 17;
|
||||
public override string Name => "Chronospatial Computer";
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var (registerA, registerB, registerC, program) = ParseState();
|
||||
|
||||
var toOutput = new List<int>();
|
||||
var instructionPointer = 0;
|
||||
|
||||
while (instructionPointer < program.Length)
|
||||
{
|
||||
var opCode = program[instructionPointer];
|
||||
|
||||
var isComboOperand = opCode switch
|
||||
{
|
||||
0 => true,
|
||||
1 => false,
|
||||
2 => true,
|
||||
3 => false,
|
||||
4 => false,
|
||||
5 => true,
|
||||
6 => true,
|
||||
7 => true,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(opCode))
|
||||
};
|
||||
|
||||
var operand = (isComboOperand, program[instructionPointer + 1]) switch
|
||||
{
|
||||
(false, var literalValue) => literalValue,
|
||||
(true, 0) => 0,
|
||||
(true, 1) => 1,
|
||||
(true, 2) => 2,
|
||||
(true, 3) => 3,
|
||||
(true, 4) => registerA,
|
||||
(true, 5) => registerB,
|
||||
(true, 6) => registerC,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
switch (opCode)
|
||||
{
|
||||
// adv
|
||||
case 0:
|
||||
registerA = (int)(registerA / Math.Pow(2, operand));
|
||||
break;
|
||||
|
||||
// bxl
|
||||
case 1:
|
||||
registerB = registerB ^ operand;
|
||||
break;
|
||||
|
||||
// bst
|
||||
case 2:
|
||||
registerB = operand % 8;
|
||||
break;
|
||||
|
||||
// jnz
|
||||
case 3:
|
||||
if (registerA is not 0)
|
||||
{
|
||||
instructionPointer = operand - 2;
|
||||
}
|
||||
break;
|
||||
|
||||
// bxc
|
||||
case 4:
|
||||
registerB = registerB ^ registerC;
|
||||
break;
|
||||
|
||||
// out
|
||||
case 5:
|
||||
toOutput.Add(operand % 8);
|
||||
break;
|
||||
|
||||
// bdv
|
||||
case 6:
|
||||
registerB = (int)(registerA / Math.Pow(2, operand));
|
||||
break;
|
||||
|
||||
// cdv
|
||||
case 7:
|
||||
registerC = (int)(registerA / Math.Pow(2, operand));
|
||||
break;
|
||||
}
|
||||
|
||||
instructionPointer += 2;
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Output: [yellow]{string.Join(',', toOutput)}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
var (_, originalRegisterB, originalRegisterC, program) = ParseState();
|
||||
var toOutput = new List<int>();
|
||||
var instructionPointer = 0;
|
||||
|
||||
var finalRegisterA = 0;
|
||||
|
||||
AnsiConsole.Status().Start("Computing (0)...", ctx =>
|
||||
{
|
||||
for (var initialRegisterA = 0; initialRegisterA < int.MaxValue; initialRegisterA++)
|
||||
{
|
||||
// Reset state
|
||||
var registerA = initialRegisterA;
|
||||
var registerB = originalRegisterB;
|
||||
var registerC = originalRegisterC;
|
||||
|
||||
toOutput.Clear();
|
||||
instructionPointer = 0;
|
||||
|
||||
while (instructionPointer < program.Length)
|
||||
{
|
||||
var opCode = program[instructionPointer];
|
||||
|
||||
var isComboOperand = opCode switch
|
||||
{
|
||||
0 => true,
|
||||
1 => false,
|
||||
2 => true,
|
||||
3 => false,
|
||||
4 => false,
|
||||
5 => true,
|
||||
6 => true,
|
||||
7 => true,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(opCode))
|
||||
};
|
||||
|
||||
var operand = (isComboOperand, program[instructionPointer + 1]) switch
|
||||
{
|
||||
(false, var literalValue) => literalValue,
|
||||
(true, 0) => 0,
|
||||
(true, 1) => 1,
|
||||
(true, 2) => 2,
|
||||
(true, 3) => 3,
|
||||
(true, 4) => registerA,
|
||||
(true, 5) => registerB,
|
||||
(true, 6) => registerC,
|
||||
_ => throw new ArgumentOutOfRangeException()
|
||||
};
|
||||
|
||||
switch (opCode)
|
||||
{
|
||||
// adv
|
||||
case 0:
|
||||
registerA = (int)(registerA / Math.Pow(2, operand));
|
||||
break;
|
||||
|
||||
// bxl
|
||||
case 1:
|
||||
registerB = registerB ^ operand;
|
||||
break;
|
||||
|
||||
// bst
|
||||
case 2:
|
||||
registerB = operand % 8;
|
||||
break;
|
||||
|
||||
// jnz
|
||||
case 3:
|
||||
if (registerA is not 0)
|
||||
{
|
||||
instructionPointer = operand - 2;
|
||||
}
|
||||
break;
|
||||
|
||||
// bxc
|
||||
case 4:
|
||||
registerB = registerB ^ registerC;
|
||||
break;
|
||||
|
||||
// out
|
||||
case 5:
|
||||
toOutput.Add(operand % 8);
|
||||
break;
|
||||
|
||||
// bdv
|
||||
case 6:
|
||||
registerB = (int)(registerA / Math.Pow(2, operand));
|
||||
break;
|
||||
|
||||
// cdv
|
||||
case 7:
|
||||
registerC = (int)(registerA / Math.Pow(2, operand));
|
||||
break;
|
||||
}
|
||||
|
||||
instructionPointer += 2;
|
||||
}
|
||||
|
||||
if (initialRegisterA % 1000 == 0)
|
||||
{
|
||||
ctx.Status($"Computing ({initialRegisterA})...");
|
||||
}
|
||||
|
||||
// Check if output is the program itself
|
||||
if (toOutput.SequenceEqual(program))
|
||||
{
|
||||
finalRegisterA = initialRegisterA;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Lowest possible value for register A to output itself: [yellow]{finalRegisterA}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
private (int RegisterA, int RegisterN, int RegisterC, int[] Program) ParseState()
|
||||
{
|
||||
var lineIterator = Input.AsSpan().EnumerateLines();
|
||||
|
||||
lineIterator.MoveNext();
|
||||
var registerA = int.Parse(lineIterator.Current[(lineIterator.Current.IndexOf(':') + 2)..]);
|
||||
|
||||
lineIterator.MoveNext();
|
||||
var registerB = int.Parse(lineIterator.Current[(lineIterator.Current.IndexOf(':') + 2)..]);
|
||||
|
||||
lineIterator.MoveNext();
|
||||
var registerC = int.Parse(lineIterator.Current[(lineIterator.Current.IndexOf(':') + 2)..]);
|
||||
|
||||
lineIterator.MoveNext();
|
||||
lineIterator.MoveNext();
|
||||
|
||||
var program = lineIterator.Current[(lineIterator.Current.IndexOf(':') + 2)..]
|
||||
.ToString()
|
||||
.Split(',')
|
||||
.Select(int.Parse)
|
||||
.ToArray();
|
||||
|
||||
return (registerA, registerB, registerC, program);
|
||||
}
|
||||
}
|
||||
204
Days/Day18.cs
204
Days/Day18.cs
@@ -1,204 +0,0 @@
|
||||
using System.Numerics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day18 : Day
|
||||
{
|
||||
public override int Number => 18;
|
||||
public override string Name => "RAM Run";
|
||||
|
||||
private const int Size = 71;
|
||||
|
||||
private const char Corrupted = '#';
|
||||
private const char Empty = ' ';
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var grid = ParseGrid(1024);
|
||||
|
||||
var visited = new Dictionary<Point, int>();
|
||||
var toVisit = new Queue<(Point Position, int Score)>();
|
||||
|
||||
var end = new Point(Size - 1, Size - 1);
|
||||
|
||||
var minimumScore = int.MaxValue;
|
||||
|
||||
toVisit.Enqueue((new Point(0, 0), 0));
|
||||
|
||||
while (toVisit.Count > 0)
|
||||
{
|
||||
var (position, score) = toVisit.Dequeue();
|
||||
|
||||
// Cannot go out of bounds
|
||||
if (position is { X: < 0 or >= Size } or { Y: < 0 or >= Size })
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cannot go onto a corrupted space
|
||||
if (grid[position.X, position.Y] is Corrupted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (visited.TryGetValue(position, out var savedScore) && savedScore <= score)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
visited[position] = score;
|
||||
|
||||
// End
|
||||
if (position == end)
|
||||
{
|
||||
if (score < minimumScore)
|
||||
{
|
||||
minimumScore = score;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
toVisit.Enqueue((position + (1, 0), score + 1));
|
||||
toVisit.Enqueue((position + (-1, 0), score + 1));
|
||||
toVisit.Enqueue((position + (0, 1), score + 1));
|
||||
toVisit.Enqueue((position + (0, -1), score + 1));
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Minimum number of steps to reach the exit: [yellow]{minimumScore}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
(int X, int Y)[] bytesToFall = Input.ReadAllLines()
|
||||
.Select(line => line.Split(','))
|
||||
.Select(split => (int.Parse(split[0]), int.Parse(split[1])))
|
||||
.ToArray();
|
||||
|
||||
var nextByteIndex = 1024;
|
||||
var grid = ParseGrid(1024);
|
||||
|
||||
while (true)
|
||||
{
|
||||
var nextByte = bytesToFall[nextByteIndex];
|
||||
|
||||
grid[nextByte.X, nextByte.Y] = Corrupted;
|
||||
|
||||
if (!IsGridCompletable())
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
nextByteIndex++;
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Grid will be blocked at byte n°{nextByteIndex} at position: [yellow]{bytesToFall[nextByteIndex]}[/][/]");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
bool IsGridCompletable()
|
||||
{
|
||||
var visited = new HashSet<Point>();
|
||||
var toVisit = new Queue<Point>();
|
||||
|
||||
var end = new Point(Size - 1, Size - 1);
|
||||
|
||||
toVisit.Enqueue(new Point(0, 0));
|
||||
|
||||
while (toVisit.Count > 0)
|
||||
{
|
||||
var position = toVisit.Dequeue();
|
||||
|
||||
// Cannot go out of bounds
|
||||
if (position is { X: < 0 or >= Size } or { Y: < 0 or >= Size })
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Cannot go onto a corrupted space
|
||||
if (grid[position.X, position.Y] is Corrupted)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!visited.Add(position))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// End
|
||||
if (position == end)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
toVisit.Enqueue(position + (1, 0));
|
||||
toVisit.Enqueue(position + (-1, 0));
|
||||
toVisit.Enqueue(position + (0, 1));
|
||||
toVisit.Enqueue(position + (0, -1));
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private char[,] ParseGrid(int limit)
|
||||
{
|
||||
var grid = new char[Size, Size];
|
||||
|
||||
var counter = 0;
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
if (counter >= limit)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
var x = int.Parse(line[..line.IndexOf(',')]);
|
||||
var y = int.Parse(line[(line.IndexOf(',') + 1)..]);
|
||||
|
||||
grid[x, y] = Corrupted;
|
||||
|
||||
counter++;
|
||||
}
|
||||
|
||||
return grid;
|
||||
}
|
||||
|
||||
private readonly record struct Point(int X, int Y)
|
||||
: IAdditionOperators<Point, Point, Point>,
|
||||
ISubtractionOperators<Point, Point, Point>,
|
||||
IMultiplyOperators<Point, int, Point>
|
||||
{
|
||||
public static Point operator +(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X + right.X, left.Y + right.Y);
|
||||
}
|
||||
|
||||
public static Point operator -(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X - right.X, left.Y - right.Y);
|
||||
}
|
||||
|
||||
public static Point operator *(Point left, int right)
|
||||
{
|
||||
return new Point(left.X * right, left.Y * right);
|
||||
}
|
||||
|
||||
public static Point operator *(int left, Point right)
|
||||
{
|
||||
return new Point(right.X * left, right.Y * left);
|
||||
}
|
||||
|
||||
public static implicit operator Point((int X, int Y) point)
|
||||
=> new(point.X, point.Y);
|
||||
}
|
||||
}
|
||||
179
Days/Day19.cs
179
Days/Day19.cs
@@ -1,179 +0,0 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Numerics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day19 : Day
|
||||
{
|
||||
public override int Number => 19;
|
||||
public override string Name => "Linen Layout";
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var (towels, patterns) = ParseInput();
|
||||
var impossiblePatterns = new HashSet<string>();
|
||||
|
||||
var possiblePatterns = 0;
|
||||
|
||||
foreach (var pattern in patterns)
|
||||
{
|
||||
// Check if it's possible to make this pattern
|
||||
if (CheckPattern(pattern))
|
||||
{
|
||||
possiblePatterns++;
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Number of designs that are possible: [yellow]{possiblePatterns}[/][/]");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
bool CheckPattern(ReadOnlySpan<char> pattern)
|
||||
{
|
||||
// Console.WriteLine($"Checking '{pattern}'");
|
||||
|
||||
if (pattern.Length is 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (impossiblePatterns.Contains(pattern.ToString()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var prefix = pattern[0];
|
||||
|
||||
if (!towels.TryGetValue(prefix, out var towelsList))
|
||||
{
|
||||
impossiblePatterns.Add(pattern.ToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var towel in towelsList)
|
||||
{
|
||||
if (pattern.StartsWith(towel) && CheckPattern(pattern[towel.Length..]))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
impossiblePatterns.Add(pattern.ToString());
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
var (towels, patterns) = ParseInput();
|
||||
var impossiblePatterns = new HashSet<string>();
|
||||
var knownPatterns = new Dictionary<string, long>();
|
||||
|
||||
var possibleCombinations = 0L;
|
||||
|
||||
foreach (var pattern in patterns)
|
||||
{
|
||||
// Check if it's possible to make this pattern
|
||||
possibleCombinations += CheckPattern(pattern);
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Number of combinations that are possible: [yellow]{possibleCombinations}[/][/]");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
long CheckPattern(ReadOnlySpan<char> pattern)
|
||||
{
|
||||
if (pattern.Length is 0)
|
||||
{
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (impossiblePatterns.Contains(pattern.ToString()))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (knownPatterns.TryGetValue(pattern.ToString(), out var count))
|
||||
{
|
||||
return count;
|
||||
}
|
||||
|
||||
var prefix = pattern[0];
|
||||
|
||||
if (!towels.TryGetValue(prefix, out var towelsList))
|
||||
{
|
||||
impossiblePatterns.Add(pattern.ToString());
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
var possibilities = 0L;
|
||||
|
||||
foreach (var towel in towelsList)
|
||||
{
|
||||
if (pattern.StartsWith(towel))
|
||||
{
|
||||
possibilities += CheckPattern(pattern[towel.Length..]);
|
||||
}
|
||||
}
|
||||
|
||||
if (possibilities is 0)
|
||||
{
|
||||
impossiblePatterns.Add(pattern.ToString());
|
||||
}
|
||||
else
|
||||
{
|
||||
knownPatterns.Add(pattern.ToString(), possibilities);
|
||||
}
|
||||
|
||||
return possibilities;
|
||||
}
|
||||
}
|
||||
|
||||
private (FrozenDictionary<char, List<string>> Towels, List<string> Patterns) ParseInput()
|
||||
{
|
||||
var towels = new Dictionary<char, List<string>>();
|
||||
var patterns = new List<string>();
|
||||
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
if (towels.Count is 0)
|
||||
{
|
||||
var split = line.Split(", ");
|
||||
|
||||
foreach (var range in split)
|
||||
{
|
||||
var towel = line[range].Trim();
|
||||
|
||||
var prefix = towel[0];
|
||||
|
||||
if (!towels.TryGetValue(prefix, out var towelsList))
|
||||
{
|
||||
towelsList = [];
|
||||
towels[prefix] = towelsList;
|
||||
}
|
||||
|
||||
towelsList.Add(towel.ToString());
|
||||
}
|
||||
}
|
||||
else if (!line.IsWhiteSpace())
|
||||
{
|
||||
var pattern = line.Trim();
|
||||
|
||||
patterns.Add(pattern.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
return (towels.ToFrozenDictionary(), patterns);
|
||||
}
|
||||
}
|
||||
139
Days/Day2.cs
139
Days/Day2.cs
@@ -1,139 +0,0 @@
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day2 : Day
|
||||
{
|
||||
public override int Number => 2;
|
||||
public override string Name => "Red-Nosed Reports";
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var safeReports = 0;
|
||||
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
var isSafe = true;
|
||||
var levels = line.Split(' ');
|
||||
|
||||
var previousDirection = 0;
|
||||
var previousLevel = int.MinValue;
|
||||
foreach (var levelRange in levels)
|
||||
{
|
||||
var level = int.Parse(line[levelRange]);
|
||||
|
||||
// First level, skip to next
|
||||
if (previousLevel == int.MinValue)
|
||||
{
|
||||
previousLevel = level;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check that direction is preserved
|
||||
var direction = level - previousLevel;
|
||||
|
||||
if (previousDirection != 0 && Math.Sign(direction) != Math.Sign(previousDirection))
|
||||
{
|
||||
isSafe = false;
|
||||
break;
|
||||
}
|
||||
|
||||
// Check that distance >= 1 and <= 3
|
||||
if (Math.Abs(direction) is < 1 or > 3)
|
||||
{
|
||||
isSafe = false;
|
||||
break;
|
||||
}
|
||||
|
||||
previousLevel = level;
|
||||
previousDirection = direction;
|
||||
}
|
||||
|
||||
if (isSafe)
|
||||
{
|
||||
safeReports++;
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Number of safe reports: [yellow]{safeReports}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
var safeReports = 0;
|
||||
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
if (IsSafe(line))
|
||||
{
|
||||
safeReports++;
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Number of safe reports: [yellow]{safeReports}[/][/]");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
bool IsSafe(ReadOnlySpan<char> line, Range? toSkip = null)
|
||||
{
|
||||
var levels = line.Split(' ');
|
||||
|
||||
var previousDirection = 0;
|
||||
var previousLevel = int.MinValue;
|
||||
Range previousLevelRange = default;
|
||||
Range firstLevelRange = default;
|
||||
|
||||
foreach (var levelRange in levels)
|
||||
{
|
||||
if (levelRange.Equals(toSkip))
|
||||
{
|
||||
// This level can be skipped using the problem dampener
|
||||
continue;
|
||||
}
|
||||
|
||||
var level = int.Parse(line[levelRange]);
|
||||
|
||||
// First level, skip to next
|
||||
if (previousLevel == int.MinValue)
|
||||
{
|
||||
previousLevel = level;
|
||||
previousLevelRange = levelRange;
|
||||
firstLevelRange = levelRange;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check that direction is preserved
|
||||
var direction = level - previousLevel;
|
||||
|
||||
if (previousDirection != 0 && Math.Sign(direction) != Math.Sign(previousDirection))
|
||||
{
|
||||
// Also try by removing current or previous level if it's first try (problem dampener)
|
||||
return toSkip is null
|
||||
&& (IsSafe(line, levelRange) || IsSafe(line, previousLevelRange) || IsSafe(line, firstLevelRange));
|
||||
}
|
||||
|
||||
// Check that distance >= 1 and <= 3
|
||||
if (Math.Abs(direction) is < 1 or > 3)
|
||||
{
|
||||
// Also try by removing current or previous level if it's first try (problem dampener)
|
||||
return toSkip is null
|
||||
&& (IsSafe(line, levelRange) || IsSafe(line, previousLevelRange) || IsSafe(line, firstLevelRange));
|
||||
}
|
||||
|
||||
previousLevel = level;
|
||||
previousDirection = direction;
|
||||
previousLevelRange = levelRange;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
288
Days/Day20.cs
288
Days/Day20.cs
@@ -1,288 +0,0 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Numerics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day20 : Day
|
||||
{
|
||||
public override int Number => 20;
|
||||
public override string Name => "Race Condition";
|
||||
|
||||
private const int Size = 141;
|
||||
private const char Wall = '#';
|
||||
private const char Empty = '.';
|
||||
|
||||
private readonly List<Point> _cheatOffsets =
|
||||
[
|
||||
(2, 0),
|
||||
(1, 1),
|
||||
(0, 2),
|
||||
(-1, 1),
|
||||
(-2, 0),
|
||||
(-1, -1),
|
||||
(0, -2),
|
||||
(1, -1)
|
||||
];
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var (map, start, end) = ParseMap();
|
||||
|
||||
// Find track path
|
||||
var trackPath = new List<Point>();
|
||||
var distanceFromStart = new Dictionary<Point, int>();
|
||||
|
||||
var position = start;
|
||||
|
||||
var distance = 0;
|
||||
while (position != end)
|
||||
{
|
||||
trackPath.Add(position);
|
||||
distanceFromStart[position] = distance;
|
||||
|
||||
// Find next track position
|
||||
distance++;
|
||||
var up = position + new Point(0, -1);
|
||||
if (up is { X: >= 0 and < Size, Y: >= 0 and < Size } && map[up.X, up.Y] is not Wall && !distanceFromStart.ContainsKey(up))
|
||||
{
|
||||
position = up;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var down = position + new Point(0, 1);
|
||||
if (down is { X: >= 0 and < Size, Y: >= 0 and < Size } && map[down.X, down.Y] is not Wall && !distanceFromStart.ContainsKey(down))
|
||||
{
|
||||
position = down;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var left = position + new Point(-1, 0);
|
||||
if (left is { X: >= 0 and < Size, Y: >= 0 and < Size } && map[left.X, left.Y] is not Wall && !distanceFromStart.ContainsKey(left))
|
||||
{
|
||||
position = left;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var right = position + new Point(1, 0);
|
||||
if (right is { X: >= 0 and < Size, Y: >= 0 and < Size } && map[right.X, right.Y] is not Wall && !distanceFromStart.ContainsKey(right))
|
||||
{
|
||||
position = right;
|
||||
}
|
||||
}
|
||||
|
||||
// Also add end
|
||||
trackPath.Add(position);
|
||||
distanceFromStart[position] = distance;
|
||||
|
||||
var cheats = new Dictionary<(Point StartPosition, Point EndPosition), int>();
|
||||
|
||||
// Now return to start and try all possible combination of cheats for each track position
|
||||
foreach (var trackPosition in trackPath)
|
||||
{
|
||||
var originDistance = distanceFromStart[trackPosition];
|
||||
|
||||
foreach (var cheatOffset in _cheatOffsets)
|
||||
{
|
||||
var cheatDestination = trackPosition + cheatOffset;
|
||||
|
||||
if (cheatDestination.X is < 0 or >= Size || cheatDestination.Y is < 0 or >= Size)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (map[cheatDestination.X, cheatDestination.Y] is Wall)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var destinationDistance = distanceFromStart[cheatDestination];
|
||||
var savedDistance = destinationDistance - originDistance - 2;
|
||||
|
||||
// Only take into account real shortcuts, not those that do not save time
|
||||
if (savedDistance < 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
cheats.Add((trackPosition, cheatDestination), savedDistance);
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Number of cheats that save at least 100 picoseconds: [yellow]{cheats.Count(c => c.Value >= 100)}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
var (map, start, end) = ParseMap();
|
||||
|
||||
// Find track path
|
||||
var trackPath = new List<Point>();
|
||||
var distanceFromStart = new Dictionary<Point, int>();
|
||||
|
||||
var position = start;
|
||||
|
||||
var distance = 0;
|
||||
while (position != end)
|
||||
{
|
||||
trackPath.Add(position);
|
||||
distanceFromStart[position] = distance;
|
||||
|
||||
// Find next track position
|
||||
distance++;
|
||||
var up = position + new Point(0, -1);
|
||||
if (up is { X: >= 0 and < Size, Y: >= 0 and < Size } && map[up.X, up.Y] is not Wall && !distanceFromStart.ContainsKey(up))
|
||||
{
|
||||
position = up;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var down = position + new Point(0, 1);
|
||||
if (down is { X: >= 0 and < Size, Y: >= 0 and < Size } && map[down.X, down.Y] is not Wall && !distanceFromStart.ContainsKey(down))
|
||||
{
|
||||
position = down;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var left = position + new Point(-1, 0);
|
||||
if (left is { X: >= 0 and < Size, Y: >= 0 and < Size } && map[left.X, left.Y] is not Wall && !distanceFromStart.ContainsKey(left))
|
||||
{
|
||||
position = left;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
var right = position + new Point(1, 0);
|
||||
if (right is { X: >= 0 and < Size, Y: >= 0 and < Size } && map[right.X, right.Y] is not Wall && !distanceFromStart.ContainsKey(right))
|
||||
{
|
||||
position = right;
|
||||
}
|
||||
}
|
||||
|
||||
// Also add end
|
||||
trackPath.Add(position);
|
||||
distanceFromStart[position] = distance;
|
||||
|
||||
var cheats = new Dictionary<(Point StartPosition, Point EndPosition), int>();
|
||||
|
||||
// Now return to start and try all possible combination of cheats for each track position
|
||||
foreach (var trackPosition in trackPath)
|
||||
{
|
||||
var originDistance = distanceFromStart[trackPosition];
|
||||
|
||||
foreach (var cheatDestination in trackPath.Where(destination => trackPosition.DistanceTo(destination) <= 20))
|
||||
{
|
||||
var cheatLength = trackPosition.DistanceTo(cheatDestination);
|
||||
|
||||
var destinationDistance = distanceFromStart[cheatDestination];
|
||||
var savedDistance = destinationDistance - originDistance - cheatLength;
|
||||
|
||||
// Only take into account real shortcuts, not those that do not save time
|
||||
if (savedDistance < 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cheats.TryGetValue((trackPosition, cheatDestination), out var otherSavedDistance))
|
||||
{
|
||||
if (savedDistance <= otherSavedDistance)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
cheats[(trackPosition, cheatDestination)] = savedDistance;
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Number of cheats that save at least 100 picoseconds: [yellow]{cheats.Count(c => c.Value >= 100)}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
private (char[,] Map, Point Start, Point End) ParseMap()
|
||||
{
|
||||
var map = new char[Size, Size];
|
||||
Point start = default;
|
||||
Point end = default;
|
||||
|
||||
var y = 0;
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
var x = 0;
|
||||
|
||||
foreach (var symbol in line)
|
||||
{
|
||||
if (symbol == 'S')
|
||||
{
|
||||
start = new Point(x, y);
|
||||
}
|
||||
else if (symbol == 'E')
|
||||
{
|
||||
end = new Point(x, y);
|
||||
}
|
||||
|
||||
if (symbol is Wall)
|
||||
{
|
||||
map[x, y] = Wall;
|
||||
}
|
||||
else
|
||||
{
|
||||
map[x, y] = Empty;
|
||||
}
|
||||
|
||||
x++;
|
||||
}
|
||||
|
||||
y++;
|
||||
}
|
||||
|
||||
return (map, start, end);
|
||||
}
|
||||
|
||||
private readonly record struct Point(int X, int Y)
|
||||
: IAdditionOperators<Point, Point, Point>,
|
||||
ISubtractionOperators<Point, Point, Point>,
|
||||
IMultiplyOperators<Point, int, Point>
|
||||
{
|
||||
public static Point operator +(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X + right.X, left.Y + right.Y);
|
||||
}
|
||||
|
||||
public static Point operator -(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X - right.X, left.Y - right.Y);
|
||||
}
|
||||
|
||||
public static Point operator *(Point left, int right)
|
||||
{
|
||||
return new Point(left.X * right, left.Y * right);
|
||||
}
|
||||
|
||||
public static Point operator *(int left, Point right)
|
||||
{
|
||||
return new Point(right.X * left, right.Y * left);
|
||||
}
|
||||
|
||||
public static implicit operator Point((int X, int Y) point)
|
||||
=> new(point.X, point.Y);
|
||||
|
||||
public int DistanceTo(Point other)
|
||||
{
|
||||
var distance = other - this;
|
||||
|
||||
return Math.Abs(distance.X) + Math.Abs(distance.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
183
Days/Day21.cs
183
Days/Day21.cs
@@ -1,183 +0,0 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Numerics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using System.Text;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day21 : Day
|
||||
{
|
||||
public override int Number => 21;
|
||||
public override string Name => "Keypad Conundrum";
|
||||
|
||||
private const char Up = '^';
|
||||
private const char Right = '>';
|
||||
private const char Down = 'v';
|
||||
private const char Left = '<';
|
||||
private const char Enter = 'A';
|
||||
|
||||
private readonly Dictionary<char, Point> _numpadPositions = new()
|
||||
{
|
||||
{ '7', (0, 0) },
|
||||
{ '8', (1, 0) },
|
||||
{ '9', (2, 0) },
|
||||
{ '4', (0, 1) },
|
||||
{ '5', (1, 1) },
|
||||
{ '6', (2, 1) },
|
||||
{ '1', (0, 2) },
|
||||
{ '2', (1, 2) },
|
||||
{ '3', (2, 2) },
|
||||
{ '0', (1, 3) },
|
||||
{ Enter, (2, 3) }
|
||||
};
|
||||
|
||||
private readonly Dictionary<char, Point> _keypadPositions = new()
|
||||
{
|
||||
{ Up, (1, 0) },
|
||||
{ Enter, (2, 0) },
|
||||
{ Left, (0, 1) },
|
||||
{ Down, (1, 1) },
|
||||
{ Right, (2, 1) }
|
||||
};
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var codes = ParseCodes();
|
||||
var complexitiesSum = 0;
|
||||
|
||||
foreach (var code in codes)
|
||||
{
|
||||
// Get first keypad sequence
|
||||
var firstKeypadSequence = new StringBuilder();
|
||||
var currentInput = Enter;
|
||||
|
||||
foreach (var digit in code)
|
||||
{
|
||||
firstKeypadSequence.Append(GetNumpadMoveSequence(currentInput, digit));
|
||||
|
||||
currentInput = digit;
|
||||
}
|
||||
|
||||
// Get second keypad sequence
|
||||
var secondKeypadSequence = new StringBuilder();
|
||||
currentInput = Enter;
|
||||
|
||||
foreach (var input in firstKeypadSequence.ToString())
|
||||
{
|
||||
secondKeypadSequence.Append(GetKeypadMoveSequence(currentInput, input));
|
||||
|
||||
currentInput = input;
|
||||
}
|
||||
|
||||
// Get third and last keypad sequence
|
||||
var thirdKeypadSequence = new StringBuilder();
|
||||
currentInput = Enter;
|
||||
|
||||
foreach (var input in secondKeypadSequence.ToString())
|
||||
{
|
||||
thirdKeypadSequence.Append(GetKeypadMoveSequence(currentInput, input));
|
||||
|
||||
currentInput = input;
|
||||
}
|
||||
|
||||
complexitiesSum += thirdKeypadSequence.Length * int.Parse(code.AsSpan()[..^1]);
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Sum of complexities: [yellow]{complexitiesSum}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private string GetNumpadMoveSequence(char origin, char destination)
|
||||
{
|
||||
var originPosition = _numpadPositions[origin];
|
||||
var destinationPosition = _numpadPositions[destination];
|
||||
|
||||
var move = destinationPosition - originPosition;
|
||||
|
||||
var moveRight = move.X > 0;
|
||||
var moveDown = move.Y > 0;
|
||||
|
||||
var sequence = string.Concat(
|
||||
new string(moveRight ? Right : Left, Math.Abs(move.X)),
|
||||
new string(moveDown ? Down : Up, Math.Abs(move.Y)),
|
||||
Enter.ToString() // Always end on Enter to press key
|
||||
);
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
private string GetKeypadMoveSequence(char origin, char destination)
|
||||
{
|
||||
var originPosition = _keypadPositions[origin];
|
||||
var destinationPosition = _keypadPositions[destination];
|
||||
|
||||
var move = destinationPosition - originPosition;
|
||||
|
||||
var moveRight = move.X > 0;
|
||||
var moveDown = move.Y > 0;
|
||||
|
||||
var sequence = string.Concat(
|
||||
new string(moveDown ? Down : Up, Math.Abs(move.Y)),
|
||||
new string(moveRight ? Right : Left, Math.Abs(move.X)),
|
||||
Enter.ToString() // Always end on Enter to press key
|
||||
);
|
||||
|
||||
return sequence;
|
||||
}
|
||||
|
||||
private List<string> ParseCodes()
|
||||
{
|
||||
var codes = new List<string>();
|
||||
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
codes.Add(line.ToString());
|
||||
}
|
||||
|
||||
return codes;
|
||||
}
|
||||
|
||||
private readonly record struct Point(int X, int Y)
|
||||
: IAdditionOperators<Point, Point, Point>,
|
||||
ISubtractionOperators<Point, Point, Point>,
|
||||
IMultiplyOperators<Point, int, Point>
|
||||
{
|
||||
public static Point operator +(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X + right.X, left.Y + right.Y);
|
||||
}
|
||||
|
||||
public static Point operator -(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X - right.X, left.Y - right.Y);
|
||||
}
|
||||
|
||||
public static Point operator *(Point left, int right)
|
||||
{
|
||||
return new Point(left.X * right, left.Y * right);
|
||||
}
|
||||
|
||||
public static Point operator *(int left, Point right)
|
||||
{
|
||||
return new Point(right.X * left, right.Y * left);
|
||||
}
|
||||
|
||||
public static implicit operator Point((int X, int Y) point)
|
||||
=> new(point.X, point.Y);
|
||||
|
||||
public int DistanceTo(Point other)
|
||||
{
|
||||
var distance = other - this;
|
||||
|
||||
return Math.Abs(distance.X) + Math.Abs(distance.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
162
Days/Day22.cs
162
Days/Day22.cs
@@ -1,162 +0,0 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Numerics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using System.Text;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day22 : Day
|
||||
{
|
||||
public override int Number => 22;
|
||||
public override string Name => "Monkey Market";
|
||||
|
||||
private const int Iterations = 2000;
|
||||
private const int PruneValue = 16777216;
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var secretsSum = 0L;
|
||||
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
var secret = long.Parse(line);
|
||||
|
||||
for (var i = 0; i < Iterations; i++)
|
||||
{
|
||||
var result = secret * 64;
|
||||
secret = (secret ^ result) % PruneValue;
|
||||
|
||||
result = secret / 32;
|
||||
secret = (secret ^ result) % PruneValue;
|
||||
|
||||
result = secret * 2048;
|
||||
secret = (secret ^ result) % PruneValue;
|
||||
}
|
||||
|
||||
secretsSum += secret;
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Sum of {Iterations}th secret numbers: [yellow]{secretsSum}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
var maximumBananas = 0L;
|
||||
|
||||
var initialSecrets = Input.ReadAllLines().Select(l => long.Parse(l)).ToArray();
|
||||
var matchSequences = GenerateSequences().ToArray();
|
||||
|
||||
AnsiConsole.Progress().Start(progress =>
|
||||
{
|
||||
var progressTask = progress.AddTask("Finding best sequence", maxValue: matchSequences.Length);
|
||||
|
||||
Parallel.ForEach(matchSequences, matchSequence =>
|
||||
{
|
||||
Span<long> sequence = stackalloc long[4];
|
||||
var currentBananas = 0L;
|
||||
|
||||
for (var initialSecretIndex = 0; initialSecretIndex < initialSecrets.Length; initialSecretIndex++)
|
||||
{
|
||||
var secret = initialSecrets[initialSecretIndex];
|
||||
|
||||
for (var i = 0; i < Iterations; i++)
|
||||
{
|
||||
var previousSecret = secret;
|
||||
|
||||
// Compute new secret
|
||||
var result = secret * 64;
|
||||
secret = (secret ^ result) % PruneValue;
|
||||
|
||||
result = secret / 32;
|
||||
secret = (secret ^ result) % PruneValue;
|
||||
|
||||
result = secret * 2048;
|
||||
secret = (secret ^ result) % PruneValue;
|
||||
|
||||
var lastDigit = secret % 10;
|
||||
var previousSecretLastDigit = previousSecret % 10;
|
||||
|
||||
// Update sequence
|
||||
sequence[0] = sequence[1];
|
||||
sequence[1] = sequence[2];
|
||||
sequence[2] = sequence[3];
|
||||
sequence[3] = lastDigit - previousSecretLastDigit;
|
||||
|
||||
// Check if sequence match
|
||||
if (sequence[0] == matchSequence.Item1
|
||||
&& sequence[1] == matchSequence.Item2
|
||||
&& sequence[2] == matchSequence.Item3
|
||||
&& sequence[3] == matchSequence.Item4
|
||||
&& i >= 3)
|
||||
{
|
||||
currentBananas += lastDigit;
|
||||
|
||||
// Go to next monkey
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Stop early if it's not possible to beat current maximum
|
||||
if (maximumBananas - currentBananas >= ((initialSecrets.Length - initialSecretIndex - 1) * 9))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ExchangeIfGreaterThan(ref maximumBananas, currentBananas);
|
||||
|
||||
progressTask.Increment(1);
|
||||
});
|
||||
});
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Maximum amount of bananas: [yellow]{maximumBananas}[/][/]");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
IEnumerable<(long, long, long, long)> GenerateSequences()
|
||||
{
|
||||
for (var a = -9L; a <= 9; a++)
|
||||
{
|
||||
for (var b = -9L; b <= 9; b++)
|
||||
{
|
||||
for (var c = -9L; c <= 9; c++)
|
||||
{
|
||||
for (var d = -9L; d <= 9; d++)
|
||||
{
|
||||
yield return (a, b, c, d);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Source: https://stackoverflow.com/a/13323172
|
||||
private static void ExchangeIfGreaterThan(ref long location, long value)
|
||||
{
|
||||
// Read
|
||||
var current = Interlocked.Read(ref location);
|
||||
|
||||
// Compare
|
||||
while (current < value)
|
||||
{
|
||||
// Set
|
||||
var previous = Interlocked.CompareExchange(ref location, value, current);
|
||||
|
||||
// If another thread has set a greater value, we can break
|
||||
// or if previous value is current value, then no other thread has it changed in between
|
||||
if (previous == current || previous >= value) // note: most common case first
|
||||
break;
|
||||
|
||||
// For all other cases, we need another run (read value, compare, set)
|
||||
current = Interlocked.Read(ref location);
|
||||
}
|
||||
}
|
||||
}
|
||||
172
Days/Day23.cs
172
Days/Day23.cs
@@ -1,172 +0,0 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Numerics;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using System.Text;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day23 : Day
|
||||
{
|
||||
public override int Number => 23;
|
||||
public override string Name => "LAN Party";
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var computerLinks = new Dictionary<string, HashSet<string>>();
|
||||
|
||||
// Parse all links
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
var firstComputer = line[..2].ToString();
|
||||
var secondComputer = line[3..].ToString();
|
||||
|
||||
if (!computerLinks.TryGetValue(firstComputer, out var firstLinks))
|
||||
{
|
||||
firstLinks = [];
|
||||
computerLinks[firstComputer] = firstLinks;
|
||||
}
|
||||
firstLinks.Add(secondComputer);
|
||||
|
||||
if (!computerLinks.TryGetValue(secondComputer, out var secondLinks))
|
||||
{
|
||||
secondLinks = [];
|
||||
computerLinks[secondComputer] = secondLinks;
|
||||
}
|
||||
secondLinks.Add(firstComputer);
|
||||
}
|
||||
|
||||
var sets = new HashSet<ComputerSet>();
|
||||
|
||||
foreach (var (firstComputer, firstLinks) in computerLinks)
|
||||
{
|
||||
foreach (var secondComputer in firstLinks)
|
||||
{
|
||||
var thirdComputers = computerLinks[secondComputer].Intersect(firstLinks);
|
||||
|
||||
foreach (var thirdComputer in thirdComputers)
|
||||
{
|
||||
sets.Add(new ComputerSet(firstComputer, secondComputer, thirdComputer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var finalCount = sets.Count(set => set.First.StartsWith('t') || set.Second.StartsWith('t') || set.Third.StartsWith('t'));
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Amount of sets that contain at least one computer that starts with t: [yellow]{finalCount}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
var computerLinks = new Dictionary<string, HashSet<string>>();
|
||||
|
||||
// Parse all links
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
var firstComputer = line[..2].ToString();
|
||||
var secondComputer = line[3..].ToString();
|
||||
|
||||
if (!computerLinks.TryGetValue(firstComputer, out var firstLinks))
|
||||
{
|
||||
firstLinks = [];
|
||||
computerLinks[firstComputer] = firstLinks;
|
||||
}
|
||||
firstLinks.Add(secondComputer);
|
||||
|
||||
if (!computerLinks.TryGetValue(secondComputer, out var secondLinks))
|
||||
{
|
||||
secondLinks = [];
|
||||
computerLinks[secondComputer] = secondLinks;
|
||||
}
|
||||
secondLinks.Add(firstComputer);
|
||||
}
|
||||
|
||||
List<string> largestSet = [];
|
||||
|
||||
AnsiConsole.Status().Start("Starting check...", status =>
|
||||
{
|
||||
var size = 4;
|
||||
while (true)
|
||||
{
|
||||
var ended = true;
|
||||
|
||||
status.Status($"Checking set size: {size}");
|
||||
|
||||
var i = 0;
|
||||
foreach (var (firstComputer, firstLinks) in computerLinks)
|
||||
{
|
||||
status.Status($"Checking set size: {size} ({i++}/{computerLinks.Count})");
|
||||
|
||||
if (GenerateLargestSet(firstLinks, size, [ firstComputer ]) is { } largest)
|
||||
{
|
||||
largestSet = largest;
|
||||
size++;
|
||||
ended = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (ended)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var password = string.Join(',', largestSet.Order());
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Password to enter lan party: [yellow]{password}[/][/]");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
List<string>? GenerateLargestSet(HashSet<string> set, int targetSize, List<string> selected)
|
||||
{
|
||||
if (selected.Count == targetSize)
|
||||
{
|
||||
return selected;
|
||||
}
|
||||
|
||||
if (set.Count is 0 || (set.Count < (targetSize - selected.Count)))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Next depth
|
||||
foreach (var computer in set)
|
||||
{
|
||||
var nextSet = set.Intersect(computerLinks[computer]).ToHashSet();
|
||||
var nextSelected = selected.Append(computer).ToList();
|
||||
|
||||
if (GenerateLargestSet(nextSet, targetSize, nextSelected) is { } largest)
|
||||
{
|
||||
return largest;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private record ComputerSet(string First, string Second, string Third) : IEquatable<ComputerSet>
|
||||
{
|
||||
public virtual bool Equals(ComputerSet? other)
|
||||
{
|
||||
return other is not null &&
|
||||
(First == other.First || First == other.Second || First == other.Third) &&
|
||||
(Second == other.First || Second == other.Second || Second == other.Third) &&
|
||||
(Third == other.First || Third == other.Second || Third == other.Third);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return First.GetHashCode() * Second.GetHashCode() * Third.GetHashCode();
|
||||
}
|
||||
}
|
||||
}
|
||||
370
Days/Day24.cs
370
Days/Day24.cs
@@ -1,370 +0,0 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.VisualBasic;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day24 : Day
|
||||
{
|
||||
public override int Number => 24;
|
||||
public override string Name => "Crossed Wires";
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var (signals, gates, zGates) = ParseInput();
|
||||
|
||||
while (zGates.Count > 0)
|
||||
{
|
||||
foreach (var (inputLeft, inputRight, output, logicOperator) in gates)
|
||||
{
|
||||
if (!signals.TryGetValue(inputLeft, out var inputLeftValue) ||
|
||||
!signals.TryGetValue(inputRight, out var inputRightValue))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
signals[output] = Compute(inputLeftValue, inputRightValue, logicOperator);
|
||||
|
||||
if (output.StartsWith('z'))
|
||||
{
|
||||
zGates.Remove(output);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var zOutputValue = signals
|
||||
.Where(s => s.Key.StartsWith('z'))
|
||||
.Select(s => new { Index = int.Parse(s.Key[1..]), Value = (long)s.Value })
|
||||
.Sum(s => s.Value << s.Index);
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Value output on z signal: [yellow]{zOutputValue}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
var (initialSignals, initialGates, initialZGates) = ParseInput();
|
||||
|
||||
var xInputValue = initialSignals
|
||||
.Where(s => s.Key.StartsWith('x'))
|
||||
.Select(s => new { Index = int.Parse(s.Key[1..]), Value = (long)s.Value })
|
||||
.Sum(s => s.Value << s.Index);
|
||||
|
||||
var yInputValue = initialSignals
|
||||
.Where(s => s.Key.StartsWith('y'))
|
||||
.Select(s => new { Index = int.Parse(s.Key[1..]), Value = (long)s.Value })
|
||||
.Sum(s => s.Value << s.Index);
|
||||
|
||||
// Find wires that need to be changed
|
||||
const long initialZValue = 58740594706150L;
|
||||
var targetZValue = xInputValue + yInputValue;
|
||||
|
||||
var difference = targetZValue ^ initialZValue;
|
||||
var possibleChange = new HashSet<string>();
|
||||
|
||||
for (var i = 0; i < sizeof(long) * 8; i++)
|
||||
{
|
||||
if (((1 << i) & difference) != 0)
|
||||
{
|
||||
possibleChange.Add($"z{i:D2}");
|
||||
}
|
||||
}
|
||||
|
||||
// Find all dependencies
|
||||
int initialSize;
|
||||
do
|
||||
{
|
||||
initialSize = possibleChange.Count;
|
||||
|
||||
foreach (var gate in initialGates)
|
||||
{
|
||||
if (possibleChange.Contains(gate.Output))
|
||||
{
|
||||
possibleChange.Add(gate.InputLeft);
|
||||
possibleChange.Add(gate.InputRight);
|
||||
}
|
||||
}
|
||||
} while (possibleChange.Count != initialSize);
|
||||
|
||||
var gatesToSwapIndices = initialGates
|
||||
.Index()
|
||||
.ToList()
|
||||
.Where(t => possibleChange.Contains(t.Item.Output))
|
||||
.Select(t => t.Index)
|
||||
.ToList();
|
||||
|
||||
// Try out all possible permutations of concerned outputs
|
||||
List<string> changedOutputs = [];
|
||||
|
||||
for (var a = 0; a < gatesToSwapIndices.Count - 7; a++)
|
||||
{
|
||||
for (var b = a + 1; b < gatesToSwapIndices.Count - 6; b++)
|
||||
{
|
||||
for (var c = b + 1; c < gatesToSwapIndices.Count - 5; c++)
|
||||
{
|
||||
for (var d = c + 1; d < gatesToSwapIndices.Count - 4; d++)
|
||||
{
|
||||
for (var e = d + 1; e < gatesToSwapIndices.Count - 3; e++)
|
||||
{
|
||||
for (var f = e + 1; f < gatesToSwapIndices.Count - 2; f++)
|
||||
{
|
||||
for (var g = f + 1; g < gatesToSwapIndices.Count - 1; g++)
|
||||
{
|
||||
for (var h = g + 1; h < gatesToSwapIndices.Count; h++)
|
||||
{
|
||||
Console.WriteLine($"h: {h}");
|
||||
|
||||
ForAllPermutation([
|
||||
gatesToSwapIndices[a],
|
||||
gatesToSwapIndices[b],
|
||||
gatesToSwapIndices[c],
|
||||
gatesToSwapIndices[d],
|
||||
gatesToSwapIndices[e],
|
||||
gatesToSwapIndices[f],
|
||||
gatesToSwapIndices[g],
|
||||
gatesToSwapIndices[h]], permutations =>
|
||||
{
|
||||
if (CheckPermutations(permutations) is { } modifiedOutputs)
|
||||
{
|
||||
changedOutputs = modifiedOutputs;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var swappedOutputs = string.Join(',', changedOutputs.Order());
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Swapped outputs: [yellow]{swappedOutputs}[/][/]");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
List<string>? CheckPermutations(int[] permutations)
|
||||
{
|
||||
var gates = initialGates.ToArray();
|
||||
var zGates = initialZGates.ToHashSet();
|
||||
var signals = initialSignals.ToDictionary();
|
||||
|
||||
SwapOutput(ref gates[permutations[0]], ref gates[permutations[1]]);
|
||||
SwapOutput(ref gates[permutations[2]], ref gates[permutations[3]]);
|
||||
SwapOutput(ref gates[permutations[4]], ref gates[permutations[5]]);
|
||||
SwapOutput(ref gates[permutations[6]], ref gates[permutations[7]]);
|
||||
|
||||
var zOutputValue = 0L;
|
||||
while (zGates.Count > 0)
|
||||
{
|
||||
// Avoid locks when result is not reachable (infinite loop)
|
||||
var resultsCount = signals.Count;
|
||||
|
||||
foreach (var (inputLeft, inputRight, output, logicOperator) in gates)
|
||||
{
|
||||
if (signals.ContainsKey(output))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!signals.TryGetValue(inputLeft, out var inputLeftValue) ||
|
||||
!signals.TryGetValue(inputRight, out var inputRightValue))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var computed = Compute(inputLeftValue, inputRightValue, logicOperator);
|
||||
signals[output] = computed;
|
||||
|
||||
if (output.StartsWith('z'))
|
||||
{
|
||||
zGates.Remove(output);
|
||||
|
||||
zOutputValue += (long)computed << int.Parse(output[1..]);
|
||||
}
|
||||
}
|
||||
|
||||
// No new signal value computed, it's an infinite loop
|
||||
if (signals.Count == resultsCount)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (xInputValue + yInputValue == zOutputValue)
|
||||
{
|
||||
return permutations.Select(i => gates[i].Output).ToList();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
void SwapOutput(ref LogicGate first, ref LogicGate second)
|
||||
{
|
||||
var temp = first.Output;
|
||||
|
||||
first = first with { Output = second.Output };
|
||||
second = second with { Output = temp };
|
||||
}
|
||||
}
|
||||
|
||||
private int Compute(int inputLeftValue, int inputRightValue, Operator logicOperator) => logicOperator switch
|
||||
{
|
||||
Operator.And => inputLeftValue & inputRightValue,
|
||||
Operator.Or => inputLeftValue | inputRightValue,
|
||||
Operator.Xor => inputLeftValue ^ inputRightValue,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(logicOperator), logicOperator, null)
|
||||
};
|
||||
|
||||
private (Dictionary<string, int> Signals, List<LogicGate> Gates, HashSet<string> ZGates) ParseInput()
|
||||
{
|
||||
var readingSignals = true;
|
||||
|
||||
var signals = new Dictionary<string, int>();
|
||||
var gates = new List<LogicGate>();
|
||||
var zGates = new HashSet<string>();
|
||||
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
if (line.IsWhiteSpace())
|
||||
{
|
||||
readingSignals = false;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (readingSignals)
|
||||
{
|
||||
var signalName = line[..line.IndexOf(':')];
|
||||
var signalValue = int.Parse(line[(line.IndexOf(':') + 2)..]);
|
||||
|
||||
signals[signalName.ToString()] = signalValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
var split = line.Split(' ');
|
||||
split.MoveNext();
|
||||
|
||||
var inputLeft = line[split.Current];
|
||||
split.MoveNext();
|
||||
|
||||
var logicOperator = Enum.Parse<Operator>(line[split.Current], true);
|
||||
split.MoveNext();
|
||||
|
||||
var inputRight = line[split.Current];
|
||||
split.MoveNext();
|
||||
split.MoveNext();
|
||||
|
||||
var output = line[split.Current];
|
||||
|
||||
gates.Add(new LogicGate(inputLeft.ToString(), inputRight.ToString(), output.ToString(), logicOperator));
|
||||
|
||||
if (output.StartsWith('z'))
|
||||
{
|
||||
zGates.Add(output.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (signals, gates, zGates);
|
||||
}
|
||||
|
||||
private record LogicGate(string InputLeft, string InputRight, string Output, Operator LogicOperator);
|
||||
|
||||
private enum Operator
|
||||
{
|
||||
And,
|
||||
Or,
|
||||
Xor
|
||||
}
|
||||
|
||||
// Source: https://stackoverflow.com/a/36634935
|
||||
|
||||
/// <summary>
|
||||
/// Heap's algorithm to find all permutations. Non recursive, more efficient.
|
||||
/// </summary>
|
||||
/// <param name="items">Items to permute in each possible ways</param>
|
||||
/// <param name="funcExecuteAndTellIfShouldStop"></param>
|
||||
/// <returns>Return true if cancelled</returns>
|
||||
private static bool ForAllPermutation<T>(T[] items, Func<T[], bool> funcExecuteAndTellIfShouldStop)
|
||||
{
|
||||
int countOfItem = items.Length;
|
||||
|
||||
if (countOfItem <= 1)
|
||||
{
|
||||
return funcExecuteAndTellIfShouldStop(items);
|
||||
}
|
||||
|
||||
var indexes = new int[countOfItem];
|
||||
|
||||
// Unnecessary. Thanks to NetManage for the advise
|
||||
// for (int i = 0; i < countOfItem; i++)
|
||||
// {
|
||||
// indexes[i] = 0;
|
||||
// }
|
||||
|
||||
if (funcExecuteAndTellIfShouldStop(items))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
for (int i = 1; i < countOfItem;)
|
||||
{
|
||||
if (indexes[i] < i)
|
||||
{
|
||||
// On the web there is an implementation with a multiplication which should be less efficient.
|
||||
if ((i & 1) == 1) // if (i % 2 == 1) ... more efficient ??? At least the same.
|
||||
{
|
||||
Swap(ref items[i], ref items[indexes[i]]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Swap(ref items[i], ref items[0]);
|
||||
}
|
||||
|
||||
if (funcExecuteAndTellIfShouldStop(items))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
indexes[i]++;
|
||||
i = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
indexes[i++] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Swap 2 elements of same type
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <param name="a"></param>
|
||||
/// <param name="b"></param>
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private static void Swap<T>(ref T a, ref T b)
|
||||
{
|
||||
T temp = a;
|
||||
a = b;
|
||||
b = temp;
|
||||
}
|
||||
}
|
||||
111
Days/Day25.cs
111
Days/Day25.cs
@@ -1,111 +0,0 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.Intrinsics.X86;
|
||||
using System.Text;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.VisualBasic;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day25 : Day
|
||||
{
|
||||
public override int Number => 25;
|
||||
public override string Name => "Code Chronicle";
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var (locks, keys) = ParseInput();
|
||||
|
||||
var workingPairs = 0;
|
||||
|
||||
foreach (var lockToTest in locks)
|
||||
{
|
||||
foreach (var key in keys)
|
||||
{
|
||||
if (lockToTest.Height1 + key.Height1 < 6 &&
|
||||
lockToTest.Height2 + key.Height2 < 6 &&
|
||||
lockToTest.Height3 + key.Height3 < 6 &&
|
||||
lockToTest.Height4 + key.Height4 < 6 &&
|
||||
lockToTest.Height5 + key.Height5 < 6)
|
||||
{
|
||||
workingPairs++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Amont of unique key/lock pairs that fit: [yellow]{workingPairs}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private (List<Lock> Locks, List<Key> Keys) ParseInput()
|
||||
{
|
||||
var locks = new List<Lock>();
|
||||
var keys = new List<Key>();
|
||||
|
||||
Span<int> itemHeights = stackalloc int[5];
|
||||
|
||||
var input = Input.AsSpan();
|
||||
foreach (var itemRange in input.Split($"{Environment.NewLine}{Environment.NewLine}"))
|
||||
{
|
||||
var item = input[itemRange];
|
||||
var lines = item.EnumerateLines();
|
||||
|
||||
var isKey = false;
|
||||
|
||||
// Reset item heights
|
||||
itemHeights[0] = -1;
|
||||
itemHeights[1] = -1;
|
||||
itemHeights[2] = -1;
|
||||
itemHeights[3] = -1;
|
||||
itemHeights[4] = -1;
|
||||
|
||||
for (var height = 0; height <= 6; height++)
|
||||
{
|
||||
lines.MoveNext();
|
||||
var line = lines.Current;
|
||||
|
||||
if (height is 0)
|
||||
{
|
||||
isKey = line is ".....";
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
for (var i = 0; i < 5; i++)
|
||||
{
|
||||
if (isKey && line[i] is '#' && itemHeights[i] is -1)
|
||||
{
|
||||
itemHeights[i] = 6 - height;
|
||||
}
|
||||
else if (!isKey && line[i] is '.' && itemHeights[i] is -1)
|
||||
{
|
||||
itemHeights[i] = height - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isKey)
|
||||
{
|
||||
keys.Add(new Key(itemHeights[0], itemHeights[1], itemHeights[2], itemHeights[3], itemHeights[4]));
|
||||
}
|
||||
else
|
||||
{
|
||||
locks.Add(new Lock(itemHeights[0], itemHeights[1], itemHeights[2], itemHeights[3], itemHeights[4]));
|
||||
}
|
||||
}
|
||||
|
||||
return (locks, keys);
|
||||
}
|
||||
|
||||
private readonly record struct Lock(int Height1, int Height2, int Height3, int Height4, int Height5);
|
||||
private readonly record struct Key(int Height1, int Height2, int Height3, int Height4, int Height5);
|
||||
}
|
||||
109
Days/Day3.cs
109
Days/Day3.cs
@@ -1,109 +0,0 @@
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day3 : Day
|
||||
{
|
||||
public override int Number => 3;
|
||||
public override string Name => "Mull It Over";
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var result = 0;
|
||||
|
||||
var input = Input.AsSpan();
|
||||
var mulStart = input.IndexOf("mul(");
|
||||
|
||||
while (mulStart != -1)
|
||||
{
|
||||
input = input[(mulStart + 4)..];
|
||||
|
||||
// Start of a mul operation, search the ending parenthesis and make sure it's before the next mul
|
||||
var mulEnd = input.IndexOf(")");
|
||||
var nextMulStart = input.IndexOf("mul(");
|
||||
|
||||
if (mulEnd != -1 && (nextMulStart == -1 || mulEnd < nextMulStart))
|
||||
{
|
||||
var operands = input[..mulEnd];
|
||||
var separator = operands.IndexOf(",");
|
||||
|
||||
if (separator != -1
|
||||
&& int.TryParse(operands[..separator], out var left)
|
||||
&& int.TryParse(operands[(separator + 1)..], out var right))
|
||||
{
|
||||
result += left * right;
|
||||
}
|
||||
}
|
||||
|
||||
mulStart = nextMulStart;
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Result: [yellow]{result}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
var result = 0;
|
||||
|
||||
var input = Input.AsSpan();
|
||||
var mulStart = input.IndexOf("mul(");
|
||||
|
||||
while (mulStart != -1)
|
||||
{
|
||||
// Check if there is a "don't()" before next mul
|
||||
var dontStart = input.IndexOf("don't()");
|
||||
|
||||
if (dontStart < mulStart)
|
||||
{
|
||||
// Search for next "do()"
|
||||
input = input[(dontStart + 7)..];
|
||||
|
||||
var doStart = input.IndexOf("do()");
|
||||
|
||||
if (doStart == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
input = input[(doStart + 4)..];
|
||||
|
||||
// Update next mult position
|
||||
mulStart = input.IndexOf("mul(");
|
||||
|
||||
if (mulStart == -1)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
input = input[(mulStart + 4)..];
|
||||
|
||||
// Start of a mul operation, search the ending parenthesis and make sure it's before the next mul
|
||||
var mulEnd = input.IndexOf(")");
|
||||
var nextMulStart = input.IndexOf("mul(");
|
||||
|
||||
if (mulEnd != -1 && (nextMulStart == -1 || mulEnd < nextMulStart))
|
||||
{
|
||||
var operands = input[..mulEnd];
|
||||
var separator = operands.IndexOf(",");
|
||||
|
||||
if (separator != -1
|
||||
&& int.TryParse(operands[..separator], out var left)
|
||||
&& int.TryParse(operands[(separator + 1)..], out var right))
|
||||
{
|
||||
result += left * right;
|
||||
}
|
||||
}
|
||||
|
||||
mulStart = nextMulStart;
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Result: [yellow]{result}[/][/]");
|
||||
}
|
||||
}
|
||||
}
|
||||
136
Days/Day4.cs
136
Days/Day4.cs
@@ -1,136 +0,0 @@
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day4 : Day
|
||||
{
|
||||
public override int Number => 4;
|
||||
public override string Name => "Ceres Search";
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var xmasCount = 0;
|
||||
|
||||
var grid = Input.AsCharGrid();
|
||||
|
||||
for (int x = 0; x < grid.GetLength(0); x++)
|
||||
{
|
||||
for (int y = 0; y < grid.GetLength(1); y++)
|
||||
{
|
||||
xmasCount += CheckXmas(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Number of safe reports: [yellow]{xmasCount}[/][/]");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
int CheckXmas(int x, int y)
|
||||
{
|
||||
(int xOffset, int yOffset)[] patterns = [
|
||||
(1, 0),
|
||||
(-1, 0),
|
||||
(0, 1),
|
||||
(0, -1),
|
||||
(1, 1),
|
||||
(1, -1),
|
||||
(-1, 1),
|
||||
(-1, -1)
|
||||
];
|
||||
|
||||
char[] word = ['X', 'M', 'A', 'S'];
|
||||
|
||||
int count = 0;
|
||||
|
||||
foreach (var pattern in patterns)
|
||||
{
|
||||
bool valid = true;
|
||||
|
||||
for (int i = 0; i < word.Length; i++)
|
||||
{
|
||||
var xTarget = x + (pattern.xOffset * i);
|
||||
var yTarget = y + (pattern.yOffset * i);
|
||||
|
||||
if (xTarget < 0 || xTarget >= grid.GetLength(0)
|
||||
|| yTarget < 0 || yTarget >= grid.GetLength(1)
|
||||
|| word[i] != grid[xTarget, yTarget])
|
||||
{
|
||||
valid = false;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (valid)
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
var xmasCount = 0;
|
||||
|
||||
var grid = Input.AsCharGrid();
|
||||
|
||||
for (int x = 0; x < grid.GetLength(0); x++)
|
||||
{
|
||||
for (int y = 0; y < grid.GetLength(1); y++)
|
||||
{
|
||||
xmasCount += CheckXmas(x, y);
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Number of safe reports: [yellow]{xmasCount}[/][/]");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
int CheckXmas(int x, int y)
|
||||
{
|
||||
((int xOffset, int yOffset) first, (int xOffset, int yOffset) second)[] patterns = [
|
||||
((-1, -1), (-1, 1)),
|
||||
((-1, -1), (1, -1)),
|
||||
((1, -1), (1, 1)),
|
||||
((1, 1), (-1, 1))
|
||||
];
|
||||
|
||||
char[] word = ['M', 'S'];
|
||||
|
||||
// Center element of the cross must be an 'A'
|
||||
if (grid[x, y] != 'A')
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Make sure we're not going out of bounds
|
||||
if (x - 1 < 0 || x + 1 >= grid.GetLength(0) ||
|
||||
y - 1 < 0 || y + 1 >= grid.GetLength(1))
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
int count = 0;
|
||||
|
||||
foreach (var (first, second) in patterns)
|
||||
{
|
||||
if (grid[x + first.xOffset, y + first.yOffset] is 'M' && grid[x - first.xOffset, y - first.yOffset] is 'S' &&
|
||||
grid[x + second.xOffset, y + second.yOffset] is 'M' && grid[x - second.xOffset, y - second.yOffset] is 'S')
|
||||
{
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
}
|
||||
}
|
||||
121
Days/Day5.cs
121
Days/Day5.cs
@@ -1,121 +0,0 @@
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day5 : Day
|
||||
{
|
||||
public override int Number => 5;
|
||||
public override string Name => "Print Queue";
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var result = 0;
|
||||
|
||||
var precedences = new HashSet<int>?[100];
|
||||
|
||||
var readingPrecedences = true;
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
if (line.IsWhiteSpace())
|
||||
{
|
||||
readingPrecedences = false;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (readingPrecedences)
|
||||
{
|
||||
var split = line.Split('|');
|
||||
split.MoveNext();
|
||||
|
||||
var ruleTarget = int.Parse(line[split.Current]);
|
||||
|
||||
split.MoveNext();
|
||||
|
||||
var ruleNumber = int.Parse(line[split.Current]);
|
||||
|
||||
if (precedences[ruleTarget] is not { } precedenceSet)
|
||||
{
|
||||
precedenceSet = [ ruleNumber ];
|
||||
precedences[ruleTarget] = precedenceSet;
|
||||
}
|
||||
else
|
||||
{
|
||||
precedenceSet.Add(ruleNumber);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
result += CheckLine(line);
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Sum of middle page numbers: [yellow]{result}[/][/]");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
int CheckLine(ReadOnlySpan<char> line)
|
||||
{
|
||||
Span<int> positions = stackalloc int[100];
|
||||
|
||||
// Read line to get positions
|
||||
var position = 1;
|
||||
foreach (var range in line.Split(','))
|
||||
{
|
||||
var number = int.Parse(line[range]);
|
||||
|
||||
positions[number] = position;
|
||||
|
||||
position++;
|
||||
}
|
||||
|
||||
var middlePosition = (position - 1) / 2;
|
||||
var middleNumber = 0;
|
||||
|
||||
var index = 0;
|
||||
// Check if positions respect precedence rules
|
||||
foreach (var range in line.Split(','))
|
||||
{
|
||||
var number = int.Parse(line[range]);
|
||||
|
||||
// Fetch middle number
|
||||
if (index == middlePosition)
|
||||
{
|
||||
middleNumber = number;
|
||||
}
|
||||
index++;
|
||||
|
||||
if (precedences[number] is not { } precedenceSet)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var numberPosition = positions[number];
|
||||
|
||||
// Check all precedence rules for numbers in the list
|
||||
foreach (var nextNumber in precedenceSet)
|
||||
{
|
||||
var nextNumberPosition = positions[nextNumber];
|
||||
|
||||
if (nextNumberPosition != 0 && numberPosition >= nextNumberPosition)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return middleNumber;
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
// if (display)
|
||||
// {
|
||||
// AnsiConsole.MarkupLine($"[green]Number of safe reports: [yellow]{xmasCount}[/][/]");
|
||||
// }
|
||||
}
|
||||
}
|
||||
194
Days/Day6.cs
194
Days/Day6.cs
@@ -1,194 +0,0 @@
|
||||
using System.Numerics;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day6 : Day
|
||||
{
|
||||
public override int Number => 6;
|
||||
public override string Name => "Guard Gallivant";
|
||||
|
||||
private const int GridSize = 130;
|
||||
|
||||
private static readonly Point DirectionUp = new(0, -1);
|
||||
private static readonly Point DirectionDown = new(0, 1);
|
||||
private static readonly Point DirectionLeft = new(-1, 0);
|
||||
private static readonly Point DirectionRight = new(1, 0);
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
HashSet<Point> visited = [];
|
||||
var direction = DirectionUp;
|
||||
|
||||
var (obstaclesGrid, position) = ReadInputGrid();
|
||||
|
||||
visited.Add(position);
|
||||
|
||||
// Move the guard till it leaves the grid
|
||||
while (true)
|
||||
{
|
||||
var destination = position + direction;
|
||||
|
||||
if (destination.X is < 0 or >= GridSize ||
|
||||
destination.Y is < 0 or >= GridSize)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if it's possible to move or if there is an obstacle
|
||||
if (obstaclesGrid[destination.X, destination.Y])
|
||||
{
|
||||
direction = NextDirection(direction);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
position = destination;
|
||||
|
||||
visited.Add(position);
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Amount of distinct positions: [yellow]{visited.Count}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
var possibleLoops = 0;
|
||||
var (initialGrid, startPosition) = ReadInputGrid();
|
||||
|
||||
for (var y = 0; y < GridSize; y++)
|
||||
{
|
||||
for (var x = 0; x < GridSize; x++)
|
||||
{
|
||||
// Store obstacle state
|
||||
var initialState = initialGrid[x, y];
|
||||
|
||||
// Check if grid is a loop after adding an obstacle there
|
||||
initialGrid[x, y] = true;
|
||||
|
||||
if (IsLoop(initialGrid, startPosition))
|
||||
{
|
||||
possibleLoops++;
|
||||
}
|
||||
|
||||
// Restore obstacle state
|
||||
initialGrid[x, y] = initialState;
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Amount of possible loop positions: [yellow]{possibleLoops}[/][/]");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
bool IsLoop(bool[,] obstaclesGrid, Point position)
|
||||
{
|
||||
HashSet<(Point position, Point direction)> visited = [];
|
||||
var direction = DirectionUp;
|
||||
|
||||
visited.Add((position, direction));
|
||||
|
||||
// Move the guard till it loops or leaves the grid
|
||||
while (true)
|
||||
{
|
||||
var destination = position + direction;
|
||||
|
||||
if (visited.Contains((destination, direction)))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (destination.X is < 0 or >= GridSize || destination.Y is < 0 or >= GridSize)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if it's possible to move or if there is an obstacle
|
||||
if (obstaclesGrid[destination.X, destination.Y])
|
||||
{
|
||||
direction = NextDirection(direction);
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
position = destination;
|
||||
|
||||
visited.Add((position, direction));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private (bool[,] ObstaclesGrid, Point Start) ReadInputGrid()
|
||||
{
|
||||
var grid = new bool[GridSize, GridSize];
|
||||
Point start = default;
|
||||
|
||||
var y = 0;
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
var x = 0;
|
||||
|
||||
foreach (var symbol in line)
|
||||
{
|
||||
if (symbol == '^')
|
||||
{
|
||||
start = new Point(x, y);
|
||||
|
||||
grid[x, y] = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
grid[x, y] = symbol switch
|
||||
{
|
||||
'#' => true,
|
||||
_ => false
|
||||
};
|
||||
}
|
||||
|
||||
x++;
|
||||
}
|
||||
|
||||
y++;
|
||||
}
|
||||
|
||||
return (grid, start);
|
||||
}
|
||||
|
||||
private static Point NextDirection(Point direction)
|
||||
{
|
||||
if (direction == DirectionUp)
|
||||
{
|
||||
return DirectionRight;
|
||||
}
|
||||
|
||||
if (direction == DirectionRight)
|
||||
{
|
||||
return DirectionDown;
|
||||
}
|
||||
|
||||
if (direction == DirectionDown)
|
||||
{
|
||||
return DirectionLeft;
|
||||
}
|
||||
|
||||
if (direction == DirectionLeft)
|
||||
{
|
||||
return DirectionUp;
|
||||
}
|
||||
|
||||
throw new ArgumentException("Invalid direction", nameof(direction));
|
||||
}
|
||||
|
||||
private readonly record struct Point(int X, int Y) : IAdditionOperators<Point, Point, Point>
|
||||
{
|
||||
public static Point operator +(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X + right.X, left.Y + right.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
119
Days/Day7.cs
119
Days/Day7.cs
@@ -1,119 +0,0 @@
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day7 : Day
|
||||
{
|
||||
public override int Number => 7;
|
||||
public override string Name => "Bridge Repair";
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var calibrationResult = 0L;
|
||||
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
// Read input equation
|
||||
var equationStart = line.IndexOf(':');
|
||||
|
||||
var target = long.Parse(line[..equationStart]);
|
||||
|
||||
var numbers = new List<long>();
|
||||
var equationNumbers = line[(equationStart + 2)..];
|
||||
|
||||
foreach (var range in equationNumbers.Split(' '))
|
||||
{
|
||||
numbers.Add(int.Parse(equationNumbers[range]));
|
||||
}
|
||||
|
||||
// Check if equation is possible
|
||||
if (CheckEquation(target, numbers.ToArray().AsSpan()[1..], numbers[0]))
|
||||
{
|
||||
calibrationResult += target;
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Total calibration result: [yellow]{calibrationResult}[/][/]");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
bool CheckEquation(long target, ReadOnlySpan<long> numbers, long result)
|
||||
{
|
||||
// Early return when result is greater than target because result is increasing
|
||||
if (result > target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (numbers.IsEmpty)
|
||||
{
|
||||
return result == target;
|
||||
}
|
||||
|
||||
var sumResult = result + numbers[0];
|
||||
var multiplyResult = result * numbers[0];
|
||||
|
||||
return CheckEquation(target, numbers[1..], sumResult)
|
||||
|| CheckEquation(target, numbers[1..], multiplyResult);
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
var calibrationResult = 0L;
|
||||
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
// Read input equation
|
||||
var equationStart = line.IndexOf(':');
|
||||
|
||||
var target = long.Parse(line[..equationStart]);
|
||||
|
||||
var numbers = new List<long>();
|
||||
var equationNumbers = line[(equationStart + 2)..];
|
||||
|
||||
foreach (var range in equationNumbers.Split(' '))
|
||||
{
|
||||
numbers.Add(int.Parse(equationNumbers[range]));
|
||||
}
|
||||
|
||||
// Check if equation is possible
|
||||
if (CheckEquation(target, numbers.ToArray().AsSpan()[1..], numbers[0]))
|
||||
{
|
||||
calibrationResult += target;
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Total calibration result: [yellow]{calibrationResult}[/][/]");
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
bool CheckEquation(long target, ReadOnlySpan<long> numbers, long result)
|
||||
{
|
||||
// Early return when result is greater than target because result is increasing
|
||||
if (result > target)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (numbers.IsEmpty)
|
||||
{
|
||||
return result == target;
|
||||
}
|
||||
|
||||
var sumResult = result + numbers[0];
|
||||
var multiplyResult = result * numbers[0];
|
||||
var concatResult = result * (int)Math.Pow(10, (int)Math.Log10(numbers[0]) + 1) + numbers[0];
|
||||
|
||||
return CheckEquation(target, numbers[1..], sumResult)
|
||||
|| CheckEquation(target, numbers[1..], multiplyResult)
|
||||
|| CheckEquation(target, numbers[1..], concatResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
169
Days/Day8.cs
169
Days/Day8.cs
@@ -1,169 +0,0 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Numerics;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day8 : Day
|
||||
{
|
||||
public override int Number => 8;
|
||||
public override string Name => "Resonant Collinearity";
|
||||
|
||||
private const int GridSize = 50;
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
HashSet<Point> antinodePositions = [];
|
||||
|
||||
var frequencyAntennas = ParseAntennas();
|
||||
|
||||
// Find antinodes for each frequency
|
||||
foreach (var (_, antennas) in frequencyAntennas)
|
||||
{
|
||||
// Take antennas by pair to find antinodes
|
||||
foreach (var firstAntenna in antennas)
|
||||
{
|
||||
foreach (var secondAntenna in antennas.Where(a => a != firstAntenna))
|
||||
{
|
||||
var firstAntinode = firstAntenna + (firstAntenna - secondAntenna);
|
||||
var secondAntinode = secondAntenna + (secondAntenna - firstAntenna);
|
||||
|
||||
if (firstAntinode is { X: >= 0 and < GridSize, Y: >= 0 and < GridSize })
|
||||
{
|
||||
antinodePositions.Add(firstAntinode);
|
||||
}
|
||||
|
||||
if (secondAntinode is { X: >= 0 and < GridSize, Y: >= 0 and < GridSize })
|
||||
{
|
||||
antinodePositions.Add(secondAntinode);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Unique antinode positions count: [yellow]{antinodePositions.Count}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
HashSet<Point> antinodePositions = [];
|
||||
|
||||
var frequencyAntennas = ParseAntennas();
|
||||
|
||||
// Find antinodes for each frequency
|
||||
foreach (var (_, antennas) in frequencyAntennas)
|
||||
{
|
||||
// Take antennas by pair to find antinodes
|
||||
foreach (var firstAntenna in antennas)
|
||||
{
|
||||
foreach (var secondAntenna in antennas.Where(a => a != firstAntenna))
|
||||
{
|
||||
// Always add antinodes on antennas if a pair is formed
|
||||
antinodePositions.Add(firstAntenna);
|
||||
antinodePositions.Add(secondAntenna);
|
||||
|
||||
// Compute potential antinode positions
|
||||
var firstAntinodeOffset = firstAntenna - secondAntenna;
|
||||
var secondAntinodeOffset = secondAntenna - firstAntenna;
|
||||
|
||||
for (var i = 1; ; i++)
|
||||
{
|
||||
var firstAntinode = firstAntenna + (i * firstAntinodeOffset);
|
||||
|
||||
if (firstAntinode is { X: >= 0 and < GridSize, Y: >= 0 and < GridSize })
|
||||
{
|
||||
antinodePositions.Add(firstAntinode);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 1; ; i++)
|
||||
{
|
||||
var secondAntinode = secondAntenna + (i * secondAntinodeOffset);
|
||||
|
||||
if (secondAntinode is { X: >= 0 and < GridSize, Y: >= 0 and < GridSize })
|
||||
{
|
||||
antinodePositions.Add(secondAntinode);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Unique antinode positions count: [yellow]{antinodePositions.Count}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
private FrozenDictionary<char, List<Point>> ParseAntennas()
|
||||
{
|
||||
var antennas = new Dictionary<char, List<Point>>();
|
||||
|
||||
var y = 0;
|
||||
foreach (var line in Input.AsSpan().EnumerateLines())
|
||||
{
|
||||
var x = 0;
|
||||
|
||||
foreach (var frequency in line)
|
||||
{
|
||||
if (frequency is '.')
|
||||
{
|
||||
x++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!antennas.TryGetValue(frequency, out var list))
|
||||
{
|
||||
list = [];
|
||||
antennas[frequency] = list;
|
||||
}
|
||||
|
||||
list.Add(new Point(x, y));
|
||||
|
||||
x++;
|
||||
}
|
||||
|
||||
y++;
|
||||
}
|
||||
|
||||
return antennas.ToFrozenDictionary();
|
||||
}
|
||||
|
||||
private readonly record struct Point(int X, int Y)
|
||||
: IAdditionOperators<Point, Point, Point>,
|
||||
ISubtractionOperators<Point, Point, Point>,
|
||||
IMultiplyOperators<Point, int, Point>
|
||||
{
|
||||
public static Point operator +(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X + right.X, left.Y + right.Y);
|
||||
}
|
||||
|
||||
public static Point operator -(Point left, Point right)
|
||||
{
|
||||
return new Point(left.X - right.X, left.Y - right.Y);
|
||||
}
|
||||
|
||||
public static Point operator *(Point left, int right)
|
||||
{
|
||||
return new Point(left.X * right, left.Y * right);
|
||||
}
|
||||
|
||||
public static Point operator *(int left, Point right)
|
||||
{
|
||||
return new Point(right.X * left, right.Y * left);
|
||||
}
|
||||
}
|
||||
}
|
||||
181
Days/Day9.cs
181
Days/Day9.cs
@@ -1,181 +0,0 @@
|
||||
using System.Collections.Frozen;
|
||||
using System.Numerics;
|
||||
using Spectre.Console;
|
||||
|
||||
namespace AdventOfCode.Days;
|
||||
|
||||
public class Day9 : Day
|
||||
{
|
||||
public override int Number => 9;
|
||||
public override string Name => "Disk Fragmenter";
|
||||
|
||||
public override void RunPart1(bool display = true)
|
||||
{
|
||||
var (blocks, freeBlocksStarts) = ParseDiskBlocks();
|
||||
|
||||
var nextFreeIndex = freeBlocksStarts.Dequeue();
|
||||
|
||||
for (var i = blocks.Count - 1; i >= 0; i--)
|
||||
{
|
||||
// There is no more available space to move blocks into on the left
|
||||
if (nextFreeIndex >= i)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (blocks[i] is -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
blocks[nextFreeIndex] = blocks[i];
|
||||
blocks[i] = -1;
|
||||
|
||||
nextFreeIndex++;
|
||||
|
||||
// Go to next free blocks if current free block is full
|
||||
if (blocks[nextFreeIndex] is not -1)
|
||||
{
|
||||
nextFreeIndex = freeBlocksStarts.Dequeue();
|
||||
}
|
||||
}
|
||||
|
||||
var checksum = blocks
|
||||
.Select((block, index) => block is -1 ? 0 : (long)block * index)
|
||||
.Sum();
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Filesystem checksum: [yellow]{checksum}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
public override void RunPart2(bool display = true)
|
||||
{
|
||||
var (blocks, freeBlocks) = ParseDiskBlocks2();
|
||||
|
||||
var mapStart = string.Join(" ", blocks);
|
||||
|
||||
for (var i = blocks.Count - 1; i >= 0; i--)
|
||||
{
|
||||
if (blocks[i] is -1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
// Get file length
|
||||
var fileId = blocks[i];
|
||||
var endPosition = i;
|
||||
var startPosition = i;
|
||||
|
||||
while (startPosition > 0 && blocks[startPosition - 1] == fileId)
|
||||
{
|
||||
startPosition--;
|
||||
}
|
||||
|
||||
var length = (endPosition - startPosition) + 1;
|
||||
|
||||
// Find first available free block on the left
|
||||
var freeBlockIndex = freeBlocks.FindIndex(b => b.StartIndex < startPosition && b.Length >= length);
|
||||
|
||||
if (freeBlockIndex is not -1)
|
||||
{
|
||||
var freeBlock = freeBlocks[freeBlockIndex];
|
||||
|
||||
for (var offset = 0; offset < length; offset++)
|
||||
{
|
||||
blocks[freeBlock.StartIndex + offset] = blocks[startPosition + offset];
|
||||
blocks[startPosition + offset] = -1;
|
||||
}
|
||||
|
||||
// Update free block start and length
|
||||
freeBlocks[freeBlockIndex] = (freeBlock.StartIndex + length, freeBlock.Length - length);
|
||||
}
|
||||
|
||||
// Move to next file or free block
|
||||
i = startPosition;
|
||||
}
|
||||
|
||||
var mapEnd = string.Join(" ", blocks).Replace("-1", ".");
|
||||
|
||||
var checksum = blocks
|
||||
.Select((block, index) => block is -1 ? 0 : (long)block * index)
|
||||
.Sum();
|
||||
|
||||
if (display)
|
||||
{
|
||||
AnsiConsole.MarkupLine($"[green]Filesystem checksum: [yellow]{checksum}[/][/]");
|
||||
}
|
||||
}
|
||||
|
||||
private (List<int> Blocks, Queue<int> FreeBlocksStarts) ParseDiskBlocks()
|
||||
{
|
||||
var blocks = new List<int>(20_000 * 10);
|
||||
var freeBlocksStarts = new Queue<int>();
|
||||
|
||||
var readingBlock = true;
|
||||
var blockId = 0;
|
||||
|
||||
foreach (var length in Input.Select(encoded => encoded - '0'))
|
||||
{
|
||||
if (readingBlock)
|
||||
{
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
blocks.Add(blockId);
|
||||
}
|
||||
|
||||
blockId++;
|
||||
}
|
||||
else if (length > 0)
|
||||
{
|
||||
freeBlocksStarts.Enqueue(blocks.Count);
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
blocks.Add(-1);
|
||||
}
|
||||
}
|
||||
|
||||
readingBlock = !readingBlock;
|
||||
}
|
||||
|
||||
return (blocks, freeBlocksStarts);
|
||||
}
|
||||
|
||||
private (List<int> Blocks, List<(int StartIndex, int Length)> FreeBlocks) ParseDiskBlocks2()
|
||||
{
|
||||
var blocks = new List<int>(20_000 * 10);
|
||||
var freeBlocksStarts = new List<(int StartIndex, int Length)>();
|
||||
|
||||
var readingBlock = true;
|
||||
var blockId = 0;
|
||||
|
||||
foreach (var length in Input.Select(encoded => encoded - '0'))
|
||||
{
|
||||
if (readingBlock)
|
||||
{
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
blocks.Add(blockId);
|
||||
}
|
||||
|
||||
blockId++;
|
||||
}
|
||||
else if (length > 0)
|
||||
{
|
||||
freeBlocksStarts.Add((blocks.Count, length));
|
||||
|
||||
for (var i = 0; i < length; i++)
|
||||
{
|
||||
blocks.Add(-1);
|
||||
}
|
||||
}
|
||||
|
||||
readingBlock = !readingBlock;
|
||||
}
|
||||
|
||||
return (blocks, freeBlocksStarts);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user