Compare commits

4 Commits

Author SHA1 Message Date
b7b3192510 Add day 16 (not-working) 2024-06-03 14:51:50 +02:00
63c866f5a0 Add benchmark support using -b 2022-12-17 21:48:16 +01:00
147c348ee1 Add approximate run time using StopWatch 2022-12-17 19:30:38 +01:00
aa833569da Add day 15 2022-12-17 19:17:15 +01:00
21 changed files with 986 additions and 340 deletions

1
.gitignore vendored
View File

@@ -5,3 +5,4 @@ riderModule.iml
/_ReSharper.Caches/ /_ReSharper.Caches/
.idea/.idea.AdventOfCode/.idea .idea/.idea.AdventOfCode/.idea
AdventOfCode.sln.DotSettings.user AdventOfCode.sln.DotSettings.user
BenchmarkDotNet.Artifacts

View File

@@ -8,6 +8,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.13.2" />
<PackageReference Include="Spectre.Console" Version="0.45.0" /> <PackageReference Include="Spectre.Console" Version="0.45.0" />
</ItemGroup> </ItemGroup>

23
DayBenchmarker.cs Normal file
View File

@@ -0,0 +1,23 @@
using AdventOfCode.Days;
using BenchmarkDotNet.Attributes;
namespace AdventOfCode;
[ShortRunJob]
[MemoryDiagnoser(false)]
public class DayBenchmark
{
private Day Day => new Day15();
[Benchmark]
public void Part1()
{
Day.RunPart1(display: false);
}
[Benchmark]
public void Part2()
{
Day.RunPart2(display: false);
}
}

View File

@@ -4,9 +4,9 @@ public abstract class Day
{ {
public abstract int Number { get; } public abstract int Number { get; }
public abstract string Name { get; } public abstract string Name { get; }
public abstract void RunPart1(); public abstract void RunPart1(bool display = true);
public abstract void RunPart2(); public abstract void RunPart2(bool display = true);
public override string ToString() public override string ToString()
{ {

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,7 @@ public class Day10 : Day
{ {
public override int Number => 10; public override int Number => 10;
public override string Name => "Cathode-Ray Tube"; public override string Name => "Cathode-Ray Tube";
public override void RunPart1() public override void RunPart1(bool display = true)
{ {
int cycle = 1; int cycle = 1;
int x = 1; int x = 1;
@@ -47,10 +47,13 @@ public class Day10 : Day
} }
} }
AnsiConsole.MarkupLine($"[green]Sum of signal strengths: [yellow]{sum}[/][/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]Sum of signal strengths: [yellow]{sum}[/][/]");
}
} }
public override void RunPart2() public override void RunPart2(bool display = true)
{ {
// Setup the CRT // Setup the CRT
const char litPixel = '#'; const char litPixel = '#';
@@ -122,7 +125,10 @@ public class Day10 : Day
} }
// Print the CRT // Print the CRT
AnsiConsole.Write(crtCanvas); if (display)
{
AnsiConsole.Write(crtCanvas);
}
} }
#region Input #region Input

View File

@@ -51,9 +51,9 @@ public class Monkey
// Divide worry level by 3 before test // Divide worry level by 3 before test
if (divideWorry) if (divideWorry)
{ {
newValue = newValue / 3; newValue = newValue / 3;
} }
// Even using this simplification, it's still needed to use longs because // Even using this simplification, it's still needed to use longs because
// they sometime overflow before being simplified in part 2 // they sometime overflow before being simplified in part 2
newValue = newValue % (2*3*5*7*11*13*17*19); newValue = newValue % (2*3*5*7*11*13*17*19);
@@ -77,7 +77,7 @@ public class Day11 : Day
{ {
public override int Number => 11; public override int Number => 11;
public override string Name => "Monkey in the Middle"; public override string Name => "Monkey in the Middle";
public override void RunPart1() public override void RunPart1(bool display = true)
{ {
const int roundsCount = 20; const int roundsCount = 20;
@@ -95,10 +95,13 @@ public class Day11 : Day
var topMonkeys = monkeys.OrderByDescending(m => m.InspectionCount).Take(2).ToList(); var topMonkeys = monkeys.OrderByDescending(m => m.InspectionCount).Take(2).ToList();
AnsiConsole.MarkupLine($"[green]Monkey business: [yellow]{topMonkeys[0].InspectionCount * topMonkeys[1].InspectionCount}[/][/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]Monkey business: [yellow]{topMonkeys[0].InspectionCount * topMonkeys[1].InspectionCount}[/][/]");
}
} }
public override void RunPart2() public override void RunPart2(bool display = true)
{ {
const int roundsCount = 10_000; const int roundsCount = 10_000;
@@ -116,7 +119,10 @@ public class Day11 : Day
var topMonkeys = monkeys.OrderByDescending(m => m.InspectionCount).Take(2).ToList(); var topMonkeys = monkeys.OrderByDescending(m => m.InspectionCount).Take(2).ToList();
AnsiConsole.MarkupLine($"[green]Monkey business: [yellow]{topMonkeys[0].InspectionCount * topMonkeys[1].InspectionCount}[/][/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]Monkey business: [yellow]{topMonkeys[0].InspectionCount * topMonkeys[1].InspectionCount}[/][/]");
}
} }
private static IImmutableList<Monkey> ParseMonkeys() private static IImmutableList<Monkey> ParseMonkeys()

View File

