From 630576c065beb54003e6deb1747a536267df24ea Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Sun, 10 Dec 2023 23:40:12 -0600 Subject: [PATCH] feat: solve part 1 --- 10/PipeMaze.Tests/MazeTests.cs | 358 +++++++++++++++++++++++++++++++ 10/PipeMaze.Tests/UnitTest1.cs | 10 - 10/PipeMaze/Program.cs | 374 ++++++++++++++++++++++++++++++++- 3 files changed, 730 insertions(+), 12 deletions(-) create mode 100644 10/PipeMaze.Tests/MazeTests.cs delete mode 100644 10/PipeMaze.Tests/UnitTest1.cs diff --git a/10/PipeMaze.Tests/MazeTests.cs b/10/PipeMaze.Tests/MazeTests.cs new file mode 100644 index 0000000..0172ba7 --- /dev/null +++ b/10/PipeMaze.Tests/MazeTests.cs @@ -0,0 +1,358 @@ +namespace PipeMaze.Tests; + +public class MazeTests +{ + + [Theory] + [MemberData(nameof(TestData.ParseTestData), MemberType = typeof(TestData))] + public void Parse_WhenGivenInput_ItShouldReturnExpectedMaze(string[] input, Maze expectedMaze, Pipe? expectedStartingPipe) + { + var maze = Maze.Parse(input); + maze.Should().BeEquivalentTo(expectedMaze); + maze.StartingPipe.Should().BeEquivalentTo(expectedStartingPipe); + } + + [Theory] + [MemberData(nameof(TestData.IdentifyPipeTypeTestData), MemberType = typeof(TestData))] + public void IdentifyPipeType_WhenGivenInput_ItShouldReturnExpectedPipeType(Maze maze, PipeType expectedPipeType) + { + maze + .IdentifyStartPipeType() + .Should() + .Be(expectedPipeType); + } + + [Theory] + [MemberData(nameof(TestData.GetLoopTestData), MemberType = typeof(TestData))] + public void GetLoop_WhenGivenInput_ItShouldReturnExpectedLoop(Maze maze, List expectedLoop) + { + maze + .GetLoop() + .Should() + .BeEquivalentTo(expectedLoop); + } + + [Theory] + [MemberData(nameof(TestData.FurthestStepFromStartTestData), MemberType = typeof(TestData))] + public void FurthestStepFromStart_WhenGivenInput_ItShouldReturnExpectedFurthestStepFromStart(Maze maze, int expectedFurthestStepFromStart) + { + maze.FarthestStepFromStart + .Should() + .Be(expectedFurthestStepFromStart); + } + + public static class TestData + { + private static readonly string[] TestMazeInput = + { + ".....", + ".S-7.", + ".|.|.", + ".L-J.", + ".....", + }; + + private static readonly Maze TestMaze = + new( + 5, + 5, + [ + new('S', PipeType.Unknown, 1, 1), + new('-', PipeType.LeftAndRight, 1, 2), + new('7', PipeType.DownAndLeft, 1, 3), + new('|', PipeType.UpAndDown, 2, 1), + new('|', PipeType.UpAndDown, 2, 3), + new('L', PipeType.UpAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('J', PipeType.UpAndLeft, 3, 3), + ] + ); + + public static IEnumerable FurthestStepFromStartTestData => + new List + { + new object[] + { + TestMaze, + 4 + }, + new object[] + { + new Maze( + 5, + 5, + [ + new('7', PipeType.DownAndLeft, 0, 0), + new('-', PipeType.UpAndRight, 0, 1), + new('F', PipeType.DownAndRight, 0, 2), + new('7', PipeType.DownAndLeft, 0, 3), + new('-', PipeType.LeftAndRight, 0, 4), + + new('F', PipeType.DownAndRight, 1, 1), + new('J', PipeType.UpAndLeft, 1, 2), + new('|', PipeType.UpAndDown, 1, 3), + new('7', PipeType.DownAndLeft, 1, 4), + + new('S', PipeType.Unknown, 2, 0), + new('J', PipeType.UpAndLeft, 2, 1), + new('L', PipeType.UpAndRight, 2, 2), + new('L', PipeType.UpAndRight, 2, 3), + new('7', PipeType.DownAndLeft, 2, 4), + + new('|', PipeType.UpAndDown, 3, 0), + new('F', PipeType.DownAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('-', PipeType.LeftAndRight, 3, 3), + new('J', PipeType.UpAndLeft, 3, 4), + + new('L', PipeType.UpAndRight, 4, 0), + new('J', PipeType.UpAndLeft, 4, 1), + new('L', PipeType.UpAndRight, 4, 3), + new('J', PipeType.UpAndLeft, 4, 4), + ] + ), + 8, + } + }; + + public static IEnumerable GetLoopTestData => + new List + { + new object[] + { + TestMaze, + new List + { + new('S', PipeType.DownAndRight, 1, 1), + new('-', PipeType.LeftAndRight, 1, 2), + new('7', PipeType.DownAndLeft, 1, 3), + new('|', PipeType.UpAndDown, 2, 1), + new('|', PipeType.UpAndDown, 2, 3), + new('L', PipeType.UpAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('J', PipeType.UpAndLeft, 3, 3), + } + }, + new object[] + { + new Maze( + 5, + 5, + [ + new('-', PipeType.LeftAndRight, 0, 0), + new('L', PipeType.UpAndRight, 0, 1), + new('|', PipeType.UpAndDown, 0, 2), + new('F', PipeType.DownAndRight, 0, 3), + new('7', PipeType.DownAndLeft, 0, 4), + + new('7', PipeType.DownAndLeft, 1, 0), + new('S', PipeType.Unknown, 1, 1), + new('-', PipeType.LeftAndRight, 1, 2), + new('7', PipeType.DownAndLeft, 1, 3), + new('|', PipeType.UpAndDown, 1, 4), + + new('L', PipeType.UpAndRight, 2, 0), + new('|', PipeType.UpAndDown, 2, 1), + new('7', PipeType.DownAndLeft, 2, 2), + new('|', PipeType.UpAndDown, 2, 3), + new('|', PipeType.UpAndDown, 2, 4), + + new('-', PipeType.LeftAndRight, 3, 0), + new('L', PipeType.UpAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('J', PipeType.UpAndLeft, 3, 3), + new('|', PipeType.UpAndDown, 3, 4), + + new('L', PipeType.UpAndRight, 4, 0), + new('|', PipeType.UpAndDown, 4, 1), + new('-', PipeType.LeftAndRight, 4, 2), + new('J', PipeType.UpAndLeft, 4, 3), + new('F', PipeType.DownAndRight, 4, 4), + ] + ), + new List + { + new('S', PipeType.DownAndRight, 1, 1), + new('-', PipeType.LeftAndRight, 1, 2), + new('7', PipeType.DownAndLeft, 1, 3), + new('|', PipeType.UpAndDown, 2, 1), + new('|', PipeType.UpAndDown, 2, 3), + new('L', PipeType.UpAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('J', PipeType.UpAndLeft, 3, 3), + } + }, + new object[] + { + new Maze( + 5, + 5, + [ + new('F', PipeType.DownAndRight, 0, 2), + new('7', PipeType.DownAndLeft, 0, 3), + + new('F', PipeType.DownAndRight, 1, 1), + new('J', PipeType.UpAndLeft, 1, 2), + new('|', PipeType.UpAndDown, 1, 3), + + new('S', PipeType.Unknown, 2, 0), + new('J', PipeType.UpAndLeft, 2, 1), + new('L', PipeType.UpAndRight, 2, 3), + new('7', PipeType.DownAndLeft, 2, 4), + + new('|', PipeType.UpAndDown, 3, 0), + new('F', PipeType.DownAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('-', PipeType.LeftAndRight, 3, 3), + new('J', PipeType.UpAndLeft, 3, 4), + + new('L', PipeType.UpAndRight, 4, 0), + new('J', PipeType.UpAndLeft, 4, 1), + ] + ), + new List + { + new('F', PipeType.DownAndRight, 0, 2), + new('7', PipeType.DownAndLeft, 0, 3), + + new('F', PipeType.DownAndRight, 1, 1), + new('J', PipeType.UpAndLeft, 1, 2), + new('|', PipeType.UpAndDown, 1, 3), + + new('S', PipeType.DownAndRight, 2, 0), + new('J', PipeType.UpAndLeft, 2, 1), + new('L', PipeType.UpAndRight, 2, 3), + new('7', PipeType.DownAndLeft, 2, 4), + + new('|', PipeType.UpAndDown, 3, 0), + new('F', PipeType.DownAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('-', PipeType.LeftAndRight, 3, 3), + new('J', PipeType.UpAndLeft, 3, 4), + + new('L', PipeType.UpAndRight, 4, 0), + new('J', PipeType.UpAndLeft, 4, 1), + } + }, + new object[] + { + new Maze( + 5, + 5, + [ + new('7', PipeType.DownAndLeft, 0, 0), + new('-', PipeType.UpAndRight, 0, 1), + new('F', PipeType.DownAndRight, 0, 2), + new('7', PipeType.DownAndLeft, 0, 3), + new('-', PipeType.LeftAndRight, 0, 4), + + new('F', PipeType.DownAndRight, 1, 1), + new('J', PipeType.UpAndLeft, 1, 2), + new('|', PipeType.UpAndDown, 1, 3), + new('7', PipeType.DownAndLeft, 1, 4), + + new('S', PipeType.Unknown, 2, 0), + new('J', PipeType.UpAndLeft, 2, 1), + new('L', PipeType.UpAndRight, 2, 2), + new('L', PipeType.UpAndRight, 2, 3), + new('7', PipeType.DownAndLeft, 2, 4), + + new('|', PipeType.UpAndDown, 3, 0), + new('F', PipeType.DownAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('-', PipeType.LeftAndRight, 3, 3), + new('J', PipeType.UpAndLeft, 3, 4), + + new('L', PipeType.UpAndRight, 4, 0), + new('J', PipeType.UpAndLeft, 4, 1), + new('L', PipeType.UpAndRight, 4, 3), + new('J', PipeType.UpAndLeft, 4, 4), + ] + ), + new List + { + new('F', PipeType.DownAndRight, 0, 2), + new('7', PipeType.DownAndLeft, 0, 3), + + new('F', PipeType.DownAndRight, 1, 1), + new('J', PipeType.UpAndLeft, 1, 2), + new('|', PipeType.UpAndDown, 1, 3), + + new('S', PipeType.DownAndRight, 2, 0), + new('J', PipeType.UpAndLeft, 2, 1), + new('L', PipeType.UpAndRight, 2, 3), + new('7', PipeType.DownAndLeft, 2, 4), + + new('|', PipeType.UpAndDown, 3, 0), + new('F', PipeType.DownAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('-', PipeType.LeftAndRight, 3, 3), + new('J', PipeType.UpAndLeft, 3, 4), + + new('L', PipeType.UpAndRight, 4, 0), + new('J', PipeType.UpAndLeft, 4, 1), + } + } + }; + + public static IEnumerable IdentifyPipeTypeTestData => + new List + { + new object[] + { + TestMaze, + PipeType.DownAndRight, + } + }; + + public static IEnumerable ParseTestData => + new List + { + new object?[] + { + new string[] + { + ".....", + ".F-7.", + ".|.|.", + ".L-J.", + ".....", + }, + new Maze( + 5, + 5, + [ + new('F', PipeType.DownAndRight, 1, 1), + new('-', PipeType.LeftAndRight, 1, 2), + new('7', PipeType.DownAndLeft, 1, 3), + new('|', PipeType.UpAndDown, 2, 1), + new('|', PipeType.UpAndDown, 2, 3), + new('L', PipeType.UpAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('J', PipeType.UpAndLeft, 3, 3), + ] + ), + null + }, + new object?[] + { + TestMazeInput, + new Maze( + 5, + 5, + [ + new('S', PipeType.Unknown, 1, 1), + new('-', PipeType.LeftAndRight, 1, 2), + new('7', PipeType.DownAndLeft, 1, 3), + new('|', PipeType.UpAndDown, 2, 1), + new('|', PipeType.UpAndDown, 2, 3), + new('L', PipeType.UpAndRight, 3, 1), + new('-', PipeType.LeftAndRight, 3, 2), + new('J', PipeType.UpAndLeft, 3, 3), + ] + ), + new Pipe('S', PipeType.Unknown, 1, 1) + } + }; + } +} \ No newline at end of file diff --git a/10/PipeMaze.Tests/UnitTest1.cs b/10/PipeMaze.Tests/UnitTest1.cs deleted file mode 100644 index 2ae55c5..0000000 --- a/10/PipeMaze.Tests/UnitTest1.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace PipeMaze.Tests; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - - } -} \ No newline at end of file diff --git a/10/PipeMaze/Program.cs b/10/PipeMaze/Program.cs index 3751555..ef145e1 100644 --- a/10/PipeMaze/Program.cs +++ b/10/PipeMaze/Program.cs @@ -1,2 +1,372 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +using System.Diagnostics; + +namespace PipeMaze; + +public class Program +{ + public static async Task Main(string[] args) + { + if (args.Length is 0) + { + Console.WriteLine("Please provide a path to the input file."); + return -1; + } + + if (File.Exists(args[0]) is false) + { + Console.WriteLine("The provided file does not exist."); + return -2; + } + + var input = await File.ReadAllLinesAsync(args[0]); + + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + var result = Maze.Parse(input).FarthestStepFromStart; + + stopwatch.Stop(); + + Console.WriteLine($"The farthest step from the starting pipe is {result}. ({stopwatch.ElapsedMilliseconds}ms)"); + + return result; + } +} + +public class Maze( + int width, + int height, + List pipes +) +{ + private static readonly Dictionary _pipes = new() + { + { '|', PipeType.UpAndDown }, + { '-', PipeType.LeftAndRight }, + { 'L', PipeType.UpAndRight }, + { 'J', PipeType.UpAndLeft }, + { '7', PipeType.DownAndLeft }, + { 'F', PipeType.DownAndRight }, + { 'S', PipeType.Unknown }, + }; + + public int Width { get; init; } = width; + public int Height { get; init; } = height; + public List Pipes { get; init; } = pipes; + + public Pipe? StartingPipe => Pipes.FirstOrDefault(p => p.Type is PipeType.Unknown); + + public int FarthestStepFromStart => GetLoop().Count / 2; + + public List GetLoop() + { + if (StartingPipe is null) + { + return []; + } + + var loop = new List(); + var startingPipeType = IdentifyStartPipeType(); + var currentPipe = new Pipe( + StartingPipe.Symbol, + startingPipeType, + StartingPipe.Coordinate.Row, + StartingPipe.Coordinate.Column + ); + + do + { + loop.Add(currentPipe); + + var connectingPipes = GetConnectingPipes(currentPipe); + currentPipe = connectingPipes + .FirstOrDefault( + p => + p is not null && + loop.Any(pipe => pipe.Coordinate.Equals(p.Coordinate)) is false + ); + + } while (currentPipe?.Equals(StartingPipe) is false); + + return loop; + } + + public PipeType IdentifyStartPipeType() + { + if (StartingPipe is null) + { + return PipeType.Unknown; + } + + var connectingPipes = GetConnectingPipes(StartingPipe); + var pipeAbove = connectingPipes.FirstOrDefault(p => p?.Coordinate.Row < StartingPipe.Coordinate.Row); + var pipeBelow = connectingPipes.FirstOrDefault(p => p?.Coordinate.Row > StartingPipe.Coordinate.Row); + var pipeToTheLeft = connectingPipes.FirstOrDefault(p => p?.Coordinate.Column < StartingPipe.Coordinate.Column); + var pipeToTheRight = connectingPipes.FirstOrDefault(p => p?.Coordinate.Column > StartingPipe.Coordinate.Column); + + var isPipeAboveConnected = pipeAbove is not null && + pipeAbove.Type is PipeType.UpAndDown or PipeType.DownAndRight or PipeType.DownAndLeft; + + var isPipeBelowConnected = pipeBelow is not null && + pipeBelow.Type is PipeType.UpAndDown or PipeType.UpAndRight or PipeType.UpAndLeft; + + var isPipeToTheLeftConnected = pipeToTheLeft is not null && + pipeToTheLeft.Type is PipeType.LeftAndRight or PipeType.DownAndRight or PipeType.UpAndRight; + + var isPipeToTheRightConnected = pipeToTheRight is not null && + pipeToTheRight.Type is PipeType.LeftAndRight or PipeType.DownAndLeft or PipeType.UpAndLeft; + + if (isPipeAboveConnected && isPipeBelowConnected && isPipeToTheLeftConnected && isPipeToTheRightConnected) + { + return PipeType.Unknown; + } + + if (isPipeAboveConnected && isPipeBelowConnected) + { + return PipeType.UpAndDown; + } + + if (isPipeToTheLeftConnected && isPipeToTheRightConnected) + { + return PipeType.LeftAndRight; + } + + if (isPipeAboveConnected && isPipeToTheRightConnected) + { + return PipeType.UpAndRight; + } + + if (isPipeAboveConnected && isPipeToTheLeftConnected) + { + return PipeType.UpAndLeft; + } + + if (isPipeBelowConnected && isPipeToTheRightConnected) + { + return PipeType.DownAndRight; + } + + if (isPipeBelowConnected && isPipeToTheLeftConnected) + { + return PipeType.DownAndLeft; + } + + return PipeType.Unknown; + } + + public List GetConnectingPipes(Pipe pipe) + { + var connectingPipes = new List(); + var isNotOnFirstRow = pipe.Coordinate.Row > 0; + var isNotOnFirstColumn = pipe.Coordinate.Column > 0; + var isNotOnLastRow = pipe.Coordinate.Row < Height - 1; + var isNotOnLastColumn = pipe.Coordinate.Column < Width - 1; + + switch (pipe.Type) + { + case PipeType.UpAndDown: + { + if (isNotOnFirstRow) + { + connectingPipes.Add( + Pipes.FirstOrDefault(p => p.Coordinate.Column == pipe.Coordinate.Column && p.Coordinate.Row == pipe.Coordinate.Row - 1) + ); + } + + if (isNotOnLastRow) + { + connectingPipes.Add( + Pipes.FirstOrDefault(p => p.Coordinate.Column == pipe.Coordinate.Column && p.Coordinate.Row == pipe.Coordinate.Row + 1) + ); + } + + break; + } + case PipeType.LeftAndRight: + { + if (isNotOnFirstColumn) + { + connectingPipes.Add( + Pipes.FirstOrDefault(p => p.Coordinate.Column == pipe.Coordinate.Column - 1 && p.Coordinate.Row == pipe.Coordinate.Row) + ); + } + + if (isNotOnLastColumn) + { + connectingPipes.Add( + Pipes.FirstOrDefault(p => p.Coordinate.Column == pipe.Coordinate.Column + 1 && p.Coordinate.Row == pipe.Coordinate.Row) + ); + } + + break; + } + case PipeType.UpAndRight: + { + if (isNotOnFirstRow) + { + connectingPipes.Add( + Pipes.FirstOrDefault(p => p.Coordinate.Column == pipe.Coordinate.Column && p.Coordinate.Row == pipe.Coordinate.Row - 1) + ); + } + + if (isNotOnLastColumn) + { + connectingPipes.Add( + Pipes.FirstOrDefault(p => p.Coordinate.Column == pipe.Coordinate.Column + 1 && p.Coordinate.Row == pipe.Coordinate.Row) + ); + } + + break; + } + case PipeType.UpAndLeft: + { + if (isNotOnFirstRow) + { + connectingPipes.Add( + Pipes.FirstOrDefault(p => p.Coordinate.Column == pipe.Coordinate.Column && p.Coordinate.Row == pipe.Coordinate.Row - 1) + ); + } + + if (isNotOnFirstColumn) + { + connectingPipes.Add( + Pipes.FirstOrDefault(p => p.Coordinate.Column == pipe.Coordinate.Column - 1 && p.Coordinate.Row == pipe.Coordinate.Row) + ); + } + + break; + } + case PipeType.DownAndRight: + { + if (isNotOnLastRow) + { + connectingPipes.Add( + Pipes.FirstOrDefault(p => p.Coordinate.Column == pipe.Coordinate.Column && p.Coordinate.Row == pipe.Coordinate.Row + 1) + ); + } + + if (isNotOnLastColumn) + { + connectingPipes.Add( + Pipes.FirstOrDefault(p => p.Coordinate.Column == pipe.Coordinate.Column + 1 && p.Coordinate.Row == pipe.Coordinate.Row) + ); + } + + break; + } + case PipeType.DownAndLeft: + { + if (isNotOnLastRow) + { + connectingPipes.Add( + Pipes.FirstOrDefault(p => p.Coordinate.Column == pipe.Coordinate.Column && p.Coordinate.Row == pipe.Coordinate.Row + 1) + ); + } + + if (isNotOnFirstColumn) + { + connectingPipes.Add( + Pipes.FirstOrDefault(p => p.Coordinate.Column == pipe.Coordinate.Column - 1 && p.Coordinate.Row == pipe.Coordinate.Row) + ); + } + + break; + } + case PipeType.Unknown: + { + if (isNotOnFirstRow) + { + connectingPipes.Add( + Pipes.FirstOrDefault(p => p.Coordinate.Column == pipe.Coordinate.Column && p.Coordinate.Row == pipe.Coordinate.Row - 1) + ); + } + + if (isNotOnLastColumn) + { + connectingPipes.Add( + Pipes.FirstOrDefault(p => p.Coordinate.Column == pipe.Coordinate.Column + 1 && p.Coordinate.Row == pipe.Coordinate.Row) + ); + } + + if (isNotOnLastRow) + { + connectingPipes.Add( + Pipes.FirstOrDefault(p => p.Coordinate.Column == pipe.Coordinate.Column && p.Coordinate.Row == pipe.Coordinate.Row + 1) + ); + } + + if (isNotOnFirstColumn) + { + connectingPipes.Add( + Pipes.FirstOrDefault(p => p.Coordinate.Column == pipe.Coordinate.Column - 1 && p.Coordinate.Row == pipe.Coordinate.Row) + ); + } + + break; + } + default: + break; + } + + return connectingPipes; + } + + public static Maze Parse(string[] input) + { + var width = input[0].Length; + var height = input.Length; + var pipes = new List(); + + for (var row = 0; row < height; row++) + { + for (var column = 0; column < width; column++) + { + var symbol = input[row][column]; + + if (_pipes.TryGetValue(symbol, out PipeType value)) + { + pipes.Add(new(symbol, value, row, column)); + } + } + } + + return new Maze(width, height, pipes); + } +} + +public class Pipe( + char symbol, + PipeType type, + int row, + int column +) +{ + public char Symbol { get; init; } = symbol; + public PipeType Type { get; init; } = type; + public PipeCoordinate Coordinate { get; init; } = new(column, row); +} + +public class PipeCoordinate( + int column, + int row +) : IEquatable +{ + public int Column { get; init; } = column; + public int Row { get; init; } = row; + + public bool Equals(PipeCoordinate? other) => + other is not null && + Column == other.Column && + Row == other.Row; +} + +public enum PipeType +{ + UpAndDown, // Vertical Pipe + LeftAndRight, // Horizontal Pipe + UpAndRight, // North-East Pipe + UpAndLeft, // North-West Pipe + DownAndRight, // South-East Pipe + DownAndLeft, // South-West Pipe + Unknown, // Starting Pipe +} \ No newline at end of file