-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
d1552e5
commit b3a619f
Showing
12 changed files
with
317 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
using Advent.Common.Settings; | ||
using Spectre.Console.Cli; | ||
|
||
namespace Advent.Tests.Commands; | ||
|
||
public class Day21CommandTests | ||
{ | ||
private readonly List<string> _arguments = []; | ||
private readonly IRemainingArguments _remaining = Substitute.For<IRemainingArguments>(); | ||
private readonly TestConsole console = new(); | ||
|
||
public Day21CommandTests() | ||
{ | ||
console.Profile.Capabilities.Interactive = true; | ||
} | ||
|
||
[Fact] | ||
public async Task Day21Command_Solves_Part1_Correctly() | ||
{ | ||
var mockReader = Substitute.For<IFileReader>(); | ||
mockReader | ||
.ReadInputAsync(Arg.Any<string>()) | ||
.Returns(Task.FromResult(TestData.Day21TestData)); | ||
|
||
var command = new Day21Command(mockReader, console); | ||
var result = await command.ExecuteAsync( | ||
new CommandContext(_arguments, _remaining, "day21", null), | ||
new AdventSettings { Part = "Part 1" } | ||
); | ||
result.Should().Be(0); | ||
console.Output.Should().Contain("Day 21 Part 1"); | ||
console.Output.Should().Contain("The answer is 126384"); | ||
} | ||
|
||
[Fact] | ||
public async Task Day21Command_Solves_Part2_Correctly() | ||
{ | ||
var mockReader = Substitute.For<IFileReader>(); | ||
mockReader | ||
.ReadInputAsync(Arg.Any<string>()) | ||
.Returns(Task.FromResult(TestData.Day21TestData)); | ||
|
||
var command = new Day21Command(mockReader, console); | ||
var result = await command.ExecuteAsync( | ||
new CommandContext(_arguments, _remaining, "day21", null), | ||
new AdventSettings { Part = "Part 2" } | ||
); | ||
result.Should().Be(0); | ||
console.Output.Should().Contain("Day 21 Part 2"); | ||
console.Output.Should().Contain("The answer is 154115708116294"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
using Advent.UseCases.Day21; | ||
|
||
namespace Advent.Tests.UseCases.Day21; | ||
|
||
public class Day21ParserTests | ||
{ | ||
[Fact] | ||
public void Parse_Returns_Correct_Data() | ||
{ | ||
var input = TestData.Day21TestData; | ||
var result = Day21Parser.Parse(input); | ||
result.Should().HaveCount(5); | ||
result.Should().Contain("029A"); | ||
result.Should().Contain("980A"); | ||
result.Should().Contain("179A"); | ||
result.Should().Contain("379A"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
using Advent.UseCases.Day21; | ||
|
||
namespace Advent.Tests.UseCases.Day21; | ||
|
||
public class Day21Part1SolverTests | ||
{ | ||
[Fact] | ||
public void Solve_ShouldReturnCorrectCost_ForDepth2() | ||
{ | ||
// Arrange | ||
|
||
var solver = new Day21Part1Solver(2); | ||
|
||
// Act | ||
var solution = solver.Solve(["029A", "980A", "179A", "456A", "379A"]); | ||
|
||
// Assert | ||
solution.Should().Be("126384"); | ||
} | ||
|
||
[Fact] | ||
public void Solve_ShouldReturnCorrectCost_ForDepth25() | ||
{ | ||
// Arrange | ||
|
||
var solver = new Day21Part1Solver(25); | ||
|
||
// Act | ||
var solution = solver.Solve(["029A", "980A", "179A", "456A", "379A"]); | ||
|
||
// Assert | ||
solution.Should().Be("154115708116294"); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
using Advent.Common; | ||
using Advent.Common.Commands; | ||
using Advent.Common.Settings; | ||
using Advent.UseCases.Day21; | ||
using Spectre.Console; | ||
using Spectre.Console.Cli; | ||
|
||
namespace Advent.Commands; | ||
|
||
public class Day21Command(IFileReader reader, IAnsiConsole console) | ||
: AdventCommand<AdventSettings>(reader, console) | ||
{ | ||
public override async Task<int> ExecuteAsync(CommandContext context, AdventSettings settings) | ||
{ | ||
var input = await _reader.ReadInputAsync("../input/day21input.txt"); | ||
var data = Day21Parser.Parse(input); | ||
|
||
var choice = settings.Part ?? PromptForPartChoice(); | ||
IDay21Solver solver = choice switch | ||
{ | ||
"Part 1" => new Day21Part1Solver(2), | ||
"Part 2" => new Day21Part1Solver(25), | ||
_ => throw new InvalidOperationException("Invalid choice") | ||
}; | ||
|
||
var result = solver.Solve(data); | ||
_console.MarkupLine($"[bold green]Day 21 {choice} [/]"); | ||
_console.MarkupLine($"The answer is [bold yellow]{result}[/]"); | ||
return 0; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
namespace Advent.UseCases.Day21; | ||
|
||
public static class Day21Parser | ||
{ | ||
public static string[] Parse(string input) | ||
{ | ||
return input.Split("\n", StringSplitOptions.RemoveEmptyEntries); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
using System.Diagnostics; | ||
using Advent.Common; | ||
using Spectre.Console; | ||
using Cache = System.Collections.Generic.Dictionary< | ||
(char currentKey, char nextKey, int depth), | ||
long | ||
>; | ||
using Keypad = System.Collections.Generic.Dictionary<Advent.Common.GridCell, char>; | ||
|
||
namespace Advent.UseCases.Day21; | ||
|
||
public class Day21Part1Solver(int depth) : IDay21Solver | ||
{ | ||
private readonly int _depth = depth; | ||
|
||
public string Solve(string[] input) | ||
{ | ||
Stopwatch sw = new(); | ||
sw.Start(); | ||
var numPad = CreateKeypads(["789", "456", "123", " 0A"]); | ||
var dirPad = CreateKeypads([" ^A", "<v>"]); | ||
var keypads = Enumerable.Repeat(dirPad, _depth).Prepend(numPad).ToArray(); | ||
var cache = new Cache(); | ||
long result = 0L; | ||
|
||
foreach (var line in input) | ||
{ | ||
var mutiplier = int.Parse(line[..^1]); | ||
var cost = ProcessKeys(line, keypads, cache); | ||
result += mutiplier * cost; | ||
} | ||
sw.Stop(); | ||
AnsiConsole.WriteLine($"Elapsed time: {sw.Elapsed.TotalMilliseconds} ms"); | ||
return result.ToString(); | ||
} | ||
|
||
private long ProcessKeys(string line, Keypad[] keypads, Cache cache) | ||
{ | ||
if (keypads.Length == 0) | ||
{ | ||
return line.Length; | ||
} | ||
else | ||
{ | ||
var currentKey = 'A'; | ||
var cost = 0L; | ||
|
||
foreach (var key in line) | ||
{ | ||
cost += GetCost(currentKey, key, keypads, cache); | ||
currentKey = key; | ||
} | ||
return cost; | ||
} | ||
} | ||
|
||
private long GetCost(char currentKey, char nextKey, Keypad[] keypads, Cache cache) | ||
{ | ||
if (cache.TryGetValue((currentKey, nextKey, keypads.Length), out var cacheCost)) | ||
{ | ||
return cacheCost; | ||
} | ||
var keypad = keypads[0]; | ||
|
||
var currentKeyPosition = keypad.Single(kvp => kvp.Value == currentKey).Key; | ||
var nextKeyPosition = keypad.Single(kvp => kvp.Value == nextKey).Key; | ||
|
||
var dRow = nextKeyPosition.Row - currentKeyPosition.Row; | ||
var vert = new string(dRow < 0 ? 'v' : '^', Math.Abs(dRow)); | ||
var dCol = nextKeyPosition.Column - currentKeyPosition.Column; | ||
var horiz = new string(dCol < 0 ? '<' : '>', Math.Abs(dCol)); | ||
|
||
var cost = long.MaxValue; | ||
|
||
if (keypad[new GridCell(currentKeyPosition.Row, nextKeyPosition.Column)] != ' ') | ||
{ | ||
cost = Math.Min(cost, ProcessKeys($"{horiz}{vert}A", keypads[1..], cache)); | ||
} | ||
if (keypad[new GridCell(nextKeyPosition.Row, currentKeyPosition.Column)] != ' ') | ||
{ | ||
cost = Math.Min(cost, ProcessKeys($"{vert}{horiz}A", keypads[1..], cache)); | ||
} | ||
cache[(currentKey, nextKey, keypads.Length)] = cost; | ||
return cost; | ||
} | ||
|
||
private static Keypad CreateKeypads(string[] keys) | ||
{ | ||
List<KeyValuePair<GridCell, char>> keyValues = []; | ||
foreach (var row in Enumerable.Range(0, keys.Length)) | ||
{ | ||
foreach (var col in Enumerable.Range(0, keys[0].Length)) | ||
{ | ||
keyValues.Add( | ||
new KeyValuePair<GridCell, char>(new GridCell(-row, col), keys[row][col]) | ||
); | ||
} | ||
} | ||
return keyValues.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
namespace Advent.UseCases.Day21; | ||
|
||
public interface IDay21Solver | ||
{ | ||
public string Solve(string[] input); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
using Advent.Common; | ||
|
||
namespace Advent.UseCases.Day21; | ||
|
||
public interface IRobotControl | ||
{ | ||
public char Move(Direction direction); | ||
public char CurrentKey { get; } | ||
public List<Direction> AvailableMoves(GridCell newPosition); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
540A | ||
582A | ||
169A | ||
593A | ||
579A |