From f4b5111fa0cbac95bfe5f20c261fbce654e7babf Mon Sep 17 00:00:00 2001 From: Sebastian Lindemeier Date: Fri, 12 Dec 2025 20:11:53 +0100 Subject: [PATCH] Refactor Day10 to remove unoptimized methods, simplify input parsing, and improve readability and maintainability of lamp and joltage logic by consolidating bitmask logic --- AoC_2025.Tests/Day10Test.cs | 16 ----- AoC_2025/Day10.cs | 125 +++++++----------------------------- 2 files changed, 24 insertions(+), 117 deletions(-) diff --git a/AoC_2025.Tests/Day10Test.cs b/AoC_2025.Tests/Day10Test.cs index b402f66..43cc4d3 100644 --- a/AoC_2025.Tests/Day10Test.cs +++ b/AoC_2025.Tests/Day10Test.cs @@ -23,22 +23,6 @@ public class Day10Test [Fact] public void Part01_Prod_equals_500() - { - var actual = (_sut as Day10).SolvePart1Unoptimized(ProdInputPath); - - Assert.Equal(500, actual); - } - - [Fact] - public void Part01Unop_Test_equals_7() - { - var actual = (_sut as Day10).SolvePart1Unoptimized(TestInputPath); - - Assert.Equal(7, actual); - } - - [Fact] - public void Part01Unop_Prod_equals_500() { var actual = _sut.SolvePart1(ProdInputPath); diff --git a/AoC_2025/Day10.cs b/AoC_2025/Day10.cs index f1326c6..1e1ddab 100644 --- a/AoC_2025/Day10.cs +++ b/AoC_2025/Day10.cs @@ -8,41 +8,7 @@ namespace AoC_2025; public class Day10 : IPuzzleSolver { - private (uint lamps, uint[] buttons)[] ParsePuzzleInput(string path) - { - var puzzleInput = File.ReadAllLines(path) - .Where(line => !string.IsNullOrWhiteSpace(line)) - .Select(line => line.Split(' ', StringSplitOptions.RemoveEmptyEntries)) - .Select(instruction => instruction switch - { - [var lamps, ..var buttons, var joltages] => (lamps: lamps[1..^1], buttons, joltages: joltages[1..^1]), - _ => throw new DataException("Misaligned data") - }) - .OrderBy(inst => inst.lamps.Length) - .ToArray(); - var lamps = puzzleInput - .Select(input => input.lamps) - .Select(lamps => lamps.Index()) - .Select(ixlamps => - ixlamps.Aggregate((uint)0L, (acc, ixLamp) => acc | ((uint)(ixLamp.Item == '#' ? 1L : 0L) << ixLamp.Index))) - .ToArray(); - var buttons = puzzleInput - .Select(input => input.buttons) - .Select(buttons => buttons - .Select(button => button[1..^1] - .Split(',', StringSplitOptions.RemoveEmptyEntries) - .Select(lampIndex => int.Parse(lampIndex)) - .ToArray()) - .Select(lampIndexes => lampIndexes.Aggregate((uint)0L, (acc, lampIndex) => acc | ((uint)1 << lampIndex))) - .ToArray()) - .ToArray(); - var res = lamps - .Zip(buttons, (lamp, button) => (lamp, button)) - .ToArray(); - return res; - } - - private (bool[] lamps, int[][] buttons, int[] joltages)[] ParsePuzzleInputUnoptimized(string path) + private (bool[] lamps, int[][] buttons, int[] joltages)[] ParsePuzzleInput(string path) { var puzzleInput = File.ReadAllLines(path) .Where(line => !string.IsNullOrWhiteSpace(line)) @@ -65,7 +31,7 @@ public class Day10 : IPuzzleSolver .Select(buttons => buttons .Select(button => button[1..^1] .Split(',', StringSplitOptions.RemoveEmptyEntries) - .Select(lampIndex => int.Parse(lampIndex)) + .Select(int.Parse) .ToArray()) .ToArray()) .ToArray(); @@ -73,7 +39,7 @@ public class Day10 : IPuzzleSolver .Select(input => input.joltages) .Select(joltage => joltage .Split(',', StringSplitOptions.RemoveEmptyEntries) - .Select(jolt => int.Parse(jolt)) + .Select(int.Parse) .ToArray()) .ToArray(); var res = lamps @@ -87,26 +53,14 @@ public class Day10 : IPuzzleSolver { var instructions = ParsePuzzleInput(pathToPuzzleInput); var minCounts = instructions - .Select(ins => GetMinCountButtonPressesForLamps(ins.lamps, ins.buttons)) + .Select(instruction => GetMinCountButtonPressesForLamps(instruction.lamps, instruction.buttons)) .ToArray(); return minCounts.Sum(); } - public long SolvePart1Unoptimized(string pathToPuzzleInput) - { - var instructions = ParsePuzzleInputUnoptimized(pathToPuzzleInput); - var minCounts = instructions - .Select(ins => - GetButtonPatternCostsForLamps(ins.lamps, ins.buttons) - .Order() - .First()) - .ToArray(); - return minCounts.Sum(); - } - public long SolvePart2(string pathToPuzzleInput) { - var instructions = ParsePuzzleInputUnoptimized(pathToPuzzleInput); + var instructions = ParsePuzzleInput(pathToPuzzleInput); var minCounts = instructions .Select(instruction => GetMinCountButtonPressesForJoltageLevels(instruction.joltages, instruction.buttons)) .ToArray(); @@ -116,76 +70,43 @@ public class Day10 : IPuzzleSolver // not proud of you public long SolvePart2Z3(string pathToPuzzleInput) { - var instructions = ParsePuzzleInputUnoptimized(pathToPuzzleInput); + var instructions = ParsePuzzleInput(pathToPuzzleInput); var minCounts = instructions .Select(ins => GetMinCountButtonPressesForJoltagesZ3(ins.joltages, ins.buttons)) .ToArray(); return minCounts.Sum(); } - private long GetMinCountButtonPressesForLamps(uint lamps, uint[] buttons) + private long GetMinCountButtonPressesForLamps(bool[] lamps, int[][] buttons) { - var presses = 0L; - HashSet lampStates = [lamps]; - while (lampStates.All(state => state > 0) && presses < 100) - { - lampStates = lampStates - .Select(lampState => GetLampStates(lampState, buttons)) - .Aggregate((current, states) => [..current,..states]) - .ToHashSet(); - presses++; - } - - return presses; - - static uint[] GetLampStates(uint lampState, uint[] buttons) - { - var states = buttons - .Where(button => (lampState & button) > 0) - .Select(button => lampState ^ button); - return [..states]; - } - } - - private IEnumerable GetButtonPatternCostsForLamps(bool[] lampState, int[][] buttons) - { - if (lampState.All(x => !x)) - { - yield return 0; - yield break; - } - - foreach (var (_,(pattern, cost)) in GetPatternCosts(lampState.Length, buttons)) - { - if (pattern.Zip(lampState, (pat, lam) => (pat % 2 == 1) == lam).All(x => x)) - { - yield return cost; - } - } + var lampMask = AsMask(lamps, x => x); + var pattern = GetPatternCosts(lamps.Length, buttons) + .First(pattern => pattern.mask == lampMask); + return pattern.data.cost; } private long GetMinCountButtonPressesForJoltageLevels(int[] joltages, int[][] buttons) { var patternCosts = GetPatternCosts(joltages.Length, buttons) - .GroupBy(x => x.Item1) - .ToDictionary(x => x.Key, x => x.Select(y => y.Item2).ToArray()); + .GroupBy(x => x.mask) + .ToDictionary(x => x.Key, x => x.Select(y => y.data).ToArray()); var res = GetMinCountInternal(joltages, patternCosts, new Dictionary(new IntArrayEqualityComparer())); return res; static long GetMinCountInternal(int[] targetJoltages, Dictionary patternCosts, Dictionary memory) { - if (targetJoltages.Min() < 0) return 1_000_000_000L; + if (targetJoltages.Any(j => j < 0)) return 1_000_000_000L; if (targetJoltages.All(j => j == 0)) return 0; if (memory.TryGetValue(targetJoltages, out var knownResult)) return knownResult; var minCount = 1_000_000_000L; - var joltageMask = GetMask(targetJoltages, level => level % 2 == 1); + var joltageMask = AsMask(targetJoltages, level => level % 2 == 1); var patternsToCheck = patternCosts.GetValueOrDefault(joltageMask, []); foreach (var (joltageLevels, cost) in patternsToCheck) { - var nextPattern = targetJoltages.Zip(joltageLevels, (jolts, level) => (jolts - level) / 2).ToArray(); - var resultNext = 2 * GetMinCountInternal(nextPattern, patternCosts, memory) + cost; - minCount = Math.Min(minCount, resultNext); + var nextTarget = targetJoltages.Zip(joltageLevels, (jolts, level) => (jolts - level) / 2).ToArray(); + var nextCount = 2 * GetMinCountInternal(nextTarget, patternCosts, memory) + cost; + minCount = Math.Min(minCount, nextCount); } memory[targetJoltages] = minCount; @@ -193,7 +114,7 @@ public class Day10 : IPuzzleSolver } } - private IEnumerable<(uint, (int[], int))> GetPatternCosts(int patternLength, int[][] buttons) + private IEnumerable<(uint mask, (int[] joltageLevels, int cost) data)> GetPatternCosts(int patternLength, int[][] buttons) { yield return (0,([..Enumerable.Repeat(0, patternLength)], 0)); for (var i = 1; i <= buttons.Length; i++) @@ -201,7 +122,7 @@ public class Day10 : IPuzzleSolver foreach (var buttonPattern in buttons.Combinations(i)) { var joltageLevels = GetJoltageLevelsForButtonPattern(patternLength, buttonPattern); - var mask = GetMask(joltageLevels, level => level % 2 == 1); + var mask = AsMask(joltageLevels, level => level % 2 == 1); yield return (mask,(joltageLevels, buttonPattern.Length)); } } @@ -223,8 +144,10 @@ public class Day10 : IPuzzleSolver } } - private static uint GetMask(int[] toBeMasked, Func maskPredicate) => - toBeMasked.Aggregate((uint)0L, (acc, val) => (acc << 1) | (uint)(maskPredicate(val) ? 1 : 0)); + private static uint AsMask(TValue[] toBeMasked, Func maskPredicate) => + toBeMasked.Length <= 32 + ? toBeMasked.Aggregate((uint)0L, (acc, val) => (acc << 1) | (uint)(maskPredicate(val) ? 1 : 0)) + : throw new DataException("Mask can only be 32 bits long"); private long GetMinCountButtonPressesForJoltagesZ3(int[] joltages, int[][] buttons) {