From 3d3b667f23c6e691e27e17222beb5aa1e9696e56 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Wed, 20 Dec 2023 13:35:37 -0600 Subject: [PATCH 1/4] chore: scaffold for day 14 puzzle --- .../GlobalUsings.cs | 2 ++ .../ParabolicReflectorDish.Tests.csproj | 30 +++++++++++++++++++ 14/ParabolicReflectorDish.Tests/UnitTest1.cs | 10 +++++++ .../ParabolicReflectorDish.csproj | 10 +++++++ 14/ParabolicReflectorDish/Program.cs | 2 ++ AdventOfCode2023.sln | 16 ++++++++++ 6 files changed, 70 insertions(+) create mode 100644 14/ParabolicReflectorDish.Tests/GlobalUsings.cs create mode 100644 14/ParabolicReflectorDish.Tests/ParabolicReflectorDish.Tests.csproj create mode 100644 14/ParabolicReflectorDish.Tests/UnitTest1.cs create mode 100644 14/ParabolicReflectorDish/ParabolicReflectorDish.csproj create mode 100644 14/ParabolicReflectorDish/Program.cs diff --git a/14/ParabolicReflectorDish.Tests/GlobalUsings.cs b/14/ParabolicReflectorDish.Tests/GlobalUsings.cs new file mode 100644 index 0000000..7fef4b0 --- /dev/null +++ b/14/ParabolicReflectorDish.Tests/GlobalUsings.cs @@ -0,0 +1,2 @@ +global using Xunit; +global using FluentAssertions; \ No newline at end of file diff --git a/14/ParabolicReflectorDish.Tests/ParabolicReflectorDish.Tests.csproj b/14/ParabolicReflectorDish.Tests/ParabolicReflectorDish.Tests.csproj new file mode 100644 index 0000000..44829c1 --- /dev/null +++ b/14/ParabolicReflectorDish.Tests/ParabolicReflectorDish.Tests.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/14/ParabolicReflectorDish.Tests/UnitTest1.cs b/14/ParabolicReflectorDish.Tests/UnitTest1.cs new file mode 100644 index 0000000..0c4d24c --- /dev/null +++ b/14/ParabolicReflectorDish.Tests/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace ParabolicReflectorDish.Tests; + +public class UnitTest1 +{ + [Fact] + public void Test1() + { + + } +} \ No newline at end of file diff --git a/14/ParabolicReflectorDish/ParabolicReflectorDish.csproj b/14/ParabolicReflectorDish/ParabolicReflectorDish.csproj new file mode 100644 index 0000000..2150e37 --- /dev/null +++ b/14/ParabolicReflectorDish/ParabolicReflectorDish.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/14/ParabolicReflectorDish/Program.cs b/14/ParabolicReflectorDish/Program.cs new file mode 100644 index 0000000..3751555 --- /dev/null +++ b/14/ParabolicReflectorDish/Program.cs @@ -0,0 +1,2 @@ +// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); diff --git a/AdventOfCode2023.sln b/AdventOfCode2023.sln index 90f8bdb..efe8d0b 100644 --- a/AdventOfCode2023.sln +++ b/AdventOfCode2023.sln @@ -81,6 +81,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PointOfIncidence", "13\Poin EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PointOfIncidence.Tests", "13\PointOfIncidence.Tests\PointOfIncidence.Tests.csproj", "{0E74E842-A876-4E8C-9D10-BA09F40039EC}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "14", "14", "{D00D5BF8-754D-4D1B-9AF0-D701DFFEC9B1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParabolicReflectorDish", "14\ParabolicReflectorDish\ParabolicReflectorDish.csproj", "{671E1E43-48A1-441F-A681-111F64445504}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ParabolicReflectorDish.Tests", "14\ParabolicReflectorDish.Tests\ParabolicReflectorDish.Tests.csproj", "{631931F8-01C5-428A-9ECE-DB54FA9032FE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -194,6 +200,14 @@ Global {0E74E842-A876-4E8C-9D10-BA09F40039EC}.Debug|Any CPU.Build.0 = Debug|Any CPU {0E74E842-A876-4E8C-9D10-BA09F40039EC}.Release|Any CPU.ActiveCfg = Release|Any CPU {0E74E842-A876-4E8C-9D10-BA09F40039EC}.Release|Any CPU.Build.0 = Release|Any CPU + {671E1E43-48A1-441F-A681-111F64445504}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {671E1E43-48A1-441F-A681-111F64445504}.Debug|Any CPU.Build.0 = Debug|Any CPU + {671E1E43-48A1-441F-A681-111F64445504}.Release|Any CPU.ActiveCfg = Release|Any CPU + {671E1E43-48A1-441F-A681-111F64445504}.Release|Any CPU.Build.0 = Release|Any CPU + {631931F8-01C5-428A-9ECE-DB54FA9032FE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {631931F8-01C5-428A-9ECE-DB54FA9032FE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {631931F8-01C5-428A-9ECE-DB54FA9032FE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {631931F8-01C5-428A-9ECE-DB54FA9032FE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {3F8EAC09-4BC7-43AA-B72B-48DDD2710F6D} = {8C29858C-623A-461A-BF0B-254E151CD9C2} @@ -222,5 +236,7 @@ Global {2731DE56-AF4B-4009-8259-2EA5A9A102FE} = {ABF03698-B81C-45DE-B76A-C7FBC0C72DB6} {334D6DFF-6131-49C1-B54C-B9E8E00F1E3E} = {DC4E691A-0061-4937-8732-E8D8073C868D} {0E74E842-A876-4E8C-9D10-BA09F40039EC} = {DC4E691A-0061-4937-8732-E8D8073C868D} + {671E1E43-48A1-441F-A681-111F64445504} = {D00D5BF8-754D-4D1B-9AF0-D701DFFEC9B1} + {631931F8-01C5-428A-9ECE-DB54FA9032FE} = {D00D5BF8-754D-4D1B-9AF0-D701DFFEC9B1} EndGlobalSection EndGlobal From 24c171c790728e5726e2d0a9f120361840b2e903 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Wed, 20 Dec 2023 20:39:14 -0600 Subject: [PATCH 2/4] feat: solve part 1 --- 14/ParabolicReflectorDish.Tests/DishTests.cs | 96 ++++++++++++++++ 14/ParabolicReflectorDish.Tests/UnitTest1.cs | 10 -- 14/ParabolicReflectorDish/Program.cs | 109 ++++++++++++++++++- 3 files changed, 203 insertions(+), 12 deletions(-) create mode 100644 14/ParabolicReflectorDish.Tests/DishTests.cs delete mode 100644 14/ParabolicReflectorDish.Tests/UnitTest1.cs diff --git a/14/ParabolicReflectorDish.Tests/DishTests.cs b/14/ParabolicReflectorDish.Tests/DishTests.cs new file mode 100644 index 0000000..db823bc --- /dev/null +++ b/14/ParabolicReflectorDish.Tests/DishTests.cs @@ -0,0 +1,96 @@ +namespace ParabolicReflectorDish.Tests; + +public class DishTests +{ + [Theory] + [MemberData(nameof(TestData.ParseTestData), MemberType = typeof(TestData))] + public void Parse_GivenInput_ItShouldReturnDishInstance(string[] input, Dish expected) + { + var result = Dish.Parse(input); + result.Should().BeEquivalentTo(expected); + } + + [Theory] + [MemberData(nameof(TestData.CalculateTotalLoadTestData), MemberType = typeof(TestData))] + public void CalculateTotalLoad_WhenCalled_ItShouldReturnExpectedLoad(Dish dish, long expected) + { + var result = dish.CalculateTotalLoad(); + result.Should().Be(expected); + } + + public static class TestData + { + private static readonly Dish TestDish = new( + [ + ['O', 'O', 'O', 'O', '.', '#', '.', 'O', '.', '.'], + ['O', 'O', '.', '.', '#', '.', '.', '.', '.', '#'], + ['O', 'O', '.', '.', 'O', '#', '#', '.', '.', 'O'], + ['O', '.', '.', '#', '.', 'O', 'O', '.', '.', '.'], + ['.', '.', '.', '.', '.', '.', '.', '.', '#', '.'], + ['.', '.', '#', '.', '.', '.', '.', '#', '.', '#'], + ['.', '.', 'O', '.', '.', '#', '.', 'O', '.', 'O'], + ['.', '.', 'O', '.', '.', '.', '.', '.', '.', '.'], + ['#', '.', '.', '.', '.', '#', '#', '#', '.', '.'], + ['#', '.', '.', '.', '.', '#', '.', '.', '.', '.'], + ] + ); + + public static IEnumerable CalculateTotalLoadTestData => + new List + { + new object[] + { + new Dish( + [ + ['O', 'O', 'O', 'O', '.', '#', '.', 'O', '.', '.'], + ['O', 'O', '.', '.', '#', '.', '.', '.', '.', '#'], + ['O', 'O', '.', '.', 'O', '#', '#', '.', '.', 'O'], + ['O', '.', '.', '#', '.', 'O', 'O', '.', '.', '.'], + ['.', '.', '.', '.', '.', '.', '.', '.', '#', '.'], + ['.', '.', '#', '.', '.', '.', '.', '#', '.', '#'], + ['.', '.', 'O', '.', '.', '#', '.', 'O', '.', 'O'], + ['.', '.', 'O', '.', '.', '.', '.', '.', '.', '.'], + ['#', '.', '.', '.', '.', '#', '#', '#', '.', '.'], + ['#', '.', '.', '.', '.', '#', '.', '.', '.', '.'], + ] + ), + 136 + } + }; + + public static IEnumerable ParseTestData => + new List + { + new object[] + { + new string[] + { + "O....#....", + "O.OO#....#", + ".....##...", + "OO.#O....O", + ".O.....O#.", + "O.#..O.#.#", + "..O..#O..O", + ".......O..", + "#....###..", + "#OO..#....", + }, + new Dish( + [ + ['O', '.', '.', '.', '.', '#', '.', '.', '.', '.'], + ['O', '.', 'O', 'O', '#', '.', '.', '.', '.', '#'], + ['.', '.', '.', '.', '.', '#', '#', '.', '.', '.'], + ['O', 'O', '.', '#', 'O', '.', '.', '.', '.', 'O'], + ['.', 'O', '.', '.', '.', '.', '.', 'O', '#', '.'], + ['O', '.', '#', '.', '.', 'O', '.', '#', '.', '#'], + ['.', '.', 'O', '.', '.', '#', 'O', '.', '.', 'O'], + ['.', '.', '.', '.', '.', '.', '.', 'O', '.', '.'], + ['#', '.', '.', '.', '.', '#', '#', '#', '.', '.'], + ['#', 'O', 'O', '.', '.', '#', '.', '.', '.', '.'], + ] + ), + } + }; + } +} \ No newline at end of file diff --git a/14/ParabolicReflectorDish.Tests/UnitTest1.cs b/14/ParabolicReflectorDish.Tests/UnitTest1.cs deleted file mode 100644 index 0c4d24c..0000000 --- a/14/ParabolicReflectorDish.Tests/UnitTest1.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace ParabolicReflectorDish.Tests; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - - } -} \ No newline at end of file diff --git a/14/ParabolicReflectorDish/Program.cs b/14/ParabolicReflectorDish/Program.cs index 3751555..25e4e47 100644 --- a/14/ParabolicReflectorDish/Program.cs +++ b/14/ParabolicReflectorDish/Program.cs @@ -1,2 +1,107 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +using System.Diagnostics; + +namespace ParabolicReflectorDish; + +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 = Dish + .Parse(input) + .CalculateTotalLoad(); + + stopwatch.Stop(); + + Console.WriteLine($"The total load is {result}. ({stopwatch.ElapsedMilliseconds}ms)"); + + return (int)result; + } +} + +public class Dish( + List> rows +) +{ + private const char RoundRockSymbol = 'O'; + private const char SquareRockSymbol = '#'; + private const char EmptySpaceSymbol = '.'; + + public List> Rows { get; init; } = rows; + + private List> TiltDishToNorth(List> rows) + { + var modifiedRows = rows.ToList(); + var numOfColumns = modifiedRows[0].Count; + var numOfRows = modifiedRows.Count; + + for (var currentColumnIndex = 0; currentColumnIndex < numOfColumns; currentColumnIndex++) + { + for (var currentRowIndex = 0; currentRowIndex < numOfRows; currentRowIndex++) + { + var current = modifiedRows[currentRowIndex][currentColumnIndex]; + + if (current is EmptySpaceSymbol) + { + for (var i = currentRowIndex + 1; i < numOfRows; i++) + { + var next = modifiedRows[i][currentColumnIndex]; + + if (next is SquareRockSymbol) + { + break; + } + + if (next is RoundRockSymbol) + { + modifiedRows[currentRowIndex][currentColumnIndex] = RoundRockSymbol; + modifiedRows[i][currentColumnIndex] = EmptySpaceSymbol; + break; + } + } + } + } + } + + return modifiedRows; + } + + public long CalculateTotalLoad() + { + var rows = TiltDishToNorth(Rows); + rows.Reverse(); + + return rows + .Select((r, index) => + { + var numOfRoundRocks = r.Where(c => c is RoundRockSymbol).Count(); + return (index + 1) * numOfRoundRocks; + }) + .Sum(); + } + + public static Dish Parse(string[] input) + { + var inputList = input + .Select(s => s.ToList()) + .ToList(); + + return new Dish(inputList); + } +} \ No newline at end of file From e18b944893ed66589a382e59f5b564b8033285e1 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Wed, 20 Dec 2023 20:45:02 -0600 Subject: [PATCH 3/4] chore: add comments to tilt dish to north method --- 14/ParabolicReflectorDish/Program.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/14/ParabolicReflectorDish/Program.cs b/14/ParabolicReflectorDish/Program.cs index 25e4e47..da67d77 100644 --- a/14/ParabolicReflectorDish/Program.cs +++ b/14/ParabolicReflectorDish/Program.cs @@ -47,27 +47,42 @@ public class Dish( private List> TiltDishToNorth(List> rows) { + // create a copy of the rows which we can modify var modifiedRows = rows.ToList(); var numOfColumns = modifiedRows[0].Count; var numOfRows = modifiedRows.Count; + // iterate over the columns from left to right for (var currentColumnIndex = 0; currentColumnIndex < numOfColumns; currentColumnIndex++) { + // iterate over the rows from top to bottom for (var currentRowIndex = 0; currentRowIndex < numOfRows; currentRowIndex++) { + // get the current symbol var current = modifiedRows[currentRowIndex][currentColumnIndex]; + // if the current symbol is an empty space if (current is EmptySpaceSymbol) { + // iterate over the rows below the current row + // starting from the row after the current row for (var i = currentRowIndex + 1; i < numOfRows; i++) { + // get the next symbol in the current column var next = modifiedRows[i][currentColumnIndex]; + // if the next symbol is a square rock + // we can stop iterating over the rows below + // because the square rock blocks moving + // any round rock to fill the empty space if (next is SquareRockSymbol) { break; } + // if the next symbol is a round rock + // we can move the round rock to the empty space + // and stop iterating over the rows below if (next is RoundRockSymbol) { modifiedRows[currentRowIndex][currentColumnIndex] = RoundRockSymbol; From 786189b914edb2d001511c17b6e43e46eab440b3 Mon Sep 17 00:00:00 2001 From: Stevan Freeborn <65925598+StevanFreeborn@users.noreply.github.com> Date: Thu, 21 Dec 2023 12:27:34 -0600 Subject: [PATCH 4/4] feat: solve part 2 --- 14/ParabolicReflectorDish.Tests/DishTests.cs | 142 +++++++++- .../ParabolicReflectorDish.Tests.csproj | 12 + 14/ParabolicReflectorDish/Program.cs | 249 +++++++++++++++++- README.md | 54 ++-- 4 files changed, 417 insertions(+), 40 deletions(-) diff --git a/14/ParabolicReflectorDish.Tests/DishTests.cs b/14/ParabolicReflectorDish.Tests/DishTests.cs index db823bc..113e4f7 100644 --- a/14/ParabolicReflectorDish.Tests/DishTests.cs +++ b/14/ParabolicReflectorDish.Tests/DishTests.cs @@ -18,23 +18,147 @@ public class DishTests result.Should().Be(expected); } + [Theory] + [MemberData(nameof(TestData.CalculateTotalLoadWithSpinCycleData), MemberType = typeof(TestData))] + public void CalculateTotalLoad_WhenCalledWithSpinCycle_ItShouldReturnExpectedLoad(Dish dish, long expected) + { + var result = dish.CalculateTotalLoad(true); + result.Should().Be(expected); + } + + [Theory] + [MemberData(nameof(TestData.TiltTestData), MemberType = typeof(TestData))] + public void TiltDish_WhenCalled_ItShouldReturnExpectedDish(Dish dish, int times, Dish expected) + { + var result = dish.TiltDish(times); + result.Should().BeEquivalentTo(expected); + } + + [Fact] + public void CalculateTotalLoad_WhenCalledWithExample_ItShouldReturnExpectedLoad() + { + var input = File.ReadAllLines("EXAMPLE.txt"); + var dish = Dish.Parse(input); + var result = dish.CalculateTotalLoad(); + result.Should().Be(136); + } + + [Fact] + public void CalculateTotalLoad_WhenCalledWithInput_ItShouldReturnExpectedLoad() + { + var input = File.ReadAllLines("INPUT.txt"); + var dish = Dish.Parse(input); + var result = dish.CalculateTotalLoad(); + result.Should().Be(106997); + } + + [Fact] + public void CalculateTotalLoad_WhenCalledWithExampleAndSpinCycle_ItShouldReturnExpectedLoad() + { + var input = File.ReadAllLines("EXAMPLE.txt"); + var dish = Dish.Parse(input); + var result = dish.CalculateTotalLoad(true); + result.Should().Be(64); + } + + [Fact] + public void CalculateTotalLoad_WhenCalledWithInputAndSpinCycle_ItShouldReturnExpectedLoad() + { + var input = File.ReadAllLines("INPUT.txt"); + var dish = Dish.Parse(input); + var result = dish.CalculateTotalLoad(true); + result.Should().Be(99641); + } + public static class TestData { private static readonly Dish TestDish = new( [ - ['O', 'O', 'O', 'O', '.', '#', '.', 'O', '.', '.'], - ['O', 'O', '.', '.', '#', '.', '.', '.', '.', '#'], - ['O', 'O', '.', '.', 'O', '#', '#', '.', '.', 'O'], - ['O', '.', '.', '#', '.', 'O', 'O', '.', '.', '.'], - ['.', '.', '.', '.', '.', '.', '.', '.', '#', '.'], - ['.', '.', '#', '.', '.', '.', '.', '#', '.', '#'], - ['.', '.', 'O', '.', '.', '#', '.', 'O', '.', 'O'], - ['.', '.', 'O', '.', '.', '.', '.', '.', '.', '.'], + ['O', '.', '.', '.', '.', '#', '.', '.', '.', '.'], + ['O', '.', 'O', 'O', '#', '.', '.', '.', '.', '#'], + ['.', '.', '.', '.', '.', '#', '#', '.', '.', '.'], + ['O', 'O', '.', '#', 'O', '.', '.', '.', '.', 'O'], + ['.', 'O', '.', '.', '.', '.', '.', 'O', '#', '.'], + ['O', '.', '#', '.', '.', 'O', '.', '#', '.', '#'], + ['.', '.', 'O', '.', '.', '#', 'O', '.', '.', 'O'], + ['.', '.', '.', '.', '.', '.', '.', 'O', '.', '.'], ['#', '.', '.', '.', '.', '#', '#', '#', '.', '.'], - ['#', '.', '.', '.', '.', '#', '.', '.', '.', '.'], + ['#', 'O', 'O', '.', '.', '#', '.', '.', '.', '.'], ] ); + public static IEnumerable TiltTestData => + new List + { + new object[] + { + TestDish, + 1, + new Dish( + [ + ['.', '.', '.', '.', '.', '#', '.', '.', '.', '.'], + ['.', '.', '.', '.', '#', '.', '.', '.', 'O', '#'], + ['.', '.', '.', 'O', 'O', '#', '#', '.', '.', '.'], + ['.', 'O', 'O', '#', '.', '.', '.', '.', '.', '.'], + ['.', '.', '.', '.', '.', 'O', 'O', 'O', '#', '.'], + ['.', 'O', '#', '.', '.', '.', 'O', '#', '.', '#'], + ['.', '.', '.', '.', 'O', '#', '.', '.', '.', '.'], + ['.', '.', '.', '.', '.', '.', 'O', 'O', 'O', 'O'], + ['#', '.', '.', '.', 'O', '#', '#', '#', '.', '.'], + ['#', '.', '.', 'O', 'O', '#', '.', '.', '.', '.'], + ] + ) + }, + new object[] + { + TestDish, + 2, + new Dish( + [ + ['.', '.', '.', '.', '.', '#', '.', '.', '.', '.'], + ['.', '.', '.', '.', '#', '.', '.', '.', 'O', '#'], + ['.', '.', '.', '.', '.', '#', '#', '.', '.', '.'], + ['.', '.', 'O', '#', '.', '.', '.', '.', '.', '.'], + ['.', '.', '.', '.', '.', 'O', 'O', 'O', '#', '.'], + ['.', 'O', '#', '.', '.', '.', 'O', '#', '.', '#'], + ['.', '.', '.', '.', 'O', '#', '.', '.', '.', 'O'], + ['.', '.', '.', '.', '.', '.', '.', 'O', 'O', 'O'], + ['#', '.', '.', 'O', 'O', '#', '#', '#', '.', '.'], + ['#', '.', 'O', 'O', 'O', '#', '.', '.', '.', 'O'], + ] + ), + }, + new object[] + { + TestDish, + 3, + new Dish( + [ + ['.', '.', '.', '.', '.', '#', '.', '.', '.', '.'], + ['.', '.', '.', '.', '#', '.', '.', '.', 'O', '#'], + ['.', '.', '.', '.', '.', '#', '#', '.', '.', '.'], + ['.', '.', 'O', '#', '.', '.', '.', '.', '.', '.'], + ['.', '.', '.', '.', '.', 'O', 'O', 'O', '#', '.'], + ['.', 'O', '#', '.', '.', '.', 'O', '#', '.', '#'], + ['.', '.', '.', '.', 'O', '#', '.', '.', '.', 'O'], + ['.', '.', '.', '.', '.', '.', '.', 'O', 'O', 'O'], + ['#', '.', '.', '.', 'O', '#', '#', '#', '.', 'O'], + ['#', '.', 'O', 'O', 'O', '#', '.', '.', '.', 'O'], + ] + ), + } + }; + + public static IEnumerable CalculateTotalLoadWithSpinCycleData => + new List + { + new object[] + { + TestDish, + 64, + } + }; + public static IEnumerable CalculateTotalLoadTestData => new List { diff --git a/14/ParabolicReflectorDish.Tests/ParabolicReflectorDish.Tests.csproj b/14/ParabolicReflectorDish.Tests/ParabolicReflectorDish.Tests.csproj index 44829c1..556ee78 100644 --- a/14/ParabolicReflectorDish.Tests/ParabolicReflectorDish.Tests.csproj +++ b/14/ParabolicReflectorDish.Tests/ParabolicReflectorDish.Tests.csproj @@ -27,4 +27,16 @@ + + + PreserveNewest + + + + + + PreserveNewest + + + diff --git a/14/ParabolicReflectorDish/Program.cs b/14/ParabolicReflectorDish/Program.cs index da67d77..046e31b 100644 --- a/14/ParabolicReflectorDish/Program.cs +++ b/14/ParabolicReflectorDish/Program.cs @@ -18,6 +18,7 @@ public class Program return -2; } + var isPart2 = args.Length > 1 && args[1] == "part2"; var input = await File.ReadAllLinesAsync(args[0]); var stopwatch = new Stopwatch(); @@ -25,7 +26,7 @@ public class Program var result = Dish .Parse(input) - .CalculateTotalLoad(); + .CalculateTotalLoad(isPart2); stopwatch.Stop(); @@ -35,6 +36,11 @@ public class Program } } +/// +/// Represents a parabolic reflector dish. +/// +/// The rows of the dish. +/// An instance of the class. public class Dish( List> rows ) @@ -43,12 +49,18 @@ public class Dish( private const char SquareRockSymbol = '#'; private const char EmptySpaceSymbol = '.'; + /// + /// Gets the rows of the dish. + /// public List> Rows { get; init; } = rows; private List> TiltDishToNorth(List> rows) { // create a copy of the rows which we can modify - var modifiedRows = rows.ToList(); + var modifiedRows = rows + .Select(r => r.ToList()) + .ToList(); + var numOfColumns = modifiedRows[0].Count; var numOfRows = modifiedRows.Count; @@ -97,9 +109,233 @@ public class Dish( return modifiedRows; } - public long CalculateTotalLoad() + private List> TiltDishToWest(List> rows) { - var rows = TiltDishToNorth(Rows); + // create a copy of the rows which we can modify + var modifiedRows = rows + .Select(r => r.ToList()) + .ToList(); + + var numOfRows = modifiedRows.Count; + var numOfColumns = modifiedRows[0].Count; + + // iterate over the rows from top to bottom + for (var currentRowIndex = 0; currentRowIndex < numOfRows; currentRowIndex++) + { + // iterate over the columns from left to right + for (var currentColumnIndex = 0; currentColumnIndex < numOfRows; currentColumnIndex++) + { + // get the current symbol + var current = modifiedRows[currentRowIndex][currentColumnIndex]; + + // if the current symbol is an empty space + if (current is EmptySpaceSymbol) + { + // iterate over the columns to the left of the current column + for (var i = currentColumnIndex + 1; i < numOfColumns; i++) + { + // get the next symbol in the current row + var next = modifiedRows[currentRowIndex][i]; + + // if the next symbol is a square rock + // we can stop iterating over the columns to the left + // because the square rock blocks moving + // any round rock to fill the empty space + if (next is SquareRockSymbol) + { + break; + } + + // if the next symbol is a round rock + // we can move the round rock to the empty space + // and stop iterating over the columns to the left + if (next is RoundRockSymbol) + { + modifiedRows[currentRowIndex][currentColumnIndex] = RoundRockSymbol; + modifiedRows[currentRowIndex][i] = EmptySpaceSymbol; + break; + } + } + } + } + } + + return modifiedRows; + } + + private List> TiltDishToSouth(List> rows) + { + // create a copy of the rows which we can modify + var modifiedRows = rows + .Select(r => r.ToList()) + .ToList(); + + var numOfColumns = modifiedRows[0].Count; + var numOfRows = modifiedRows.Count; + + // iterate over the columns from left to right + for (var currentColumnIndex = 0; currentColumnIndex < numOfColumns; currentColumnIndex++) + { + // iterate over the rows from bottom to top + for (var currentRowIndex = numOfRows - 1; currentRowIndex >= 0; currentRowIndex--) + { + // get the current symbol + var current = modifiedRows[currentRowIndex][currentColumnIndex]; + + // if the current symbol is an empty space + if (current is EmptySpaceSymbol) + { + // iterate over the rows above the current row + // starting from the row before the current row + for (var i = currentRowIndex - 1; i >= 0; i--) + { + // get the next symbol in the current column + var next = modifiedRows[i][currentColumnIndex]; + + // if the next symbol is a square rock + // we can stop iterating over the rows below + // because the square rock blocks moving + // any round rock to fill the empty space + if (next is SquareRockSymbol) + { + break; + } + + // if the next symbol is a round rock + // we can move the round rock to the empty space + // and stop iterating over the rows below + if (next is RoundRockSymbol) + { + modifiedRows[currentRowIndex][currentColumnIndex] = RoundRockSymbol; + modifiedRows[i][currentColumnIndex] = EmptySpaceSymbol; + break; + } + } + } + } + } + + return modifiedRows; + } + + private List> TiltDishToEast(List> rows) + { + // create a copy of the rows which we can modify + var modifiedRows = rows + .Select(r => r.ToList()) + .ToList(); + + var numOfRows = modifiedRows.Count; + var numOfColumns = modifiedRows[0].Count; + + // iterate over the rows from top to bottom + for (var currentRowIndex = 0; currentRowIndex < numOfRows; currentRowIndex++) + { + // iterate over the columns from right to left + for (var currentColumnIndex = numOfColumns - 1; currentColumnIndex >= 0; currentColumnIndex--) + { + // get the current symbol + var current = modifiedRows[currentRowIndex][currentColumnIndex]; + + // if the current symbol is an empty space + if (current is EmptySpaceSymbol) + { + // iterate over the columns to the left of the current column + for (var i = currentColumnIndex - 1; i >= 0; i--) + { + // get the next symbol in the current row + var next = modifiedRows[currentRowIndex][i]; + + // if the next symbol is a square rock + // we can stop iterating over the columns to the left + // because the square rock blocks moving + // any round rock to fill the empty space + if (next is SquareRockSymbol) + { + break; + } + + // if the next symbol is a round rock + // we can move the round rock to the empty space + // and stop iterating over the columns to the left + if (next is RoundRockSymbol) + { + modifiedRows[currentRowIndex][currentColumnIndex] = RoundRockSymbol; + modifiedRows[currentRowIndex][i] = EmptySpaceSymbol; + break; + } + } + } + } + } + + return modifiedRows; + } + + /// + /// Tilt the dish to the north, west, south and east. + /// + /// The number of cycles to tilt the dish. + /// An instance of the class. + public Dish TiltDish(long numOfCycles) + { + var modifiedRows = Rows + .Select(r => r.ToList()) + .ToList(); + + // create a cache to store the modified rows + // and the index of the cycle in which they were modified + // this should allow us to detect cycles + var cache = new Dictionary(); + + for (var i = 0; i < numOfCycles; i++) + { + modifiedRows = TiltDishToNorth(modifiedRows); + modifiedRows = TiltDishToWest(modifiedRows); + modifiedRows = TiltDishToSouth(modifiedRows); + modifiedRows = TiltDishToEast(modifiedRows); + + // use the modified rows as a key for the cache + var key = string.Join(Environment.NewLine, modifiedRows.Select(r => string.Join(string.Empty, r))); + + // if the cache already contains the key + if (cache.TryGetValue(key, out var matchingIndex)) + { + // calculate the start index of the current cycle + // and identify the key for the start index value + var cycleLength = i - matchingIndex; + var remainingCycles = numOfCycles - i; + var cycleIndex = remainingCycles % cycleLength; + var cycleStartIndex = cycleIndex + matchingIndex - 1; + var startCycleKey = cache.First(c => c.Value == cycleStartIndex).Key; + + // parse the key for the start index value + // and use it as the modified rows + modifiedRows = startCycleKey + .Split(Environment.NewLine) + .Select(s => s.ToList()) + .ToList(); + break; + } + + // add the modified rows to the cache + cache.Add(key, i); + } + + return new Dish(modifiedRows); + } + + /// + /// Calculate the total load of the dish. + /// + /// Whether to spin the dish. + /// The total load of the dish. + public long CalculateTotalLoad(bool spinDish = false) + { + var rows = spinDish + ? TiltDish(1_000_000_000).Rows + : TiltDishToNorth(Rows); + rows.Reverse(); return rows @@ -111,6 +347,11 @@ public class Dish( .Sum(); } + /// + /// Parse the input into an instance of the class. + /// + /// The input to parse. + /// An instance of the class. public static Dish Parse(string[] input) { var inputList = input diff --git a/README.md b/README.md index eb8f2d6..1e6cc7b 100644 --- a/README.md +++ b/README.md @@ -42,30 +42,30 @@ dotnet build ## Challenges -| Day | Problem | Solution | Status | Notes | -| --- | -------------------------- | :---------------------------------: | :----: | ------------------------------------------------------------------------------------------------------------------------------------------------ | -| 01 | [Problem](./01/PROBLEM.md) | [Solution](./01/Trebuchet/) | ✅ | The trickiest part here was accounting for overlapping digit words. | -| 02 | [Problem](./02/PROBLEM.md) | [Solution](./02/CubeConundrum/) | ✅ | The key to me here was to parse the input into a useful model. | -| 03 | [Problem](./03/PROBLEM.md) | [Solution](./03/GearRatios/) | ✅ | The edge case that got me here was lines ending with a part number. | -| 04 | [Problem](./04/PROBLEM.md) | [Solution](./04/Scratchcards/) | ✅ | Part 2 gets out of hand quickly with just 200 cards. | -| 05 | [Problem](./05/PROBLEM.md) | [Solution](./05/IYGASAF/) | ✅ | I brute forced part 2 using parallelism. I know shame. | -| 06 | [Problem](./06/PROBLEM.md) | [Solution](./06/WaitForIt/) | ✅ | Thank goodness part 2 was not like 5's part 2. 😅 | -| 07 | [Problem](./07/PROBLEM.md) | [Solution](./07/CamelCards/) | ✅ | What took me longest here was I missed a case when jokers are wild and there are three groups of cards. | -| 08 | [Problem](./08/PROBLEM.md) | [Solution](./08/HauntedWasteland/) | ✅ | Got to implement a least common multiple algorithm for part 2. | -| 09 | [Problem](./09/PROBLEM.md) | [Solution](./09/MirageMaintenance/) | ✅ | Part 1 took me unnecessarily long. The bug was summing a line to check for all zeros is bad idea when negative numbers are involved. | -| 10 | [Problem](./10/PROBLEM.md) | [Solution](./10/PipeMaze/) | ✅ | So...my solutions are rather slow, but they work. Part 2 appears to have a mathematical solution, but scanning is what I came up with on my own. | -| 11 | [Problem](./11/PROBLEM.md) | [Solution](./11/CosmicExpansion/) | ✅ | Expanding the universe was fine in part 1, but part 2 showed I actually needed to calculate the expansion instead of expanding the input. | -| 12 | [Problem](./12/PROBLEM.md) | [Solution](./12/HotSprings/) | ✅ | This one stretched my skills. I needed lots of help from the interwebz. | -| 13 | [Problem](./13/PROBLEM.md) | [Solution](./13/PointOfIncidence/) | ✅ | This solution is the one I think I'm most proud of so far. | -| 14 | [Problem](./14/PROBLEM.md) | [Solution](./14/) | ⌛ | -| 15 | [Problem](./15/PROBLEM.md) | [Solution](./15/) | ⌛ | -| 16 | [Problem](./16/PROBLEM.md) | [Solution](./16/) | ⌛ | -| 17 | [Problem](./17/PROBLEM.md) | [Solution](./17/) | ⌛ | -| 18 | [Problem](./18/PROBLEM.md) | [Solution](./18/) | ⌛ | -| 19 | [Problem](./19/PROBLEM.md) | [Solution](./19/) | ⌛ | -| 20 | [Problem](./20/PROBLEM.md) | [Solution](./20/) | ⌛ | -| 21 | [Problem](./21/PROBLEM.md) | [Solution](./21/) | ⌛ | -| 22 | [Problem](./22/PROBLEM.md) | [Solution](./22/) | ⌛ | -| 23 | [Problem](./23/PROBLEM.md) | [Solution](./23/) | ⌛ | -| 24 | [Problem](./24/PROBLEM.md) | [Solution](./24/) | ⌛ | -| 25 | [Problem](./25/PROBLEM.md) | [Solution](./25/) | ⌛ | +| Day | Problem | Solution | Status | Notes | +| --- | -------------------------- | :--------------------------------------: | :----: | ------------------------------------------------------------------------------------------------------------------------------------------------ | +| 01 | [Problem](./01/PROBLEM.md) | [Solution](./01/Trebuchet/) | ✅ | The trickiest part here was accounting for overlapping digit words. | +| 02 | [Problem](./02/PROBLEM.md) | [Solution](./02/CubeConundrum/) | ✅ | The key to me here was to parse the input into a useful model. | +| 03 | [Problem](./03/PROBLEM.md) | [Solution](./03/GearRatios/) | ✅ | The edge case that got me here was lines ending with a part number. | +| 04 | [Problem](./04/PROBLEM.md) | [Solution](./04/Scratchcards/) | ✅ | Part 2 gets out of hand quickly with just 200 cards. | +| 05 | [Problem](./05/PROBLEM.md) | [Solution](./05/IYGASAF/) | ✅ | I brute forced part 2 using parallelism. I know shame. | +| 06 | [Problem](./06/PROBLEM.md) | [Solution](./06/WaitForIt/) | ✅ | Thank goodness part 2 was not like 5's part 2. 😅 | +| 07 | [Problem](./07/PROBLEM.md) | [Solution](./07/CamelCards/) | ✅ | What took me longest here was I missed a case when jokers are wild and there are three groups of cards. | +| 08 | [Problem](./08/PROBLEM.md) | [Solution](./08/HauntedWasteland/) | ✅ | Got to implement a least common multiple algorithm for part 2. | +| 09 | [Problem](./09/PROBLEM.md) | [Solution](./09/MirageMaintenance/) | ✅ | Part 1 took me unnecessarily long. The bug was summing a line to check for all zeros is bad idea when negative numbers are involved. | +| 10 | [Problem](./10/PROBLEM.md) | [Solution](./10/PipeMaze/) | ✅ | So...my solutions are rather slow, but they work. Part 2 appears to have a mathematical solution, but scanning is what I came up with on my own. | +| 11 | [Problem](./11/PROBLEM.md) | [Solution](./11/CosmicExpansion/) | ✅ | Expanding the universe was fine in part 1, but part 2 showed I actually needed to calculate the expansion instead of expanding the input. | +| 12 | [Problem](./12/PROBLEM.md) | [Solution](./12/HotSprings/) | ✅ | This one stretched my skills. I needed lots of help from the interwebz. | +| 13 | [Problem](./13/PROBLEM.md) | [Solution](./13/PointOfIncidence/) | ✅ | This solution is the one I think I'm most proud of so far. | +| 14 | [Problem](./14/PROBLEM.md) | [Solution](./14/ParabolicReflectorDish/) | ✅ | Cycles and a cache...that's all I'm going to say. | +| 15 | [Problem](./15/PROBLEM.md) | [Solution](./15/) | ⌛ | +| 16 | [Problem](./16/PROBLEM.md) | [Solution](./16/) | ⌛ | +| 17 | [Problem](./17/PROBLEM.md) | [Solution](./17/) | ⌛ | +| 18 | [Problem](./18/PROBLEM.md) | [Solution](./18/) | ⌛ | +| 19 | [Problem](./19/PROBLEM.md) | [Solution](./19/) | ⌛ | +| 20 | [Problem](./20/PROBLEM.md) | [Solution](./20/) | ⌛ | +| 21 | [Problem](./21/PROBLEM.md) | [Solution](./21/) | ⌛ | +| 22 | [Problem](./22/PROBLEM.md) | [Solution](./22/) | ⌛ | +| 23 | [Problem](./23/PROBLEM.md) | [Solution](./23/) | ⌛ | +| 24 | [Problem](./24/PROBLEM.md) | [Solution](./24/) | ⌛ | +| 25 | [Problem](./25/PROBLEM.md) | [Solution](./25/) | ⌛ |