@@ -43,7 +43,7 @@ public class Day12 : Day
private (int x, int y) _end; private (int x, int y) _end;
private (int x, int y) _start; private (int x, int y) _start;
public override void RunPart1() public override void RunPart1(bool display = true)
{ {
var map = ParseMap(); var map = ParseMap();
var canvas = DrawMap(map); var canvas = DrawMap(map);
@@ -57,13 +57,16 @@ public class Day12 : Day
canvas.SetPixel(x, y, Color.White); canvas.SetPixel(x, y, Color.White);
} }
AnsiConsole.Write(canvas); if (display)
AnsiConsole.WriteLine(); {
AnsiConsole.Write(canvas);
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine($"[green]Minimum path length: [yellow]{minimumPathLength.length}[/][/]"); AnsiConsole.MarkupLine($"[green]Minimum path length: [yellow]{minimumPathLength.length}[/][/]");
}
} }
public override void RunPart2() public override void RunPart2(bool display = true)
{ {
var map = ParseMap(); var map = ParseMap();
var canvas = DrawMap(map); var canvas = DrawMap(map);
@@ -77,10 +80,13 @@ public class Day12 : Day
canvas.SetPixel(x, y, Color.White); canvas.SetPixel(x, y, Color.White);
} }
AnsiConsole.Write(canvas); if (display)
AnsiConsole.WriteLine(); {
AnsiConsole.Write(canvas);
AnsiConsole.WriteLine();
AnsiConsole.MarkupLine($"[green]Minimum path length: [yellow]{minimumPathLength.length}[/][/]"); AnsiConsole.MarkupLine($"[green]Minimum path length: [yellow]{minimumPathLength.length}[/][/]");
}
} }
private (int length, List<(int x, int y)> path) ExplorePath(byte[,] map) private (int length, List<(int x, int y)> path) ExplorePath(byte[,] map)

View File

