Skip to content

Commit

Permalink
Updating some method names, adding manhattan distance.
Browse files Browse the repository at this point in the history
Day 20 solutions.
  • Loading branch information
byte2pixel committed Dec 23, 2024
1 parent d3dadd6 commit d1552e5
Show file tree
Hide file tree
Showing 13 changed files with 390 additions and 14 deletions.
52 changes: 52 additions & 0 deletions 2024/Advent.Tests/Commands/Day20CommandTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
using Advent.UseCases.Day20;
using Spectre.Console.Cli;

namespace Advent.Tests.Commands;

public class Day20CommandTests
{
private readonly List<string> _arguments = [];
private readonly IRemainingArguments _remaining = Substitute.For<IRemainingArguments>();
private readonly TestConsole console = new();

public Day20CommandTests()
{
console.Profile.Capabilities.Interactive = true;
}

[Fact]
public async Task Day20Command_Solves_Part1_Correctly()
{
var mockReader = Substitute.For<IFileReader>();
mockReader
.ReadInputAsync(Arg.Any<string>())
.Returns(Task.FromResult(TestData.Day20TestData));

var command = new Day20Command(mockReader, console);
var result = await command.ExecuteAsync(
new CommandContext(_arguments, _remaining, "day20", null),
new Day20Settings { Part = "Part 1", Threshold = 0 }
);
result.Should().Be(0);
console.Output.Should().Contain("Day 20 Part 1");
console.Output.Should().Contain("The answer is 44");
}

[Fact]
public async Task Day20Command_Solves_Part2_Correctly()
{
var mockReader = Substitute.For<IFileReader>();
mockReader
.ReadInputAsync(Arg.Any<string>())
.Returns(Task.FromResult(TestData.Day20TestData));

var command = new Day20Command(mockReader, console);
var result = await command.ExecuteAsync(
new CommandContext(_arguments, _remaining, "day20", null),
new Day20Settings { Part = "Part 2", Threshold = 50 }
);
result.Should().Be(0);
console.Output.Should().Contain("Day 20 Part 2");
console.Output.Should().Contain("The answer is 285");
}
}
18 changes: 18 additions & 0 deletions 2024/Advent.Tests/Common/TestData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -310,4 +310,22 @@ internal static class TestData
+ "bwurrg\n"
+ "brgr\n"
+ "bbrgwb\n\n";

// csharpier-ignore
public const string Day20TestData =
"###############\n"
+ "#...#...#.....#\n"
+ "#.#.#.#.#.###.#\n"
+ "#S#...#.#.#...#\n"
+ "#######.#.#.###\n"
+ "#######.#.#...#\n"
+ "#######.#.###.#\n"
+ "###..E#...#...#\n"
+ "###.#######.###\n"
+ "#...###...#...#\n"
+ "#.#####.#.###.#\n"
+ "#.#...#.#.#...#\n"
+ "#.#.#.#.#.#.###\n"
+ "#...#...#...###\n"
+ "###############\n";
}
48 changes: 48 additions & 0 deletions 2024/Advent.Tests/IntegrationTests/CommandAppTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,54 @@ public async Task Day19Part2_IntegrationTest_Success()
result.Output.Should().Contain("The answer is 624802218898092");
}
#endregion

#region Day20
[Fact]
public async Task Day20Part1_IntegrationTest_Success()
{
// Arrange
var args = new string[] { "day20", "--part", "Part 1" };
var app = new CommandAppTester(_registrar);

app.Configure(config =>
{
config.PropagateExceptions();
config.ConfigureConsole(_console);
config.AddCommand<Day20Command>("day20");
});

// Act
var result = await app.RunAsync(args);

// Assert
result.ExitCode.Should().Be(0);
result.Output.Should().Contain("Day 20 Part 1");
result.Output.Should().Contain("The answer is 1395");
}

[Fact]
public async Task Day20Part2_IntegrationTest_Success()
{
// Arrange
var args = new string[] { "day20", "--part", "Part 2" };
var app = new CommandAppTester(_registrar);

app.Configure(config =>
{
config.PropagateExceptions();
config.ConfigureConsole(_console);
config.AddCommand<Day20Command>("day20");
});

// Act
var result = await app.RunAsync(args);

// Assert
result.ExitCode.Should().Be(0);
result.Output.Should().Contain("Day 20 Part 2");
result.Output.Should().Contain("The answer is 993178");
}
#endregion
}

