feat: solved day 6 part 1 and part 2

This commit is contained in:
Stevan Freeborn
2024-12-07 01:28:15 -06:00
parent 75af0819fa
commit abf19be8cd
7 changed files with 306 additions and 5 deletions
+64
View File
@@ -0,0 +1,64 @@
namespace GuardGallivant.Tests;
public class MapTests
{
private readonly string[] _exampleMap = [
"....#.....",
".........#",
"..........",
"..#.......",
".......#..",
"..........",
".#..^.....",
"........#.",
"#.........",
"......#...",
];
private async Task<string[]> GetPuzzleInput()
{
return await File.ReadAllLinesAsync(Path.Combine(AppContext.BaseDirectory, "INPUT.txt"));
}
[Test]
public async Task PredictDistinctPositionsCount_WhenCalledWithExample_ItShouldReturnExpectedCount()
{
var map = new Map(_exampleMap);
var result = map.PredictDistinctPositionsCount();
await Assert.That(result).IsEqualTo(41);
}
[Test]
public async Task PredictDistinctPositionsCount_WhenCalledWithPuzzleInput_ItShouldReturnExpectedCount()
{
var input = await GetPuzzleInput();
var map = new Map(input);
var result = map.PredictDistinctPositionsCount();
await Assert.That(result).IsEqualTo(4789);
}
[Test]
public async Task IdentifyNumberOfPlacementsForNewObstruction_WhenCalledWithExample_ItShouldReturnExpectedCount()
{
var map = new Map(_exampleMap);
var result = map.IdentifyNumberOfPlacementsForNewObstruction();
await Assert.That(result).IsEqualTo(6);
}
[Test]
public async Task IdentifyNumberOfPlacementsForNewObstruction_WhenCalledWithPuzzleInput_ItShouldReturnExpectedCount()
{
var input = await GetPuzzleInput();
var map = new Map(input);
var result = map.IdentifyNumberOfPlacementsForNewObstruction();
await Assert.That(result).IsEqualTo(1304);
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace GuardGallivant;
record Direction(char Value)
{
public static readonly Direction Up = new('^');
public static readonly Direction Down = new('v');
public static readonly Direction Left = new('<');
public static readonly Direction Right = new('>');
}
+3
View File
@@ -0,0 +1,3 @@
namespace GuardGallivant;
record GuardPosition(int RowIndex, int ColumnIndex, Direction Direction);
+208
View File
@@ -0,0 +1,208 @@
namespace GuardGallivant;
class Map
{
private static readonly Dictionary<Direction, Move> MovementDictionary = new()
{
{ Direction.Up, Move.Up },
{ Direction.Down, Move.Down },
{ Direction.Left, Move.Left },
{ Direction.Right, Move.Right },
};
private static readonly Direction[] Directions = MovementDictionary.Keys.ToArray();
private readonly string[] _input;
public Map(string[] input)
{
_input = input;
}
// TODO: This can be optimized I think.
// Right now I'm basically doing a flood fill...I think
// I'm finding all the places on the map where I can
// place the obstruction, placing, and then seeing if
// a loop is created when guard patrols map.
public int IdentifyNumberOfPlacementsForNewObstruction()
{
var count = 0;
WalkMap((rowIndex, columnIndex) =>
{
var currentCharacter = _input[rowIndex][columnIndex];
if (IsGuard(currentCharacter) || IsBlocked(currentCharacter))
{
return;
}
var mapWithNewObstruction = _input.ToArray();
var row = mapWithNewObstruction[rowIndex].ToCharArray();
row[columnIndex] = '#';
mapWithNewObstruction[rowIndex] = new string(row);
var hasLoop = new Map(mapWithNewObstruction).DetectLoop();
if (hasLoop)
{
count++;
}
});
return count;
}
private bool DetectLoop()
{
var originalGuardPosition = GetGuardPosition();
var currentGuardPosition = originalGuardPosition;
var blockedPositions = new List<GuardPosition>();
while (true)
{
var nextMove = MovementDictionary[currentGuardPosition.Direction];
var nextGuardPosition = currentGuardPosition with
{
RowIndex = currentGuardPosition.RowIndex + nextMove.YOffset,
ColumnIndex = currentGuardPosition.ColumnIndex + nextMove.XOffset,
};
if (IsOffGrid(nextGuardPosition))
{
return false;
}
if (IsBlocked(nextGuardPosition))
{
if (blockedPositions.Contains(nextGuardPosition))
{
return true;
}
blockedPositions.Add(nextGuardPosition);
var newDirection = TurnRight(currentGuardPosition);
currentGuardPosition = currentGuardPosition with { Direction = newDirection };
continue;
}
currentGuardPosition = nextGuardPosition;
}
}
public int PredictDistinctPositionsCount()
{
var currentGuardPosition = GetGuardPosition();
var uniquePositions = new HashSet<(int x, int y)>()
{
(currentGuardPosition.ColumnIndex, currentGuardPosition.RowIndex),
};
while (true)
{
var nextMove = MovementDictionary[currentGuardPosition.Direction];
var nextGuardPosition = currentGuardPosition with
{
RowIndex = currentGuardPosition.RowIndex + nextMove.YOffset,
ColumnIndex = currentGuardPosition.ColumnIndex + nextMove.XOffset,
};
if (IsOffGrid(nextGuardPosition))
{
break;
}
if (IsBlocked(nextGuardPosition))
{
var newDirection = TurnRight(currentGuardPosition);
currentGuardPosition = currentGuardPosition with { Direction = newDirection };
continue;
}
currentGuardPosition = nextGuardPosition;
uniquePositions.Add((currentGuardPosition.ColumnIndex, currentGuardPosition.RowIndex));
}
return uniquePositions.Count;
}
private GuardPosition GetGuardPosition()
{
var guardPosition = new GuardPosition(-1, -1, new Direction('X'));
WalkMap((rowIndex, columnIndex) =>
{
var possibleGuard = new Direction(_input[rowIndex][columnIndex]);
if (Directions.Contains(possibleGuard) is false)
{
return;
}
guardPosition = new GuardPosition(rowIndex, columnIndex, possibleGuard);
});
return guardPosition;
}
private void WalkMap(Action<int, int> callback)
{
for (var rowIndex = 0; rowIndex < _input.Length; rowIndex++)
{
var row = _input[rowIndex];
for (var columnIndex = 0; columnIndex < row.Length; columnIndex++)
{
callback(rowIndex, columnIndex);
}
}
}
private bool IsOffGrid(GuardPosition position)
{
return position.RowIndex < 0 ||
position.RowIndex > _input.Length - 1 ||
position.ColumnIndex < 0 ||
position.ColumnIndex > _input[0].Length - 1;
}
private bool IsBlocked(GuardPosition position)
{
return _input[position.RowIndex][position.ColumnIndex] is '#';
}
private bool IsBlocked(char character)
{
return character is '#';
}
private bool IsGuard(char currentCharacter)
{
return Directions.Contains(new Direction(currentCharacter));
}
private static Direction TurnRight(GuardPosition guardPosition)
{
if (guardPosition.Direction == Direction.Up)
{
return Direction.Right;
}
if (guardPosition.Direction == Direction.Right)
{
return Direction.Down;
}
if (guardPosition.Direction == Direction.Down)
{
return Direction.Left;
}
return Direction.Up;
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace GuardGallivant;
record Move(int XOffset, int YOffset)
{
public static readonly Move Up = new(0, -1);
public static readonly Move Down = new(0, 1);
public static readonly Move Left = new(-1, 0);
public static readonly Move Right = new(1, 0);
}
+12 -5
View File
@@ -1,5 +1,7 @@
using System.Diagnostics;
using GuardGallivant;
if (args.Length is 0)
{
Console.WriteLine("Please provide a path to the input file.");
@@ -13,14 +15,19 @@ if (File.Exists(args[0]) is false)
}
var isPart2 = args.Length is 2 && args[1] is "part2";
var input = await File.ReadAllTextAsync(args[0]);
var input = await File.ReadAllLinesAsync(args[0]);
var stopwatch = new Stopwatch();
stopwatch.Start();
// TODO: Implement solution
var map = new Map(input);
var result = isPart2
? map.IdentifyNumberOfPlacementsForNewObstruction()
: map.PredictDistinctPositionsCount();
var msg = isPart2
? $"There are {result} positions for the new obstruction"
: $"The guard will visit {result} positions";
stopwatch.Stop();
// TODO: Print result
Console.WriteLine($". ({stopwatch.ElapsedMilliseconds}ms)");
Console.WriteLine($"{msg}. ({stopwatch.ElapsedMilliseconds}ms)");
+1
View File
@@ -49,3 +49,4 @@ dotnet build
| 03 | [Problem](./03/PROBLEM.md) | [Solution](./03/MullItOver/) | Thank goodness this wasn't a repeat of day 2. |
| 04 | [Problem](./04/PROBLEM.md) | [Solution](./04/CeresSearch/) | This was fun! Definitely was able to see the growth in my abilities here. |
| 05 | [Problem](./05/PROBLEM.md) | [Solution](./05/PrintQueue/) | A graph to the rescue! |
| 06 | [Problem](./06/PROBLEM.md) | [Solution](./06/GuardGallivant/) | I kind of brute forced part two... |