@@ -153,7 +153,7 @@ public class Day13 : Day
{ {
public override int Number => 13; public override int Number => 13;
public override string Name => "Distress Signal"; public override string Name => "Distress Signal";
public override void RunPart1() public override void RunPart1(bool display = true)
{ {
var pairs = ParsePacketPairs(); var pairs = ParsePacketPairs();
@@ -169,10 +169,13 @@ public class Day13 : Day
} }
} }
AnsiConsole.MarkupLine($"[green]Right order pair indices sum: [yellow]{sum}[/][/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]Right order pair indices sum: [yellow]{sum}[/][/]");
}
} }
public override void RunPart2() public override void RunPart2(bool display = true)
{ {
var packets = ParsePackets(); var packets = ParsePackets();
@@ -186,7 +189,11 @@ public class Day13 : Day
var firstDividerIndex = orderedPackets.IndexOf(firstDivider) + 1; var firstDividerIndex = orderedPackets.IndexOf(firstDivider) + 1;
var lastDividerIndex = orderedPackets.IndexOf(lastDivider) + 1; var lastDividerIndex = orderedPackets.IndexOf(lastDivider) + 1;
AnsiConsole.MarkupLine($"[green]Decoder key is: [yellow]{firstDividerIndex * lastDividerIndex}[/][/]");
if (display)
{
AnsiConsole.MarkupLine($"[green]Decoder key is: [yellow]{firstDividerIndex * lastDividerIndex}[/][/]");
}
} }
private static PacketElement ReadElement(ref ReadOnlySpan<char> packet) private static PacketElement ReadElement(ref ReadOnlySpan<char> packet)
{ {

View File

@@ -118,10 +118,10 @@ public class Day14 : Day
public override int Number => 14; public override int Number => 14;
public override string Name => "Regolith Reservoir"; public override string Name => "Regolith Reservoir";
private Point _sandSource = new(500, 0); private readonly Point _sandSource = new(500, 0);
private Point _offsetSandSource; private Point _offsetSandSource;
public override void RunPart1() public override void RunPart1(bool display = true)
{ {
var map = GenerateMap(); var map = GenerateMap();
@@ -136,12 +136,15 @@ public class Day14 : Day
sandBlocksCount++; sandBlocksCount++;
} }
AnsiConsole.Write(canvas); if (display)
{
AnsiConsole.Write(canvas);
AnsiConsole.MarkupLine($"[green]Total sand blocks: [yellow]{sandBlocksCount}[/][/]"); AnsiConsole.MarkupLine($"[green]Total sand blocks: [yellow]{sandBlocksCount}[/][/]");
}
} }
public override void RunPart2() public override void RunPart2(bool display = true)
{ {
var map = GenerateDictionaryMap(); var map = GenerateDictionaryMap();
@@ -156,12 +159,16 @@ public class Day14 : Day
sandBlocksCount++; sandBlocksCount++;
} }
AnsiConsole.Write(GenerateCanvas(map, _sandSource));
AnsiConsole.MarkupLine($"[green]Total sand blocks: [yellow]{sandBlocksCount}[/][/]"); if (display)
{
AnsiConsole.Write(GenerateCanvas(map, _sandSource));
AnsiConsole.MarkupLine($"[green]Total sand blocks: [yellow]{sandBlocksCount}[/][/]");
}
} }
private bool GenerateSand(CellType[,] map, Canvas canvas, in Point sandSource) private static bool GenerateSand(CellType[,] map, Canvas canvas, in Point sandSource)
{ {
// Stop if source is blocked // Stop if source is blocked
if (map[sandSource.X, sandSource.Y] == CellType.Sand) if (map[sandSource.X, sandSource.Y] == CellType.Sand)
@@ -230,7 +237,7 @@ public class Day14 : Day
return true; return true;
} }
private bool GenerateSand(IDictionary<Point, CellType> map, in Point sandSource, int groundY) private static bool GenerateSand(IDictionary<Point, CellType> map, in Point sandSource, int groundY)
{ {
// Stop if source is blocked // Stop if source is blocked
if (map.GetValueOrDefault(sandSource, CellType.Air) == CellType.Sand) if (map.GetValueOrDefault(sandSource, CellType.Air) == CellType.Sand)
@@ -374,7 +381,7 @@ public class Day14 : Day
}; };
} }
private IDictionary<Point, CellType> GenerateDictionaryMap() private static IDictionary<Point, CellType> GenerateDictionaryMap()
{ {
var paths = Input.ReadAllLines() var paths = Input.ReadAllLines()
.Select(line => new MapPath(line.Split(" -> ") .Select(line => new MapPath(line.Split(" -> ")

292
Days/Day15.cs Normal file
View File

@@ -0,0 +1,292 @@
using System.Collections.Concurrent;
using System.Drawing;
using Spectre.Console;
namespace AdventOfCode.Days;
public class SensorBeaconPair
{
public Point SensorPosition { get; }
public Point BeaconPosition { get; }
public int SensorRange =>
Math.Abs(SensorPosition.X - BeaconPosition.X) + Math.Abs(SensorPosition.Y - BeaconPosition.Y);
public SensorBeaconPair(in Point sensorPosition, in Point beaconPosition)
{
SensorPosition = sensorPosition;
BeaconPosition = beaconPosition;
}
}
public class CoverRange
{
public int Start { get; }
public int End { get; }
public int Covered => End - Start + 1;
public CoverRange(int start, int end)
{
Start = start;
End = end;
}
public CoverRange(CoverRange coverRange)
{
Start = coverRange.Start;
End = coverRange.End;
}
public CoverRange Subtract(CoverRange other)
{
// Overlap on left
int newStart;
if (other.Start <= Start && other.End >= Start)
{
newStart = other.End + 1;
}
else
{
newStart = Start;
}
// Overlap on right
int newEnd;
if (other.Start <= End && other.End >= End)
{
newEnd = other.Start - 1;
}
else
{
newEnd = End;
}
return new CoverRange(newStart, newEnd);
}
public static CoverRange operator -(CoverRange left, CoverRange right)
{
return left.Subtract(right);
}
}
public class Day15 : Day
{
public override int Number => 15;
public override string Name => "Beacon Exclusion Zone";
public override void RunPart1(bool display = true)
{
const int targetY = 2_000_000;
// Parse pairs
var pairs = ParsePairs();
var ranges = new List<CoverRange>(pairs.Count);
// Get which columns are included on line targetY, remove the ones which already contains a beacon
var beaconsOnTargetY = new HashSet<int>();
foreach (var sensorBeaconPair in pairs)
{
// Get covered columns on y line
var xCenter = sensorBeaconPair.SensorPosition.X;
var coveredColumns = sensorBeaconPair.SensorRange - Math.Abs(sensorBeaconPair.SensorPosition.Y - targetY);
if (coveredColumns > 0)
{
ranges.Add(new CoverRange(xCenter - coveredColumns, xCenter + coveredColumns));
// Check if the beacon is on targetY
if (sensorBeaconPair.BeaconPosition.Y == targetY)
{
beaconsOnTargetY.Add(sensorBeaconPair.BeaconPosition.X);
}
}
}
// Remove overlapped ranges => inner.Start >= outer.Start && inner.End <= outer.End));
ranges.RemoveAll(inner =>
ranges.Where(outer => outer != inner).Any(outer => inner.Start >= outer.Start && inner.End <= outer.End));
// Compute unique number of x columns
long totalCovered = 0;
for (int i = 0; i < ranges.Count; i++)
{
var range = ranges[i];
var coverRange = range;
// Add number of covered columns and then subtract common ones
for (int j = i + 1; j < ranges.Count; j++)
{
var exclude = ranges[j];
coverRange -= exclude;
}
totalCovered += coverRange.Covered;
}
// Remove lines which contains a beacon
totalCovered -= beaconsOnTargetY.Count;
// Print total covered columns on line Y
if (display)
{
AnsiConsole.MarkupLine($"[green]Number of positions that cannot contain a beacon: [yellow]{totalCovered}[/][/]");
}
}
public override void RunPart2(bool display = true)
{
const int xMin = 0;
const int xMax = 4_000_000;
const int yMin = 0;
const int yMax = 4_000_000;
bool IsOutbound(Point point) => point.X is < xMin or > xMax || point.Y is < yMin or > yMax;
// Parse pairs
var pairs = ParsePairs();
// Find all points that are just outside a sensor range
var possiblePoints = new ConcurrentBag<Point>();
Parallel.For(0, pairs.Count, i =>
{
var pair = pairs[i];
var sensor = pair.SensorPosition;
var distance = pair.SensorRange + 1;
for (int x = -distance; x <= distance; x++)
{
var y = distance - Math.Abs(x);
var positivePoint = new Point(sensor.X + x, sensor.Y + y);
var negativePoint = new Point(sensor.X + x, sensor.Y - y);
// If both points are outbound, just skip
var positiveIsOutbound = IsOutbound(positivePoint);
var negativeIsOutbound = IsOutbound(negativePoint);
if (positiveIsOutbound && negativeIsOutbound)
{
continue;
}
// Check if this point is just outside at least one other sensor range
for (int otherIndex = 0; otherIndex < pairs.Count; otherIndex++)
{
if (otherIndex == i)
{
continue;
}
var otherPair = pairs[otherIndex];
var otherSensor = otherPair.SensorPosition;
var otherDistance = otherPair.SensorRange + 1;
if (!positiveIsOutbound)
{
var distancePositive = Math.Abs(positivePoint.X - otherSensor.X) +
Math.Abs(positivePoint.Y - otherSensor.Y);
if (distancePositive == otherDistance)
{
possiblePoints.Add(positivePoint);
}
}
if (!negativeIsOutbound)
{
var distanceNegative = Math.Abs(negativePoint.X - negativePoint.X) +
Math.Abs(negativePoint.Y - negativePoint.Y);
if (distanceNegative == otherDistance)
{
possiblePoints.Add(negativePoint);
}
}
}
}
});
// Keep the only outside detection point
var finalPoint = possiblePoints.First(f =>
pairs.All(p => Math.Abs(f.X - p.SensorPosition.X) + Math.Abs(f.Y - p.SensorPosition.Y) > p.SensorRange));
var tuningFrequency = finalPoint.X * (long) 4_000_000 + finalPoint.Y;
if (display)
{
AnsiConsole.MarkupLine($"[green]Tuning frequency: [yellow]{tuningFrequency}[/][/]");
}
}
private static IList<SensorBeaconPair> ParsePairs()
{
var pairs = new List<SensorBeaconPair>();
foreach (var line in Input.ReadAllLines())
{
var span = line.AsSpan();
// Parse sensor position
int sensorXStart = span.IndexOf('=') + 1;
int sensorXEnd = span.IndexOf(',');
var sensorX = int.Parse(span[sensorXStart..sensorXEnd]);
span = span[sensorXEnd..];
int sensorYStart = span.IndexOf('=') + 1;
int sensorYEnd = span.IndexOf(':');
var sensorY = int.Parse(span[sensorYStart..sensorYEnd]);
span = span[sensorYEnd..];
// Parse beacon
int beaconXStart = span.IndexOf('=') + 1;
int beaconXEnd = span.IndexOf(',');
var beaconX = int.Parse(span[beaconXStart..beaconXEnd]);
span = span[beaconXEnd..];
int beaconYStart = span.IndexOf('=') + 1;
var beaconY = int.Parse(span[beaconYStart..]);
pairs.Add(new SensorBeaconPair(new Point(sensorX, sensorY), new Point(beaconX, beaconY)));
}
return pairs;
}
#region Input
public const string Input =
"""
Sensor at x=1384790, y=3850432: closest beacon is at x=2674241, y=4192888
Sensor at x=2825953, y=288046: closest beacon is at x=2154954, y=-342775
Sensor at x=3553843, y=2822363: closest beacon is at x=3444765, y=2347460
Sensor at x=2495377, y=3130491: closest beacon is at x=2761496, y=2831113
Sensor at x=1329263, y=1778185: closest beacon is at x=2729595, y=2000000
Sensor at x=2882039, y=2206085: closest beacon is at x=2729595, y=2000000
Sensor at x=3903141, y=2510440: closest beacon is at x=4006219, y=3011198
Sensor at x=3403454, y=3996578: closest beacon is at x=3754119, y=4475047
Sensor at x=3630476, y=1048796: closest beacon is at x=3444765, y=2347460
Sensor at x=16252, y=2089672: closest beacon is at x=-276514, y=2995794
Sensor at x=428672, y=1150723: closest beacon is at x=-281319, y=668868
Sensor at x=2939101, y=3624676: closest beacon is at x=2674241, y=4192888
Sensor at x=3166958, y=2890076: closest beacon is at x=2761496, y=2831113
Sensor at x=3758241, y=3546895: closest beacon is at x=4006219, y=3011198
Sensor at x=218942, y=3011070: closest beacon is at x=-276514, y=2995794
Sensor at x=52656, y=3484635: closest beacon is at x=-276514, y=2995794
Sensor at x=2057106, y=405314: closest beacon is at x=2154954, y=-342775
Sensor at x=1966905, y=2495701: closest beacon is at x=2761496, y=2831113
Sensor at x=511976, y=2696731: closest beacon is at x=-276514, y=2995794
Sensor at x=3094465, y=2478570: closest beacon is at x=3444765, y=2347460
Sensor at x=806671, y=228252: closest beacon is at x=-281319, y=668868
Sensor at x=3011731, y=1976307: closest beacon is at x=2729595, y=2000000
""";
#endregion
}

219
Days/Day16.cs Normal file
View File

@@ -0,0 +1,219 @@
using Spectre.Console;
namespace AdventOfCode.Days;
public class Valve
{
public string Name { get; }
public int Rate { get; }
public IList<Valve> AccessibleValves { get; }
public Valve(string name, int rate)
{
Name = name;
Rate = rate;
AccessibleValves = new List<Valve>();
}
}
public record ValvePath(int CurrentMinute, int PressurePerMinute, int ReleasedPressure, Valve CurrentValve, HashSet<Valve> OpenedValves);
public class Day16 : Day
{
public override int Number => 16;
public override string Name => "Proboscidea Volcanium";
public override void RunPart1(bool display = true)
{
var startValve = ParseValves();
// Test all possibles paths
int maxPressure = 0;
var paths = new Queue<ValvePath>();
paths.Enqueue(new ValvePath(1, 0, 0, startValve, new HashSet<Valve>()));
// Remember best pressure at each valve
var valvePotential = new Dictionary<Valve, (int currentMinute, int potential)>();
while (paths.Count > 0)
{
var state = paths.Dequeue();
// Check if this state is worse than last one at this same valve
int potential = state.ReleasedPressure + (30 - state.CurrentMinute + 1) * state.PressurePerMinute;
if (valvePotential.TryGetValue(state.CurrentValve, out var lastState))
{
if (state.CurrentMinute >= lastState.currentMinute)
{
if (potential <= lastState.potential)
{
continue;
}
}
}
valvePotential[state.CurrentValve] = (state.CurrentMinute, potential);
// Release pressure at start of minute
var newPressure = state.ReleasedPressure + state.PressurePerMinute;
// Compute new max
if (newPressure > maxPressure)
{
maxPressure = newPressure;
}
// Stop at minute 30
if (state.CurrentMinute == 30)
{
continue;
}
// Check if the valve can be opened
if (!state.OpenedValves.Contains(state.CurrentValve))
{
// New state with current valve opened
var openedValves = new HashSet<Valve>(state.OpenedValves);
openedValves.Add(state.CurrentValve);
paths.Enqueue(new ValvePath(state.CurrentMinute + 1, state.PressurePerMinute + state.CurrentValve.Rate, newPressure, state.CurrentValve, openedValves));
}
// Move to all accessible valves
foreach (var accessibleValve in state.CurrentValve.AccessibleValves)
{
paths.Enqueue(new ValvePath(state.CurrentMinute + 1, state.PressurePerMinute, newPressure, accessibleValve, state.OpenedValves));
}
}
if (display)
{
AnsiConsole.MarkupLine($"[green]Max pressure released: [yellow]{maxPressure}[/][/]");
}
}
public override void RunPart2(bool display = true)
{
throw new NotImplementedException();
}
private static Valve ParseValves()
{
var valves = new Dictionary<string, Valve>();
// Create valves first
foreach (var line in Input.ReadAllLines())
{
var span = line.AsSpan();
var nameStart = span.IndexOf(' ') + 1;
var name = span[nameStart..(nameStart + 2)].ToString();
var rateStart = span.IndexOf('=') + 1;
var rateEnd = span.IndexOf(';');
var rate = int.Parse(span[rateStart..rateEnd]);
valves.Add(name, new Valve(name, rate));
}
// Add links
// Create valves first
foreach (var line in Input.ReadAllLines())
{
var span = line.AsSpan();
var nameStart = span.IndexOf(' ') + 1;
var name = span[nameStart..(nameStart + 2)].ToString();
var valve = valves[name];
var valvesStart = span.IndexOf(',') != -1 ? span.IndexOf(',') - 2 : span.Length - 2;
var valvesToAdd = span[valvesStart..].ToString().Split(", ").Select(v => valves[v]);
foreach (var valveToAdd in valvesToAdd)
{
valve.AccessibleValves.Add(valveToAdd);
}
}
return valves["AA"];
}
#region Input
public const string ExampleInput =
"""
Valve AA has flow rate=0; tunnels lead to valves DD, II, BB
Valve BB has flow rate=13; tunnels lead to valves CC, AA
Valve CC has flow rate=2; tunnels lead to valves DD, BB
Valve DD has flow rate=20; tunnels lead to valves CC, AA, EE
Valve EE has flow rate=3; tunnels lead to valves FF, DD
Valve FF has flow rate=0; tunnels lead to valves EE, GG
Valve GG has flow rate=0; tunnels lead to valves FF, HH
Valve HH has flow rate=22; tunnel leads to valve GG
Valve II has flow rate=0; tunnels lead to valves AA, JJ
Valve JJ has flow rate=21; tunnel leads to valve II
""";
public const string Input =
"""
Valve JI has flow rate=21; tunnels lead to valves WI, XG
Valve DM has flow rate=3; tunnels lead to valves JX, NG, AW, BY, PF
Valve AZ has flow rate=0; tunnels lead to valves FJ, VC
Valve YQ has flow rate=0; tunnels lead to valves TE, OP
Valve WI has flow rate=0; tunnels lead to valves JI, VC
Valve NE has flow rate=0; tunnels lead to valves ZK, AA
Valve FM has flow rate=0; tunnels lead to valves LC, DU
Valve QI has flow rate=0; tunnels lead to valves TE, JW
Valve OY has flow rate=0; tunnels lead to valves XS, VF
Valve XS has flow rate=18; tunnels lead to valves RR, OY, SV, NQ
Valve NU has flow rate=0; tunnels lead to valves IZ, BD
Valve JX has flow rate=0; tunnels lead to valves DM, ZK
Valve WT has flow rate=23; tunnels lead to valves OV, QJ
Valve KM has flow rate=0; tunnels lead to valves TE, OL
Valve NG has flow rate=0; tunnels lead to valves II, DM
Valve FJ has flow rate=0; tunnels lead to valves AZ, II
Valve QR has flow rate=0; tunnels lead to valves ZK, KI
Valve KI has flow rate=9; tunnels lead to valves ZZ, DI, TL, AJ, QR
Valve ON has flow rate=0; tunnels lead to valves LC, QT
Valve AW has flow rate=0; tunnels lead to valves DM, AA
Valve HI has flow rate=0; tunnels lead to valves TE, VC
Valve XG has flow rate=0; tunnels lead to valves II, JI
Valve II has flow rate=19; tunnels lead to valves LF, NG, OL, FJ, XG
Valve VC has flow rate=24; tunnels lead to valves WI, HI, AZ
Valve VJ has flow rate=0; tunnels lead to valves UG, AA
Valve IZ has flow rate=0; tunnels lead to valves VF, NU
Valve EJ has flow rate=0; tunnels lead to valves ZK, LC
Valve DU has flow rate=12; tunnels lead to valves TC, UG, FM
Valve ZK has flow rate=10; tunnels lead to valves JX, EJ, JW, QR, NE
Valve XF has flow rate=25; tunnels lead to valves OP, VT
Valve LC has flow rate=4; tunnels lead to valves FM, EJ, ON, AJ, PF
Valve SV has flow rate=0; tunnels lead to valves XS, IY
Valve LF has flow rate=0; tunnels lead to valves II, OV
Valve DI has flow rate=0; tunnels lead to valves KI, BY
Valve OP has flow rate=0; tunnels lead to valves YQ, XF
Valve NQ has flow rate=0; tunnels lead to valves TC, XS
Valve QJ has flow rate=0; tunnels lead to valves VT, WT
Valve IY has flow rate=22; tunnel leads to valve SV
Valve AJ has flow rate=0; tunnels lead to valves LC, KI
Valve TE has flow rate=11; tunnels lead to valves QI, HI, KM, YQ
Valve ZZ has flow rate=0; tunnels lead to valves KI, AA
Valve VT has flow rate=0; tunnels lead to valves XF, QJ
Valve OL has flow rate=0; tunnels lead to valves KM, II
Valve TC has flow rate=0; tunnels lead to valves NQ, DU
Valve TL has flow rate=0; tunnels lead to valves VF, KI
Valve QT has flow rate=0; tunnels lead to valves AA, ON
Valve BY has flow rate=0; tunnels lead to valves DM, DI
Valve OV has flow rate=0; tunnels lead to valves LF, WT
Valve VN has flow rate=0; tunnels lead to valves RR, BD
Valve VF has flow rate=13; tunnels lead to valves OY, IZ, TL
Valve BD has flow rate=17; tunnels lead to valves NU, VN
Valve UG has flow rate=0; tunnels lead to valves VJ, DU
Valve PF has flow rate=0; tunnels lead to valves LC, DM
Valve RR has flow rate=0; tunnels lead to valves XS, VN
Valve AA has flow rate=0; tunnels lead to valves QT, ZZ, AW, VJ, NE
Valve JW has flow rate=0; tunnels lead to valves ZK, QI
""";
#endregion
}

View File

@@ -18,10 +18,10 @@ public enum Outcome : long
public class Day2 : Day public class Day2 : Day
{ {
public override int Number { get; } = 2; public override int Number => 2;
public override string Name { get; } = "Rock Paper Scissors"; public override string Name => "Rock Paper Scissors";
public override void RunPart1() public override void RunPart1(bool display = true)
{ {
long score = 0; long score = 0;
@@ -33,14 +33,17 @@ public class Day2 : Day
score += (long) selfChoice; score += (long) selfChoice;
score += (long) ComputeOutcome(adversaryChoice, selfChoice); score += (long) ComputeOutcome(adversaryChoice, selfChoice);
} }
AnsiConsole.MarkupLine($"[green]Total score: [yellow]{score}[/][/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]Total score: [yellow]{score}[/][/]");
}
} }
public override void RunPart2() public override void RunPart2(bool display = true)
{ {
long score = 0; long score = 0;
foreach (var line in Input.ReadAllLines()) foreach (var line in Input.ReadAllLines())
{ {
var adversaryChoice = AdversaryInputToChoice(line[0]); var adversaryChoice = AdversaryInputToChoice(line[0]);
@@ -50,8 +53,11 @@ public class Day2 : Day
score += (long) selfChoice; score += (long) selfChoice;
score += (long) desiredOutcome; score += (long) desiredOutcome;
} }
AnsiConsole.MarkupLine($"[green]Total score: [yellow]{score}[/][/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]Total score: [yellow]{score}[/][/]");
}
} }
private Outcome ComputeOutcome(Choice adversary, Choice self) => (adversary, self) switch private Outcome ComputeOutcome(Choice adversary, Choice self) => (adversary, self) switch
@@ -83,7 +89,7 @@ public class Day2 : Day
'C' => Choice.Scissors, 'C' => Choice.Scissors,
_ => throw new ArgumentException("Invalid input") _ => throw new ArgumentException("Invalid input")
}; };
private Choice SelfInputToChoice(char input) => input switch private Choice SelfInputToChoice(char input) => input switch
{ {
'X' => Choice.Rock, 'X' => Choice.Rock,

View File

@@ -7,7 +7,7 @@ public class Day3 : Day
public override int Number => 3; public override int Number => 3;
public override string Name => "Rucksack Reorganization"; public override string Name => "Rucksack Reorganization";
public override void RunPart1() public override void RunPart1(bool display = true)
{ {
long sum = 0; long sum = 0;
@@ -22,10 +22,13 @@ public class Day3 : Day
sum += CharToPriority(common); sum += CharToPriority(common);
} }
AnsiConsole.MarkupLine($"[green]Sum of priorities is: [yellow]{sum}[/][/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]Sum of priorities is: [yellow]{sum}[/][/]");
}
} }
public override void RunPart2() public override void RunPart2(bool display = true)
{ {
long sum = 0; long sum = 0;
@@ -40,10 +43,13 @@ public class Day3 : Day
sum += CharToPriority(common); sum += CharToPriority(common);
} }
AnsiConsole.MarkupLine($"[green]Sum of priorities is: [yellow]{sum}[/][/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]Sum of priorities is: [yellow]{sum}[/][/]");
}
} }
private int CharToPriority(char c) private static int CharToPriority(char c)
{ {
if (char.IsLower(c)) if (char.IsLower(c))
{ {

View File

@@ -6,7 +6,7 @@ public class Day4 : Day
{ {
public override int Number => 4; public override int Number => 4;
public override string Name => "Camp Cleanup"; public override string Name => "Camp Cleanup";
public override void RunPart1() public override void RunPart1(bool display = true)
{ {
int overlaps = 0; int overlaps = 0;
foreach (var line in Input.ReadAllLines()) foreach (var line in Input.ReadAllLines())
@@ -18,7 +18,7 @@ public class Day4 : Day
var firstSection = sections[0]; var firstSection = sections[0];
var secondSection = sections[1]; var secondSection = sections[1];
// Check if first section contains second section // Check if first section contains second section
if (firstSection.Start <= secondSection.Start && firstSection.End >= secondSection.End) if (firstSection.Start <= secondSection.Start && firstSection.End >= secondSection.End)
{ {
@@ -30,11 +30,14 @@ public class Day4 : Day
overlaps++; overlaps++;
} }
} }
AnsiConsole.MarkupLine($"[green]Overlapping sections: [yellow]{overlaps}[/][/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]Overlapping sections: [yellow]{overlaps}[/][/]");
}
} }
public override void RunPart2() public override void RunPart2(bool display = true)
{ {
int overlaps = 0; int overlaps = 0;
foreach (var line in Input.ReadAllLines()) foreach (var line in Input.ReadAllLines())
@@ -46,15 +49,18 @@ public class Day4 : Day
var firstSection = sections[0]; var firstSection = sections[0];
var secondSection = sections[1]; var secondSection = sections[1];
// Check if first section overlaps second // Check if first section overlaps second
if (firstSection.Start <= secondSection.End && firstSection.End >= secondSection.Start) if (firstSection.Start <= secondSection.End && firstSection.End >= secondSection.Start)
{ {
overlaps++; overlaps++;
} }
} }
AnsiConsole.MarkupLine($"[green]Overlapping sections: [yellow]{overlaps}[/][/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]Overlapping sections: [yellow]{overlaps}[/][/]");
}
} }
#region Input #region Input