public class TestFixture
Expand Down
33 changes: 33 additions & 0 deletions 2024/Advent.Tests/UseCases/Day20/Day20Part2SolverTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Advent.UseCases.Day20;
using Advent.UseCases.Day6;

namespace Advent.Tests.UseCases.Day20;

public class Day20Part2SolverTests
{
[Fact]
public void Solve_ShouldReturnShortestPathLength_Part1Test()
{
// Arrange
var input = Day6Parser.Parse(TestData.Day20TestData);

// Act
var result = new Day20Solver(0, 2).Solve(input);

// Assert
result.Should().Be(44);
}

[Fact]
public void Solve_ShouldReturnShortestPathLength_Part2Test()
{
// Arrange
var input = Day6Parser.Parse(TestData.Day20TestData);

// Act
var result = new Day20Solver(50, 20).Solve(input);

// Assert
result.Should().Be(285);
}
}
31 changes: 31 additions & 0 deletions 2024/Advent/Commands/Day20Command.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using Advent.Common;
using Advent.Common.Commands;
using Advent.UseCases.Day20;
using Advent.UseCases.Day6;
using Spectre.Console;
using Spectre.Console.Cli;

namespace Advent.Commands;

public class Day20Command(IFileReader reader, IAnsiConsole console)
: AdventCommand<Day20Settings>(reader, console)
{
public override async Task<int> ExecuteAsync(CommandContext context, Day20Settings settings)
{
var input = await _reader.ReadInputAsync("../input/day20input.txt");
var data = Day6Parser.Parse(input);

var choice = settings.Part ?? PromptForPartChoice();
IDay6Solver solver = choice switch
{
"Part 1" => new Day20Solver(settings.Threshold, 2),
"Part 2" => new Day20Solver(settings.Threshold, 20),
_ => throw new InvalidOperationException("Invalid choice")
};

var result = solver.Solve(data);
_console.MarkupLine($"[bold green]Day 20 {choice} [/]");
_console.MarkupLine($"The answer is [bold yellow]{result}[/]");
return 0;
}
}
3 changes: 3 additions & 0 deletions 2024/Advent/Common/GridCell.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ public Direction GetDirection(GridCell other)
};
}

public int ManhattanDistance(GridCell other) =>
Math.Abs(Row - other.Row) + Math.Abs(Column - other.Column);

