diff --git a/AoC2018.Runner/Properties/launchSettings.json b/AoC2018.Runner/Properties/launchSettings.json index b6ed4bc..2b5ac1a 100644 --- a/AoC2018.Runner/Properties/launchSettings.json +++ b/AoC2018.Runner/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "AoC2018.Runner": { "commandName": "Project", - "commandLineArgs": "17 1" + "commandLineArgs": "18 1" } } } \ No newline at end of file diff --git a/AoC2018.Solutions/Day18/MapAcre.cs b/AoC2018.Solutions/Day18/MapAcre.cs new file mode 100644 index 0000000..8d4bdb2 --- /dev/null +++ b/AoC2018.Solutions/Day18/MapAcre.cs @@ -0,0 +1,13 @@ +namespace AoC2018.Solutions.Day18 +{ + using System.Collections.Generic; + + public static class MapAcre + { + public const char Open = '.'; + + public const char Trees = '|'; + + public const char Lumberyard = '#'; + } +} diff --git a/AoC2018.Solutions/Day18/MapExtensions.cs b/AoC2018.Solutions/Day18/MapExtensions.cs new file mode 100644 index 0000000..baf86bb --- /dev/null +++ b/AoC2018.Solutions/Day18/MapExtensions.cs @@ -0,0 +1,128 @@ +namespace AoC2018.Solutions.Day18 +{ + using System; + using System.Collections.Generic; + using System.Linq; + + public static class MapExtensions + { + public static void WriteToConsole(this (char[] Map, int yOffset) state) + { + int rows = state.Map.Length / state.yOffset; + + for (int i = 0; i < rows; i++) + { + string row = new string(state.Map.Skip(i * state.yOffset).Take(state.yOffset).ToArray()); + Console.WriteLine(row); + } + } + + public static (char[] Map, int yOffset) GetNextState(this (char[] Map, int yOffset) state) + { + char[] result = new char[state.Map.Length]; + + for (int i = 0; i < result.Length; i++) + { + result[i] = state.GetNextStateForAcre(i); + } + + return (result, state.yOffset); + } + + public static string Memoize(this (char[] Map, int yOffset) state) + { + // Lazy, as I'm not bothering to include the yOffset... + return new string(state.Map); + } + + public static char GetNextStateForAcre(this (char[] Map, int yOffset) state, int acre) + { + char[] adjacentAcres = state.GetAdjacentAcres(acre).ToArray(); + + switch (state.Map[acre]) + { + case MapAcre.Open: + if (adjacentAcres.Count(x => x == MapAcre.Trees) >= 3) + { + return MapAcre.Trees; + } + + return MapAcre.Open; + + case MapAcre.Trees: + if (adjacentAcres.Count(x => x == MapAcre.Lumberyard) >= 3) + { + return MapAcre.Lumberyard; + } + + return MapAcre.Trees; + + case MapAcre.Lumberyard: + if (adjacentAcres.Count(x => x == MapAcre.Lumberyard) >= 1 && adjacentAcres.Count(x => x == MapAcre.Trees) >= 1) + { + return MapAcre.Lumberyard; + } + + return MapAcre.Open; + } + + throw new InvalidOperationException(); + } + + public static IEnumerable GetAdjacentAcres(this (char[] Map, int yOffset) state, int acre) + { + int up = acre - state.yOffset; + int left = acre - 1; + int right = acre + 1; + int down = acre + state.yOffset; + + int minX = (acre / state.yOffset) * state.yOffset; + int maxX = minX + state.yOffset; + + bool canLookUp = up >= 0; + bool canLookDown = down < state.Map.Length; + bool canLookLeft = left >= minX; + bool canLookRight = right < maxX; + + if (canLookUp) + { + yield return state.Map[up]; + + if (canLookLeft) + { + yield return state.Map[up - 1]; + } + + if (canLookRight) + { + yield return state.Map[up + 1]; + } + } + + if (canLookDown) + { + yield return state.Map[down]; + + if (canLookLeft) + { + yield return state.Map[down - 1]; + } + + if (canLookRight) + { + yield return state.Map[down + 1]; + } + } + + if (canLookLeft) + { + yield return state.Map[left]; + } + + if (canLookRight) + { + yield return state.Map[right]; + } + } + } +} diff --git a/AoC2018.Solutions/Day18/MapParser.cs b/AoC2018.Solutions/Day18/MapParser.cs new file mode 100644 index 0000000..49f33dd --- /dev/null +++ b/AoC2018.Solutions/Day18/MapParser.cs @@ -0,0 +1,17 @@ +namespace AoC2018.Solutions.Day18 +{ + using System; + using System.Linq; + + public static class MapParser + { + public static (char[] Map, int yOffset) Parse(string input) + { + string[] rows = input.Split(new string[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries); + int yOffset = rows[0].Length; + char[] map = rows.SelectMany(x => x.ToCharArray()).ToArray(); + + return (map, yOffset); + } + } +} diff --git a/AoC2018.Solutions/Day18/Part01.cs b/AoC2018.Solutions/Day18/Part01.cs new file mode 100644 index 0000000..2dcd0ab --- /dev/null +++ b/AoC2018.Solutions/Day18/Part01.cs @@ -0,0 +1,29 @@ +namespace AoC2018.Solutions.Day18 +{ + using System; + using System.Linq; + + public class Part01 : ISolution + { + public string Solve(string input) + { + (char[] Map, int yOffset) map = MapParser.Parse(input); + + ////Console.WriteLine("Initial state:"); + ////map.WriteToConsole(); + + for (int i = 0; i < 10; i++) + { + map = map.GetNextState(); + + ////Console.WriteLine($"After {i + 1} minute:"); + ////map.WriteToConsole(); + } + + int woodCount = map.Map.Count(x => x == MapAcre.Trees); + int lumberYardCount = map.Map.Count(x => x == MapAcre.Lumberyard); + + return (woodCount * lumberYardCount).ToString(); + } + } +} diff --git a/AoC2018.Solutions/Day18/Part02.cs b/AoC2018.Solutions/Day18/Part02.cs new file mode 100644 index 0000000..db661d0 --- /dev/null +++ b/AoC2018.Solutions/Day18/Part02.cs @@ -0,0 +1,53 @@ +namespace AoC2018.Solutions.Day18 +{ + using System; + using System.Collections.Generic; + using System.Linq; + + public class Part02 : ISolution + { + public string Solve(string input) + { + (char[] Map, int yOffset) map = MapParser.Parse(input); + + // Predictably, part 2 is just part 1 amped up. Equally predictably, this means + // that after a certain amount of time, the pattern will settle into a loop. This + // means we need to: + // 1. Work out the point at which we start iterating. + // 2. Find the iteration length. + // 3. Do some maths + // 4. Iterate a few more times + var states = new List(10000); + string mapMemo = map.Memoize(); + + do + { + states.Add(mapMemo); + + map = map.GetNextState(); + mapMemo = map.Memoize(); + } + while (!states.Contains(mapMemo)); + + Console.WriteLine($"Iteration point hit after {states.Count} iterations"); + + // We've hit a point where we've seen this state before. + int originalIterationForThisState = states.IndexOf(mapMemo); + int iterationLength = states.Count - originalIterationForThisState; + + const int target = 1000000000; + int numberOfWholeIterations = (target - originalIterationForThisState) / iterationLength; + int remainingTicksRequired = target - originalIterationForThisState - (numberOfWholeIterations * iterationLength); + + for (int i = 0; i < remainingTicksRequired; i++) + { + map = map.GetNextState(); + } + + int woodCount = map.Map.Count(x => x == MapAcre.Trees); + int lumberYardCount = map.Map.Count(x => x == MapAcre.Lumberyard); + + return (woodCount * lumberYardCount).ToString(); + } + } +} diff --git a/AoC2018.Tests/AoCTestCases.cs b/AoC2018.Tests/AoCTestCases.cs index 5672691..8b40113 100644 --- a/AoC2018.Tests/AoCTestCases.cs +++ b/AoC2018.Tests/AoCTestCases.cs @@ -56,6 +56,7 @@ public class AoCTestCases [TestCase(16, 1, "Before: [3, 2, 1, 1]\r\n9 2 1 2\r\nAfter: [3, 2, 2, 1]", "1")] [TestCase(17, 1, "x=495, y=2..7\r\ny=7, x=495..501\r\nx=501, y=3..7\r\nx=498, y=2..4\r\nx=506, y=1..2\r\nx=498, y=10..13\r\nx=504, y=10..13\r\ny=13, x=498..504", "57")] [TestCase(17, 2, "x=495, y=2..7\r\ny=7, x=495..501\r\nx=501, y=3..7\r\nx=498, y=2..4\r\nx=506, y=1..2\r\nx=498, y=10..13\r\nx=504, y=10..13\r\ny=13, x=498..504", "29")] + [TestCase(18, 1, ".#.#...|#.\r\n.....#|##|\r\n.|..|...#.\r\n..|#.....#\r\n#.#|||#|#|\r\n...#.||...\r\n.|....|...\r\n||...#|.#|\r\n|.||||..|.\r\n...#.|..|.", "1147")] public void Tests(int day, int part, string input, string expectedResult) { ISolution solution = SolutionFactory.GetSolution(day, part);