View File

@@ -9,7 +9,7 @@ public class Day5 : Day
private IDictionary<int, Stack<char>>? _stacks; private IDictionary<int, Stack<char>>? _stacks;
public override void RunPart1() public override void RunPart1(bool display = true)
{ {
_stacks = InitStacks(); _stacks = InitStacks();
@@ -26,14 +26,17 @@ public class Day5 : Day
} }
} }
for (int i = 1; i <= 9; i++) if (display)
{ {
AnsiConsole.Markup($"[{(i % 2 == 0 ? "yellow" : "green")}]{_stacks[i].Pop()}[/]"); for (int i = 1; i <= 9; i++)
{
AnsiConsole.Markup($"[{(i % 2 == 0 ? "yellow" : "green")}]{_stacks[i].Pop()}[/]");
}
AnsiConsole.WriteLine();
} }
AnsiConsole.WriteLine();
} }
public override void RunPart2() public override void RunPart2(bool display = true)
{ {
_stacks = InitStacks(); _stacks = InitStacks();
@@ -53,11 +56,14 @@ public class Day5 : Day
} }
} }
for (int i = 1; i <= 9; i++) if (display)
{ {
AnsiConsole.Markup($"[{(i % 2 == 0 ? "yellow" : "green")}]{_stacks[i].Pop()}[/]"); for (int i = 1; i <= 9; i++)
{
AnsiConsole.Markup($"[{(i % 2 == 0 ? "yellow" : "green")}]{_stacks[i].Pop()}[/]");
}
AnsiConsole.WriteLine();
} }
AnsiConsole.WriteLine();
} }
#region Input #region Input