public bool Equals(GridCell other)
{
return Row == other.Row && Column == other.Column;
Expand Down
59 changes: 54 additions & 5 deletions 2024/Advent/Common/GridData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@ public readonly bool IsValid(GridCell cell) =>

public readonly bool IsValid(GridCell cell, Direction direction) => IsValid(cell.Go(direction));

/// <summary>
/// Find the first cell that contains the specified character.
/// </summary>
/// <param name="c"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
public readonly GridCell Find(char c)
{
for (int i = 0; i < Rows; i++)
Expand All @@ -74,6 +80,11 @@ public readonly GridCell Find(char c)
throw new InvalidOperationException($"No {c} position found");
}

/// <summary>
/// Find all cells that contain the specified character.
/// </summary>
/// <param name="c">The character to search for</param>
/// <returns>All cells that contain the character</returns>
internal IEnumerable<GridCell> FindAll(char c)
{
var results = new List<GridCell>();
Expand All @@ -90,11 +101,39 @@ internal IEnumerable<GridCell> FindAll(char c)
return results;
}

/// <summary>
/// Find all cells that are within the specified distance from the start cell.<br/>
/// Optionally, the cells can contain the specified character.
/// </summary>
/// <param name="start">The starting cell</param>
/// <param name="distance">The maximum distance</param>
/// <param name="c">Optional character(s) to search for</param>
/// <returns>All cells that match the criteria</returns>
internal IEnumerable<GridCell> FindAll(GridCell start, int distance, char[]? c = null)
{
var results = new List<GridCell>();
for (int row = 0; row < Rows; row++)
{
for (int column = 0; column < Columns; column++)
{
var end = new GridCell(row, column);
if (
start.ManhattanDistance(end) <= distance
&& (c == null || c.Contains(this[end]))
)
{
results.Add(end);
}
}
}
return results;
}

/// <summary>
/// Returns the adjacent cells that are within the grid.
/// </summary>
/// <param name="cell"></param>
/// <returns></returns>
/// <returns>All adjacent cells</returns>
internal IEnumerable<GridCell> Adjacent(GridCell cell)
{
var results = new List<GridCell>();
Expand Down Expand Up @@ -130,9 +169,19 @@ internal IEnumerable<GridCell> Adjacent(GridCell cell, char c)
return results;
}

internal IEnumerable<GridCell> FromTo(GridCell start, Direction direction, int? count = null)
/// <summary>
/// Returns the cells from the start cell in the specified direction.
/// Optionally, the distance can be specified or it will go to the edge of the grid.
/// Stops if it encounters a wall '#'.
/// </summary>
/// <param name="start"></param>
/// <param name="direction"></param>
/// <param name="distance"></param>
/// <returns></returns>
/// <exception cref="InvalidOperationException"></exception>
internal IEnumerable<GridCell> FromTo(GridCell start, Direction direction, int? distance = null)
{
count ??= direction switch
distance ??= direction switch
{
Direction.North => start.Row,
Direction.South => Rows - start.Row,
Expand All @@ -142,7 +191,7 @@ internal IEnumerable<GridCell> FromTo(GridCell start, Direction direction, int?
};
var results = new List<GridCell>();
var current = start.Go(direction); // skip the start cell
for (int i = 0; i < count; i++)
for (int i = 0; i < distance; i++)
{
if (!IsValid(current))
{
Expand All @@ -159,7 +208,7 @@ internal IEnumerable<GridCell> FromTo(GridCell start, Direction direction, int?
}

/// <summary>
/// Returns the all cells that are adjacent to the cell.
/// Returns the all cells that are adjacent to the cell, including those that are outside the grid.
/// </summary>
/// <param name="cell"></param>
/// <returns></returns>
Expand Down
1 change: 1 addition & 0 deletions 2024/Advent/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
config.AddCommand<Day17Command>("day17").WithDescription("Advent of Code 2024 Day 17");
config.AddCommand<Day18Command>("day18").WithDescription("Advent of Code 2024 Day 18");
config.AddCommand<Day19Command>("day19").WithDescription("Advent of Code 2024 Day 19");
config.AddCommand<Day20Command>("day20").WithDescription("Advent of Code 2024 Day 20");
});

return await app.RunAsync(args);
40 changes: 33 additions & 7 deletions 2024/Advent/UseCases/Day18/Day18Helper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,40 +12,66 @@ private sealed class QueueState

/// <summary>
/// Find the path from the top left to the bottom right of the grid
/// or from the start to the end cell.
/// avoiding the '#' characters. The path must be the shortest path
/// </summary>
/// <param name="grid"></param>
/// <param name="bytesToFall"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
internal static int FindPathFromTopLeftToBottomRight(GridData grid)
internal static List<GridCell> FindPathFromTo(
GridData grid,
GridCell? start = null,
GridCell? end = null
)
{
GridCell nStart = start ?? new GridCell(0, 0);
GridCell nEnd = end ?? new GridCell(grid.Rows - 1, grid.Columns - 1);

if (nStart == nEnd)
{
return [nStart];
}

return FindShortestPath(grid, nStart, nEnd);
}

private static List<GridCell> FindShortestPath(GridData grid, GridCell nStart, GridCell nEnd)
{
var start = new GridCell(0, 0);
var end = new GridCell(grid.Rows - 1, grid.Columns - 1);
var queue = new PriorityQueue<QueueState, int>();
var visited = new HashSet<GridCell>();
var pathMap = new Dictionary<GridCell, GridCell>();

queue.Enqueue(new QueueState { Cell = start, Distance = 0 }, 0);
queue.Enqueue(new QueueState { Cell = nStart, Distance = 0 }, 0);

while (queue.Count > 0)
{
var q = queue.Dequeue();
var current = q.Cell;
var distance = q.Distance;
if (current == end)
if (current == nEnd)
{
return distance;
List<GridCell> path = [current];
var cell = current;
while (cell != nStart)
{
cell = pathMap[cell];
path.Add(cell);
}
path.Reverse();
return path;
}

foreach (var neighbor in grid.Adjacent(current))
{
if (grid[neighbor] == '#')
if (grid[neighbor] == '#' || grid[neighbor] == 'S')
{
continue;
}

if (visited.Add(neighbor))
{
pathMap[neighbor] = current;
queue.Enqueue(
new QueueState { Cell = neighbor, Distance = distance + 1 },
distance + 1
Expand Down
Loading

0 comments on commit d1552e5

Please sign in to comment.