[2k25] Add day 1

This commit is contained in:
2025-12-01 22:14:56 +01:00
parent f8843866ca
commit 0c3a8478d4
51 changed files with 4819 additions and 24970 deletions

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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)..])
);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}

View File

@@ -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}[/][/]");
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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}[/][/]");
// }
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}