View File

@@ -6,7 +6,7 @@ public class Day6 : Day
{ {
public override int Number => 6; public override int Number => 6;
public override string Name => "Tuning Trouble"; public override string Name => "Tuning Trouble";
public override void RunPart1() public override void RunPart1(bool display = true)
{ {
int position = 0; int position = 0;
@@ -30,10 +30,13 @@ public class Day6 : Day
charStream.Dequeue(); charStream.Dequeue();
} }
AnsiConsole.MarkupLine($"[green]Position of start-of-packet marker is: [yellow]{position}[/][/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]Position of start-of-packet marker is: [yellow]{position}[/][/]");
}
} }
public override void RunPart2() public override void RunPart2(bool display = true)
{ {
const int length = 14; const int length = 14;
int position = 0; int position = 0;
@@ -48,7 +51,7 @@ public class Day6 : Day
for (int i = length - 1; i < Input.Length; i++) for (int i = length - 1; i < Input.Length; i++)
{ {
charStream.Enqueue(Input[i]); charStream.Enqueue(Input[i]);
// Check if all 14 chars are different // Check if all 14 chars are different
if (charStream.ToHashSet().Count == charStream.Count) if (charStream.ToHashSet().Count == charStream.Count)
{ {
@@ -59,7 +62,10 @@ public class Day6 : Day
charStream.Dequeue(); charStream.Dequeue();
} }
AnsiConsole.MarkupLine($"[green]Position of start-of-message marker is: [yellow]{position}[/][/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]Position of start-of-message marker is: [yellow]{position}[/][/]");
}
} }
#region Input #region Input

View File

@@ -32,7 +32,7 @@ public class Day7 : Day
{ {
public override int Number => 7; public override int Number => 7;
public override string Name => "No Space Left On Device"; public override string Name => "No Space Left On Device";
public override void RunPart1() public override void RunPart1(bool display = true)
{ {
var fileSystem = ParseFileSystem(); var fileSystem = ParseFileSystem();
@@ -42,10 +42,13 @@ public class Day7 : Day
.Where(d => d.Size < 100_000) .Where(d => d.Size < 100_000)
.Sum(d => d.Size); .Sum(d => d.Size);
AnsiConsole.MarkupLine($"[green]Total size: [yellow]{totalSize}[/][/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]Total size: [yellow]{totalSize}[/][/]");
}
} }
public override void RunPart2() public override void RunPart2(bool display = true)
{ {
const long fileSystemSpace = 70_000_000; const long fileSystemSpace = 70_000_000;
const long requiredSpace = 30_000_000; const long requiredSpace = 30_000_000;
@@ -63,7 +66,10 @@ public class Day7 : Day
.Where(d => d.Size > missingSpace) .Where(d => d.Size > missingSpace)
.MinBy(d => d.Size); .MinBy(d => d.Size);
AnsiConsole.MarkupLine($"[green]Directory to remove size: [yellow]{directoryToRemove!.Size}[/][/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]Directory to remove size: [yellow]{directoryToRemove!.Size}[/][/]");
}
} }
private static DirectoryEntry ParseFileSystem() private static DirectoryEntry ParseFileSystem()

View File

@@ -6,7 +6,7 @@ public class Day8 : Day
{ {
public override int Number => 8; public override int Number => 8;
public override string Name => "Treetop Tree House"; public override string Name => "Treetop Tree House";
public override void RunPart1() public override void RunPart1(bool display = true)
{ {
var grid = CreateGrid(); var grid = CreateGrid();
@@ -92,10 +92,14 @@ public class Day8 : Day
} }
} }
AnsiConsole.MarkupLine($"[green]Total viewable trees: [yellow]{valid.Count}[/][/]");
if (display)
{
AnsiConsole.MarkupLine($"[green]Total viewable trees: [yellow]{valid.Count}[/][/]");
}
} }
public override void RunPart2() public override void RunPart2(bool display = true)
{ {
var grid = CreateGrid(); var grid = CreateGrid();
@@ -116,7 +120,10 @@ public class Day8 : Day
} }
} }
AnsiConsole.MarkupLine($"[green]Max scenic score: [yellow]{maxScenicScore}[/][/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]Max scenic score: [yellow]{maxScenicScore}[/][/]");
}
} }
private int ComputeScenicScore(int[,] grid, int i, int j, int size) private int ComputeScenicScore(int[,] grid, int i, int j, int size)

