Files
AdventOfCode/Days/Day7.cs
2025-12-07 20:04:48 +01:00

175 lines
5.5 KiB
C#

using System.Buffers;
using System.Numerics;
using Spectre.Console;
namespace AdventOfCode.Days;
public class Day7 : Day
{
public override int Number => 7;
public override string Name => "Laboratories";
public const int Width = 141;
public const int Height = 142;
public override void RunPart1(bool display = true)
{
var splitCount = 0;
var splittersMap = ParseSplittersMap(Input, out var startPosition);
var beams = new HashSet<Position>();
var nextLineBeams = new List<Position>();
var usedSplitters = new HashSet<Position>();
beams.Add(startPosition with { I = startPosition.I + 1 });
// Simulate beams going down
var row = 1;
while (row < Height - 1)
{
foreach (var beamPosition in beams)
{
var targetPosition = beamPosition with { I = beamPosition.I + 1 };
// There is nothing below
if (!splittersMap[targetPosition.I, targetPosition.J])
{
nextLineBeams.Add(targetPosition);
}
// There is a splitter, only counts it if not already used
else if (usedSplitters.Add(targetPosition))
{
splitCount++;
// Left and right beam
if (targetPosition.J - 1 >= 0)
{
nextLineBeams.Add(targetPosition with { J = targetPosition.J - 1 });
}
if (targetPosition.J + 1 < Width)
{
nextLineBeams.Add(targetPosition with { J = targetPosition.J + 1 });
}
}
}
// Always delete current line beams since we iterate from top to bottom
beams.Clear();
foreach (var nextLineBeam in nextLineBeams)
{
beams.Add(nextLineBeam);
}
nextLineBeams.Clear();
// Also clear the used splitters list for next line
usedSplitters.Clear();
row++;
}
if (display)
{
AnsiConsole.MarkupLine($"[green]Beam split count: [yellow]{splitCount}[/][/]");
}
}
public override void RunPart2(bool display = true)
{
var splittersMap = ParseSplittersMap(Input, out var startPosition);
var quantumPositions = new Dictionary<Position, long>();
var nextQuantumPositions = new Dictionary<Position, long>();
quantumPositions.Add(startPosition with { I = startPosition.I + 1 }, 1L);
// Simulate beams going down for each timeline
var row = 1;
while (row < Height - 1)
{
// Simulation step
foreach (var (beamPosition, timelinesCount) in quantumPositions)
{
var targetPosition = beamPosition with { I = beamPosition.I + 1 };
// There is nothing below, keep going down
if (!splittersMap[targetPosition.I, targetPosition.J])
{
UpdateTimelines(nextQuantumPositions, targetPosition, timelinesCount);
}
else
{
// Left and right beams
var leftBeamPosition = targetPosition with { J = targetPosition.J - 1 };
var rightBeamPosition = targetPosition with { J = targetPosition.J + 1 };
UpdateTimelines(nextQuantumPositions, leftBeamPosition, timelinesCount);
UpdateTimelines(nextQuantumPositions, rightBeamPosition, timelinesCount);
}
}
// Always delete current line beams since we iterate from top to bottom
quantumPositions.Clear();
foreach (var nextQuantumPosition in nextQuantumPositions)
{
quantumPositions.Add(nextQuantumPosition.Key, nextQuantumPosition.Value);
}
nextQuantumPositions.Clear();
row++;
}
if (display)
{
AnsiConsole.MarkupLine($"[green]Timelines count: [yellow]{quantumPositions.Sum(p => p.Value)}[/][/]");
}
return;
// Update and merge timelines
static void UpdateTimelines(Dictionary<Position, long> nextQuantumPositions, Position targetPosition, long timelinesCount)
{
// Merge timelines if there is already one for this target position
if (nextQuantumPositions.TryGetValue(targetPosition, out var otherTimelinesCount))
{
nextQuantumPositions[targetPosition] = otherTimelinesCount + timelinesCount;
}
// No timelines to merge, no new timeline
else
{
nextQuantumPositions[targetPosition] = timelinesCount;
}
}
}
private static bool[,] ParseSplittersMap(string input, out Position startPosition)
{
var map = new bool[Height, Width];
startPosition = new Position(0, 0);
var i = 0;
foreach (var line in input.EnumerateLines())
{
var j = 0;
foreach (var symbol in line)
{
// Find start
if (symbol is 'S')
{
startPosition = new Position(i, j);
continue;
}
map[i, j] = symbol is '^';
j++;
}
i++;
}
return map;
}
private record Position(int I, int J);
}