From 60b5c726cf7ea51b228abbbb1ab1846b0e1c6fd0 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Tue, 3 Dec 2024 02:32:30 -0600 Subject: [PATCH] feat: solved day 2 part 1...part 2 is WIP --- .vscode/launch.json | 16 ++- 02/RedNosedReports.Tests/PuzzleParserTests.cs | 31 ++++++ .../RedNosedReports.Tests.csproj | 19 ++++ 02/RedNosedReports.Tests/ReportTests.cs | 102 ++++++++++++++++++ 02/RedNosedReports/Direction.cs | 8 ++ 02/RedNosedReports/Program.cs | 30 ++++++ 02/RedNosedReports/PuzzleParser.cs | 17 +++ 02/RedNosedReports/RedNosedReports.csproj | 21 ++++ 02/RedNosedReports/Report.cs | 81 ++++++++++++++ AdventOfCode2024.sln | 16 +++ 10 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 02/RedNosedReports.Tests/PuzzleParserTests.cs create mode 100644 02/RedNosedReports.Tests/RedNosedReports.Tests.csproj create mode 100644 02/RedNosedReports.Tests/ReportTests.cs create mode 100644 02/RedNosedReports/Direction.cs create mode 100644 02/RedNosedReports/Program.cs create mode 100644 02/RedNosedReports/PuzzleParser.cs create mode 100644 02/RedNosedReports/RedNosedReports.csproj create mode 100644 02/RedNosedReports/Report.cs diff --git a/.vscode/launch.json b/.vscode/launch.json index cdf3cc2..f5388f2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -2,7 +2,7 @@ "version": "0.2.0", "configurations": [ { - "name": ".NET Core Launch (console)", + "name": "Day 1", "type": "coreclr", "request": "launch", "preLaunchTask": "build", @@ -14,6 +14,20 @@ "console": "integratedTerminal", "stopAtEntry": false }, + { + "name": "Day 2", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + "program": "${workspaceFolder}/02/RedNosedReports/bin/Debug/net9.0/RedNosedReports.dll", + "args": [ + "${input:inputFilePath}", + "part2" + ], + "cwd": "${workspaceFolder}/02/RedNosedReports", + "console": "integratedTerminal", + "stopAtEntry": false + }, { "name": ".NET Core Attach", "type": "coreclr", diff --git a/02/RedNosedReports.Tests/PuzzleParserTests.cs b/02/RedNosedReports.Tests/PuzzleParserTests.cs new file mode 100644 index 0000000..5e89ca7 --- /dev/null +++ b/02/RedNosedReports.Tests/PuzzleParserTests.cs @@ -0,0 +1,31 @@ +namespace RedNosedReports.Tests; + +public class PuzzleParserTests +{ + private readonly PuzzleParser _puzzleParser = new(); + + [Test] + public async Task Parse_WhenGivenInput_ItShouldReturnListOfReports() + { + string[] input = [ + "7 6 4 2 1", + "1 2 7 8 9", + "9 7 6 2 1", + "1 3 2 4 5", + "8 6 4 4 1", + "1 3 6 7 9", + ]; + + var result = _puzzleParser.Parse(input); + + await Assert.That(result).IsEquivalentTo(new List() + { + new([7, 6, 4, 2, 1]), + new([1, 2, 7, 8, 9]), + new([9, 7, 6, 2, 1]), + new([1, 3, 2, 4, 5]), + new([8, 6, 4, 4, 1]), + new([1, 3, 6, 7, 9]), + }); + } +} \ No newline at end of file diff --git a/02/RedNosedReports.Tests/RedNosedReports.Tests.csproj b/02/RedNosedReports.Tests/RedNosedReports.Tests.csproj new file mode 100644 index 0000000..71bcb07 --- /dev/null +++ b/02/RedNosedReports.Tests/RedNosedReports.Tests.csproj @@ -0,0 +1,19 @@ + + + + Exe + net9.0 + enable + enable + CA1822 + + + + + + + + + + + diff --git a/02/RedNosedReports.Tests/ReportTests.cs b/02/RedNosedReports.Tests/ReportTests.cs new file mode 100644 index 0000000..172f0ce --- /dev/null +++ b/02/RedNosedReports.Tests/ReportTests.cs @@ -0,0 +1,102 @@ +namespace RedNosedReports.Tests; + +public class ReportTests +{ + private readonly PuzzleParser _puzzleParser = new(); + + private async Task> GetPuzzleLists() + { + var inputPath = Path.Combine(AppContext.BaseDirectory, "INPUT.txt"); + var input = await File.ReadAllLinesAsync(inputPath); + return _puzzleParser.Parse(input); + } + + [Test] + public async Task IsSafe_WhenGivenPuzzleInput_ItShouldReturnExpectedCount() + { + var reports = await GetPuzzleLists(); + var count = reports.Count(r => r.IsSafe()); + + await Assert.That(count).IsEqualTo(407); + } + + [Test] + public async Task IsSafe_WhenAllLevelsAreNotDecreasing_ItShouldReturnFalse() + { + var report = new Report([7, 6, 9, 2, 1]); + + await Assert.That(report.IsSafe()).IsEqualTo(false); + } + + [Test] + public async Task IsSafe_WhenAllLevelsAreDecreasing_ItShouldReturnTrue() + { + var report = new Report([7, 6, 4, 2, 1]); + + await Assert.That(report.IsSafe()).IsEqualTo(true); + } + + [Test] + public async Task IsSafe_WhenAllLevelsAreNotIncreasing_ItShouldReturnFalse() + { + var report = new Report([1, 3, 6, 4, 9]); + + await Assert.That(report.IsSafe()).IsEqualTo(false); + } + + [Test] + public async Task IsSafe_WhenAllLevelsAreIncreasing_ItShouldReturnTrue() + { + var report = new Report([1, 3, 6, 7, 9]); + + await Assert.That(report.IsSafe()).IsEqualTo(true); + } + + [Test] + public async Task IsSafe_WhenTheChangeIsTooLarge_ItShouldReturnFalse() + { + var report = new Report([1, 2, 7, 8, 9]); + + await Assert.That(report.IsSafe()).IsEqualTo(false); + } + + [Test] + public async Task IsSafe_WhenTheChangeIsTooSmall_ItShouldReturnFalse() + { + var report = new Report([8, 6, 4, 4, 1]); + + await Assert.That(report.IsSafe()).IsEqualTo(false); + } + + [Test] + public async Task IsSafeWithProblemDampener_WhenSafeByRemovingOneLevel_ItShouldReturnTrue() + { + var reportOne = new Report([1, 3, 2, 4, 5]); + var reportTwo = new Report([8, 6, 4, 4, 1]); + var reportThree = new Report([19, 21, 24, 27, 24]); + + await Assert.That(reportOne.IsSafeWithProblemDampener()).IsEqualTo(true); + await Assert.That(reportTwo.IsSafeWithProblemDampener()).IsEqualTo(true); + await Assert.That(reportThree.IsSafeWithProblemDampener()).IsEqualTo(true); + } + + [Test] + public async Task IsSafeWithProblemDampener_WhenSafeByRemovingNoLevels_ItShouldReturnTrue() + { + var reportOne = new Report([7, 6, 4, 2, 1]); + var reportTwo = new Report([1, 3, 6, 7, 9]); + + await Assert.That(reportOne.IsSafeWithProblemDampener()).IsEqualTo(true); + await Assert.That(reportTwo.IsSafeWithProblemDampener()).IsEqualTo(true); + } + + [Test] + public async Task IsSafeWithProblemDampener_WhenUnsafeRegardlessOfLevelsRemoved_ItShouldReturnFalse() + { + var reportOne = new Report([1, 2, 7, 8, 9]); + var reportTwo = new Report([9, 7, 6, 2, 1]); + + await Assert.That(reportOne.IsSafeWithProblemDampener()).IsEqualTo(false); + await Assert.That(reportTwo.IsSafeWithProblemDampener()).IsEqualTo(false); + } +} \ No newline at end of file diff --git a/02/RedNosedReports/Direction.cs b/02/RedNosedReports/Direction.cs new file mode 100644 index 0000000..3ced75a --- /dev/null +++ b/02/RedNosedReports/Direction.cs @@ -0,0 +1,8 @@ +namespace RedNosedReports; + +static class Direction +{ + public const string Unknown = "Unknown"; + public const string Increasing = "Increasing"; + public const string Decreasing = "Decreasing"; +} \ No newline at end of file diff --git a/02/RedNosedReports/Program.cs b/02/RedNosedReports/Program.cs new file mode 100644 index 0000000..97e9c66 --- /dev/null +++ b/02/RedNosedReports/Program.cs @@ -0,0 +1,30 @@ +using System.Diagnostics; + +using RedNosedReports; + +if (args.Length is 0) +{ + Console.WriteLine("Please provide a path to the input file."); + return; +} + +if (File.Exists(args[0]) is false) +{ + Console.WriteLine("The provided file does not exist."); + return; +} + +var isPart2 = args.Length is 2 && args[1] is "part2"; +var input = await File.ReadAllLinesAsync(args[0]); + +var stopwatch = new Stopwatch(); +stopwatch.Start(); + +var reports = new PuzzleParser().Parse(input); + +var numberOfSafeReports = isPart2 + ? reports.Count(r => r.IsSafeWithProblemDampener()) + : reports.Count(r => r.IsSafe()); + +stopwatch.Stop(); +Console.WriteLine($"The total number of safe reports is {numberOfSafeReports}. ({stopwatch.ElapsedMilliseconds}ms)"); \ No newline at end of file diff --git a/02/RedNosedReports/PuzzleParser.cs b/02/RedNosedReports/PuzzleParser.cs new file mode 100644 index 0000000..09f34b8 --- /dev/null +++ b/02/RedNosedReports/PuzzleParser.cs @@ -0,0 +1,17 @@ +namespace RedNosedReports; + +class PuzzleParser +{ + public List Parse(string[] lines) + { + var reports = new List(); + + foreach (var line in lines) + { + var numbers = line.Split(' ').Select(int.Parse).ToList(); + reports.Add(new Report(numbers)); + } + + return reports; + } +} diff --git a/02/RedNosedReports/RedNosedReports.csproj b/02/RedNosedReports/RedNosedReports.csproj new file mode 100644 index 0000000..89d1400 --- /dev/null +++ b/02/RedNosedReports/RedNosedReports.csproj @@ -0,0 +1,21 @@ + + + + Exe + net9.0 + enable + enable + CA1822 + + + + + + + + + PreserveNewest + + + + diff --git a/02/RedNosedReports/Report.cs b/02/RedNosedReports/Report.cs new file mode 100644 index 0000000..b892cf1 --- /dev/null +++ b/02/RedNosedReports/Report.cs @@ -0,0 +1,81 @@ +namespace RedNosedReports; + +class Report(List levels) +{ + private readonly List _levels = levels; + + public bool IsSafe() + { + for (var i = 0; i < _levels.Count - 1; i++) + { + if (CheckIfSafe(_levels[i], _levels[i + 1]) is false) + { + return false; + } + } + + return true; + } + + public bool IsSafeWithProblemDampener() + { + for (var currentIndex = 0; currentIndex < _levels.Count - 1; currentIndex++) + { + var nextIndex = currentIndex + 1; + var current = _levels[currentIndex]; + var next = _levels[nextIndex]; + + if (CheckIfSafe(current, next) is false) + { + var levelsWithoutCurrent = _levels.ToList(); + levelsWithoutCurrent.RemoveAt(currentIndex); + var isSafeWithoutCurrent = new Report(levelsWithoutCurrent).IsSafe(); + + if (isSafeWithoutCurrent) + { + return true; + } + + var levelsWithoutNext = _levels.ToList(); + levelsWithoutNext.RemoveAt(nextIndex); + var isSafeWithoutNext = new Report(levelsWithoutNext).IsSafe(); + + if (isSafeWithoutNext) + { + return true; + } + + return false; + } + } + + return true; + } + + private bool CheckIfSafe(int current, int next) + { + var direction = _levels[1] > _levels[0] + ? Direction.Increasing + : Direction.Decreasing; + + var isNextGreaterThanCurrent = next > current; + var delta = Math.Abs(next - current); + + if (delta > 3 || delta < 1) + { + return false; + } + + if (direction is Direction.Increasing && isNextGreaterThanCurrent is false) + { + return false; + } + + if (direction is Direction.Decreasing && isNextGreaterThanCurrent) + { + return false; + } + + return true; + } +} diff --git a/AdventOfCode2024.sln b/AdventOfCode2024.sln index 80f237c..9fbd111 100644 --- a/AdventOfCode2024.sln +++ b/AdventOfCode2024.sln @@ -9,6 +9,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HistorianHysteria", "01\His EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HistorianHysteria.Tests", "01\HistorianHysteria.Tests\HistorianHysteria.Tests.csproj", "{60DB12DB-3F63-4D9E-BA5A-ACEE0C9E1D05}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "02", "02", "{F73AFAD4-FBCE-4A39-B69E-CDA1B3921902}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedNosedReports", "02\RedNosedReports\RedNosedReports.csproj", "{C2904CE1-B31B-47E4-B1A3-E2EC30731179}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RedNosedReports.Tests", "02\RedNosedReports.Tests\RedNosedReports.Tests.csproj", "{8275EA3C-B88A-4719-866C-A5B49BD6B74E}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -26,9 +32,19 @@ Global {60DB12DB-3F63-4D9E-BA5A-ACEE0C9E1D05}.Debug|Any CPU.Build.0 = Debug|Any CPU {60DB12DB-3F63-4D9E-BA5A-ACEE0C9E1D05}.Release|Any CPU.ActiveCfg = Release|Any CPU {60DB12DB-3F63-4D9E-BA5A-ACEE0C9E1D05}.Release|Any CPU.Build.0 = Release|Any CPU + {C2904CE1-B31B-47E4-B1A3-E2EC30731179}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2904CE1-B31B-47E4-B1A3-E2EC30731179}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2904CE1-B31B-47E4-B1A3-E2EC30731179}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2904CE1-B31B-47E4-B1A3-E2EC30731179}.Release|Any CPU.Build.0 = Release|Any CPU + {8275EA3C-B88A-4719-866C-A5B49BD6B74E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8275EA3C-B88A-4719-866C-A5B49BD6B74E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8275EA3C-B88A-4719-866C-A5B49BD6B74E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8275EA3C-B88A-4719-866C-A5B49BD6B74E}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {1AFE2FF1-A55D-4D86-A9BB-9875CA0D14D8} = {270B7829-6D23-4B34-91FE-D87D533AF2AB} {60DB12DB-3F63-4D9E-BA5A-ACEE0C9E1D05} = {270B7829-6D23-4B34-91FE-D87D533AF2AB} + {C2904CE1-B31B-47E4-B1A3-E2EC30731179} = {F73AFAD4-FBCE-4A39-B69E-CDA1B3921902} + {8275EA3C-B88A-4719-866C-A5B49BD6B74E} = {F73AFAD4-FBCE-4A39-B69E-CDA1B3921902} EndGlobalSection EndGlobal