View File

@@ -7,7 +7,7 @@ public class Day9 : Day
public override int Number => 9; public override int Number => 9;
public override string Name => "Rope Bridge"; public override string Name => "Rope Bridge";
public override void RunPart1() public override void RunPart1(bool display = true)
{ {
// Start // Start
(int x, int y) start = (0, 0); (int x, int y) start = (0, 0);
@@ -49,10 +49,13 @@ public class Day9 : Day
} }
} }
AnsiConsole.MarkupLine($"[green]The tail visited [yellow]{tailVisitedPositions.Count}[/] unique positions[/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]The tail visited [yellow]{tailVisitedPositions.Count}[/] unique positions[/]");
}
} }
public override void RunPart2() public override void RunPart2(bool display = true)
{ {
const int tailSegments = 9; const int tailSegments = 9;
@@ -130,7 +133,10 @@ public class Day9 : Day
} }
} }
AnsiConsole.MarkupLine($"[green]The tail visited [yellow]{tailVisitedPositions.Count}[/] unique positions[/]"); if (display)
{
AnsiConsole.MarkupLine($"[green]The tail visited [yellow]{tailVisitedPositions.Count}[/] unique positions[/]");
}
} }
#region Input #region Input

View File

@@ -1,8 +1,18 @@
 using System.Diagnostics;
