diff --git a/11/CosmicExpansion.Tests/CosmicExpansion.Tests.csproj b/11/CosmicExpansion.Tests/CosmicExpansion.Tests.csproj new file mode 100644 index 0000000..08f293c --- /dev/null +++ b/11/CosmicExpansion.Tests/CosmicExpansion.Tests.csproj @@ -0,0 +1,36 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + PreserveNewest + + + + diff --git a/11/CosmicExpansion.Tests/GlobalUsings.cs b/11/CosmicExpansion.Tests/GlobalUsings.cs new file mode 100644 index 0000000..7fef4b0 --- /dev/null +++ b/11/CosmicExpansion.Tests/GlobalUsings.cs @@ -0,0 +1,2 @@ +global using Xunit; +global using FluentAssertions; \ No newline at end of file diff --git a/11/CosmicExpansion.Tests/UniverseTests.cs b/11/CosmicExpansion.Tests/UniverseTests.cs new file mode 100644 index 0000000..e99b316 --- /dev/null +++ b/11/CosmicExpansion.Tests/UniverseTests.cs @@ -0,0 +1,143 @@ +namespace CosmicExpansion.Tests; + +public class UniverseTests +{ + [Theory] + [MemberData(nameof(TestData.ParseTestData), MemberType = typeof(TestData))] + public void Parse_WhenGivenUniverseWithGalaxies_ItShouldReturnListOfGalaxies(string[] givenInput, Universe expectedUniverse) + { + var result = Universe.Parse(givenInput); + result.Should().BeEquivalentTo(expectedUniverse); + } + + [Theory] + [MemberData(nameof(TestData.GetShortestPathsBetweenGalaxiesTestData), MemberType = typeof(TestData))] + public void GetShortestPathsBetweenGalaxies_WhenGivenUniverseWithGalaxies_ItShouldReturnShortestPathsBetweenGalaxies(Universe givenUniverse, int expectedNumberOrPaths) + { + var result = givenUniverse.GetShortestPathsBetweenGalaxies(); + result.Count.Should().Be(expectedNumberOrPaths); + } + + [Theory] + [MemberData(nameof(TestData.SumOfShortestPathsBetweenGalaxiesTestData), MemberType = typeof(TestData))] + public void SumOfShortestPathsBetweenGalaxies_WhenGivenUniverseWithGalaxies_ItShouldReturnSumOfShortestPathsBetweenGalaxies(Universe givenUniverse, int expectedSumOfShortestPaths) + { + givenUniverse.SumOfShortestPathsBetweenGalaxies.Should().Be(expectedSumOfShortestPaths); + } + + [Theory] + [MemberData(nameof(TestData.CalculateDistanceBetweenGalaxiesTestData), MemberType = typeof(TestData))] + public void CalculateDistanceBetweenGalaxies_WhenGivenTwoGalaxies_ItShouldReturnDistanceBetweenGalaxies(Galaxy startGalaxy, Galaxy endGalaxy, int expectedDistance) + { + var result = Universe.CalculateDistanceBetweenGalaxies(startGalaxy, endGalaxy); + result.Should().Be(expectedDistance); + } + + [Fact] + public void SumOfShortestPathsBetweenGalaxies_WhenGivenInput_ItShouldReturnSumOfShortestPathsBetweenGalaxies() + { + var input = File.ReadAllLines("INPUT.txt"); + var universe = Universe.Parse(input); + universe.SumOfShortestPathsBetweenGalaxies.Should().Be(10292708); + } + + [Fact] + public void SumOfShortestPathsBetweenGalaxies_WhenGivenInputAndIsPart2_ItShouldReturnSumOfShortestPathsBetweenGalaxies() + { + var input = File.ReadAllLines("INPUT.txt"); + var universe = Universe.Parse(input, 1_000_000); + universe.SumOfShortestPathsBetweenGalaxies.Should().Be(790194712336); + } + + + + public static class TestData + { + private static readonly Universe TestUniverse = new( + 12, + 13, + [ + new(0, 4), + new(1, 9), + new(2, 0), + new(5, 8), + new(6, 1), + new(7, 12), + new(10, 9), + new(11, 0), + new(11, 5), + ] + ); + + public static IEnumerable ParseTestData => + new List + { + new object[] + { + new string[] + { + "...#......", + ".......#..", + "#.........", + "..........", + "......#...", + ".#........", + ".........#", + "..........", + ".......#..", + "#...#.....", + }, + TestUniverse, + } + }; + + public static IEnumerable GetShortestPathsBetweenGalaxiesTestData => + new List + { + new object[] + { + TestUniverse, + 36, + } + }; + + public static IEnumerable SumOfShortestPathsBetweenGalaxiesTestData => + new List + { + new object[] + { + TestUniverse, + 374, + } + }; + + public static IEnumerable CalculateDistanceBetweenGalaxiesTestData => + new List + { + new object[] + { + TestUniverse.Galaxies[4], + TestUniverse.Galaxies[8], + 9, + }, + new object[] + { + TestUniverse.Galaxies[0], + TestUniverse.Galaxies[6], + 15, + }, + new object[] + { + TestUniverse.Galaxies[2], + TestUniverse.Galaxies[5], + 17, + }, + new object[] + { + TestUniverse.Galaxies[7], + TestUniverse.Galaxies[8], + 5, + } + }; + } +} \ No newline at end of file diff --git a/11/CosmicExpansion/CosmicExpansion.csproj b/11/CosmicExpansion/CosmicExpansion.csproj new file mode 100644 index 0000000..2150e37 --- /dev/null +++ b/11/CosmicExpansion/CosmicExpansion.csproj @@ -0,0 +1,10 @@ + + + + Exe + net8.0 + enable + enable + + + diff --git a/11/CosmicExpansion/Program.cs b/11/CosmicExpansion/Program.cs new file mode 100644 index 0000000..424c97a --- /dev/null +++ b/11/CosmicExpansion/Program.cs @@ -0,0 +1,223 @@ +using System.Diagnostics; + +namespace CosmicExpansion; + +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 isPart2 = args.Length > 1 && args[1] == "part2"; + var input = await File.ReadAllLinesAsync(args[0]); + + var stopwatch = new Stopwatch(); + stopwatch.Start(); + + var universe = isPart2 + ? Universe.Parse(input, 1_000_000) + : Universe.Parse(input); + + var result = universe.SumOfShortestPathsBetweenGalaxies; + + stopwatch.Stop(); + + Console.WriteLine($"The sum of the shortest paths between galaxies is {result}. ({stopwatch.ElapsedMilliseconds}ms)"); + + return (int)result; + } +} + +/// +/// Represents the universe. +/// +/// The height of the universe. +/// The width of the universe. +/// The galaxies in the universe. +/// An instance of . +public class Universe( + int height, + int width, + List galaxies +) +{ + private const char GalaxySymbol = '#'; + + /// + /// Gets the height of the universe. + /// + public int Height { get; init; } = height; + + /// + /// Gets the width of the universe. + /// + public int Width { get; init; } = width; + + /// + /// Gets the galaxies in the universe. + /// + public List Galaxies { get; init; } = galaxies; + + /// + /// Gets the sum of the shortest paths between galaxies. + /// + public long SumOfShortestPathsBetweenGalaxies => + GetShortestPathsBetweenGalaxies() + .Values + .Sum(); + + /// + /// Gets the shortest paths between all pairs of galaxies. + /// + /// A dictionary containing the shortest paths between galaxies. + public Dictionary<(int galaxyOneIndex, int galaxyTwoIndex), long> GetShortestPathsBetweenGalaxies() + { + var shortestPaths = new Dictionary<(int galaxyOneIndex, int galaxyTwoIndex), long>(); + + for (int i = 0; i < Galaxies.Count; i++) + { + var galaxyOne = Galaxies[i]; + + for (int j = i + 1; j < Galaxies.Count; j++) + { + var galaxyTwo = Galaxies[j]; + + if (shortestPaths.ContainsKey((i, j)) || shortestPaths.ContainsKey((j, i))) + { + continue; + } + + var shortestPath = CalculateDistanceBetweenGalaxies(galaxyOne, galaxyTwo); + shortestPaths.Add((i, j), shortestPath); + } + } + + return shortestPaths; + } + + /// + /// Calculates the distance between two galaxies. + /// + /// The first galaxy. + /// The second galaxy. + /// The distance between the two galaxies. + public static long CalculateDistanceBetweenGalaxies(Galaxy galaxyOne, Galaxy galaxyTwo) => + Math.Abs(galaxyTwo.Row - galaxyOne.Row) + Math.Abs(galaxyTwo.Column - galaxyOne.Column); + + /// + /// Parses the input into an instance of . + /// + /// The input. + /// The factor by which to expand the universe. + /// An instance of . + public static Universe Parse(string[] input, int expandFactor = 2) + { + var emptyRows = new List(); + var emptyColumns = new List(); + var galaxies = new List(); + + for (int i = 0; i < input.Length; i++) + { + var currentRow = input[i]; + + if (currentRow.Contains(GalaxySymbol) is false) + { + emptyRows.Add(i); + } + } + + for (int i = 0; i < input[0].Length; i++) + { + var galaxyInColumn = false; + + for (int j = 0; j < input.Length; j++) + { + var currentPosition = input[j][i]; + + if (currentPosition == GalaxySymbol) + { + galaxyInColumn = true; + break; + } + } + + if (galaxyInColumn is false) + { + emptyColumns.Add(i); + } + } + + for (int i = 0; i < input.Length; i++) + { + var currentRow = input[i]; + + for (int j = 0; j < currentRow.Length; j++) + { + var currentPosition = currentRow[j]; + + if (currentPosition == GalaxySymbol) + { + var numOfEmptyRowsBefore = emptyRows.Where(row => row < i).Count(); + var rowOffset = numOfEmptyRowsBefore is 0 + ? 0 + : (numOfEmptyRowsBefore * expandFactor) - numOfEmptyRowsBefore; + + var numOfEmptyColumnsBefore = emptyColumns.Where(column => column < j).Count(); + var columnOffset = numOfEmptyColumnsBefore is 0 + ? 0 + : (numOfEmptyColumnsBefore * expandFactor) - numOfEmptyColumnsBefore; + + galaxies.Add(new Galaxy(i + rowOffset, j + columnOffset)); + } + } + } + + var heightOffset = emptyRows.Count is 0 + ? 0 + : (emptyRows.Count * expandFactor) - emptyRows.Count; + var widthOffset = emptyColumns.Count is 0 + ? 0 + : (emptyColumns.Count * expandFactor) - emptyColumns.Count; + + var height = input.Length + heightOffset; + var width = input[0].Length + widthOffset; + + return new Universe( + height, + width, + galaxies + ); + } +} + +/// +/// Represents a galaxy. +/// +/// The row of the galaxy. +/// The column of the galaxy. +/// An instance of . +public class Galaxy( + long row, + long column +) +{ + /// + /// Gets the row of the galaxy. + /// + public long Row { get; init; } = row; + + /// + /// Gets the column of the galaxy. + /// + public long Column { get; init; } = column; +} \ No newline at end of file diff --git a/AdventOfCode2023.sln b/AdventOfCode2023.sln index 6ea177f..34aded4 100644 --- a/AdventOfCode2023.sln +++ b/AdventOfCode2023.sln @@ -63,6 +63,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PipeMaze", "10\PipeMaze\Pip EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PipeMaze.Tests", "10\PipeMaze.Tests\PipeMaze.Tests.csproj", "{B1DA407F-DA29-4BDA-B10E-8B9A976B0C5E}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "11", "11", "{01A6D73F-996E-48B3-B111-2725016A49C2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CosmicExpansion", "11\CosmicExpansion\CosmicExpansion.csproj", "{580A3C98-7A39-4323-839A-5C2E0B6E8E74}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CosmicExpansion.Tests", "11\CosmicExpansion.Tests\CosmicExpansion.Tests.csproj", "{0D6CD658-65BB-47DC-A865-E3428EBE3075}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -152,6 +158,14 @@ Global {B1DA407F-DA29-4BDA-B10E-8B9A976B0C5E}.Debug|Any CPU.Build.0 = Debug|Any CPU {B1DA407F-DA29-4BDA-B10E-8B9A976B0C5E}.Release|Any CPU.ActiveCfg = Release|Any CPU {B1DA407F-DA29-4BDA-B10E-8B9A976B0C5E}.Release|Any CPU.Build.0 = Release|Any CPU + {580A3C98-7A39-4323-839A-5C2E0B6E8E74}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {580A3C98-7A39-4323-839A-5C2E0B6E8E74}.Debug|Any CPU.Build.0 = Debug|Any CPU + {580A3C98-7A39-4323-839A-5C2E0B6E8E74}.Release|Any CPU.ActiveCfg = Release|Any CPU + {580A3C98-7A39-4323-839A-5C2E0B6E8E74}.Release|Any CPU.Build.0 = Release|Any CPU + {0D6CD658-65BB-47DC-A865-E3428EBE3075}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0D6CD658-65BB-47DC-A865-E3428EBE3075}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0D6CD658-65BB-47DC-A865-E3428EBE3075}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0D6CD658-65BB-47DC-A865-E3428EBE3075}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {3F8EAC09-4BC7-43AA-B72B-48DDD2710F6D} = {8C29858C-623A-461A-BF0B-254E151CD9C2} @@ -174,5 +188,7 @@ Global {5C39C115-ACBF-458A-9409-FBD23637A789} = {79203C8D-F382-4C95-90EA-FECEE65602DA} {B46ADCFF-35FF-496D-A035-FE2B9FFC185A} = {9703A36A-A019-4ADC-93A7-9DF4EFBCBFBC} {B1DA407F-DA29-4BDA-B10E-8B9A976B0C5E} = {9703A36A-A019-4ADC-93A7-9DF4EFBCBFBC} + {580A3C98-7A39-4323-839A-5C2E0B6E8E74} = {01A6D73F-996E-48B3-B111-2725016A49C2} + {0D6CD658-65BB-47DC-A865-E3428EBE3075} = {01A6D73F-996E-48B3-B111-2725016A49C2} EndGlobalSection EndGlobal diff --git a/README.md b/README.md index 7ceeeee..145f4e8 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ dotnet build | 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/) | ⌛ | +| 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/) | ⌛ | | 13 | [Problem](./13/PROBLEM.md) | [Solution](./13/) | ⌛ | | 14 | [Problem](./14/PROBLEM.md) | [Solution](./14/) | ⌛ |