using System.Reflection; using System.Reflection;
using AdventOfCode;
using AdventOfCode.Days; using AdventOfCode.Days;
using BenchmarkDotNet.Running;
using Spectre.Console; using Spectre.Console;
// Benchmark
if (args is ["--bench" or "-b"])
{
BenchmarkRunner.Run<DayBenchmark>();
Environment.Exit(0);
}
// Normal run
var days = Assembly.GetAssembly(typeof(Day))!.GetTypes() var days = Assembly.GetAssembly(typeof(Day))!.GetTypes()
.Where(t => t.IsAssignableTo(typeof(Day)) && t.GetConstructor(Type.EmptyTypes) != null && !t.IsAbstract) .Where(t => t.IsAssignableTo(typeof(Day)) && t.GetConstructor(Type.EmptyTypes) != null && !t.IsAbstract)
.Select(t => (Day)Activator.CreateInstance(t)!); .Select(t => (Day)Activator.CreateInstance(t)!);
@@ -13,23 +23,35 @@ var select = new SelectionPrompt<Day>()
var selectedDay = AnsiConsole.Prompt(select); var selectedDay = AnsiConsole.Prompt(select);
var stopWatch = new Stopwatch();
AnsiConsole.MarkupLine($"[cyan]Running [yellow]{selectedDay}[/]...[/]\n"); AnsiConsole.MarkupLine($"[cyan]Running [yellow]{selectedDay}[/]...[/]\n");
AnsiConsole.MarkupLine("[cyan]Part [yellow]1[/] result:[/]"); AnsiConsole.MarkupLine("[cyan]Part [yellow]1[/] result:[/]");
try try
{ {
stopWatch.Start();
selectedDay.RunPart1(); selectedDay.RunPart1();
stopWatch.Stop();
AnsiConsole.MarkupLine($"[red]Approximate run time: [yellow]{stopWatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000:F3} ms[/][/]");
} }
catch (Exception e) catch (Exception e)
{ {
AnsiConsole.WriteException(e); AnsiConsole.WriteException(e);
} }
stopWatch.Reset();
AnsiConsole.MarkupLine("\n[cyan]Part [yellow]2[/] result:[/]"); AnsiConsole.MarkupLine("\n[cyan]Part [yellow]2[/] result:[/]");
try try
{ {
stopWatch.Start();
selectedDay.RunPart2(); selectedDay.RunPart2();
stopWatch.Stop();
AnsiConsole.MarkupLine($"[red]Approximate run time: [yellow]{stopWatch.ElapsedTicks / (double)Stopwatch.Frequency * 1000:F3} ms[/][/]");
} }
catch (Exception e) catch (Exception e)
{ {