Refactor Day10 to remove unoptimized methods, simplify input parsing, and improve readability and maintainability of lamp and joltage logic by consolidating bitmask logic
This commit is contained in:
parent
1873ce8e66
commit
f4b5111fa0
|
|
@ -23,22 +23,6 @@ public class Day10Test
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Part01_Prod_equals_500()
|
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);
|
var actual = _sut.SolvePart1(ProdInputPath);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,41 +8,7 @@ namespace AoC_2025;
|
||||||
|
|
||||||
public class Day10 : IPuzzleSolver<long>
|
public class Day10 : IPuzzleSolver<long>
|
||||||
{
|
{
|
||||||
private (uint lamps, uint[] buttons)[] ParsePuzzleInput(string path)
|
private (bool[] lamps, int[][] buttons, int[] joltages)[] 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)
|
|
||||||
{
|
{
|
||||||
var puzzleInput = File.ReadAllLines(path)
|
var puzzleInput = File.ReadAllLines(path)
|
||||||
.Where(line => !string.IsNullOrWhiteSpace(line))
|
.Where(line => !string.IsNullOrWhiteSpace(line))
|
||||||
|
|
@ -65,7 +31,7 @@ public class Day10 : IPuzzleSolver<long>
|
||||||
.Select(buttons => buttons
|
.Select(buttons => buttons
|
||||||
.Select(button => button[1..^1]
|
.Select(button => button[1..^1]
|
||||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||||
.Select(lampIndex => int.Parse(lampIndex))
|
.Select(int.Parse)
|
||||||
.ToArray())
|
.ToArray())
|
||||||
.ToArray())
|
.ToArray())
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
@ -73,7 +39,7 @@ public class Day10 : IPuzzleSolver<long>
|
||||||
.Select(input => input.joltages)
|
.Select(input => input.joltages)
|
||||||
.Select(joltage => joltage
|
.Select(joltage => joltage
|
||||||
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
||||||
.Select(jolt => int.Parse(jolt))
|
.Select(int.Parse)
|
||||||
.ToArray())
|
.ToArray())
|
||||||
.ToArray();
|
.ToArray();
|
||||||
var res = lamps
|
var res = lamps
|
||||||
|
|
@ -87,26 +53,14 @@ public class Day10 : IPuzzleSolver<long>
|
||||||
{
|
{
|
||||||
var instructions = ParsePuzzleInput(pathToPuzzleInput);
|
var instructions = ParsePuzzleInput(pathToPuzzleInput);
|
||||||
var minCounts = instructions
|
var minCounts = instructions
|
||||||
.Select(ins => GetMinCountButtonPressesForLamps(ins.lamps, ins.buttons))
|
.Select(instruction => GetMinCountButtonPressesForLamps(instruction.lamps, instruction.buttons))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
return minCounts.Sum();
|
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)
|
public long SolvePart2(string pathToPuzzleInput)
|
||||||
{
|
{
|
||||||
var instructions = ParsePuzzleInputUnoptimized(pathToPuzzleInput);
|
var instructions = ParsePuzzleInput(pathToPuzzleInput);
|
||||||
var minCounts = instructions
|
var minCounts = instructions
|
||||||
.Select(instruction => GetMinCountButtonPressesForJoltageLevels(instruction.joltages, instruction.buttons))
|
.Select(instruction => GetMinCountButtonPressesForJoltageLevels(instruction.joltages, instruction.buttons))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
|
|
@ -116,76 +70,43 @@ public class Day10 : IPuzzleSolver<long>
|
||||||
// not proud of you
|
// not proud of you
|
||||||
public long SolvePart2Z3(string pathToPuzzleInput)
|
public long SolvePart2Z3(string pathToPuzzleInput)
|
||||||
{
|
{
|
||||||
var instructions = ParsePuzzleInputUnoptimized(pathToPuzzleInput);
|
var instructions = ParsePuzzleInput(pathToPuzzleInput);
|
||||||
var minCounts = instructions
|
var minCounts = instructions
|
||||||
.Select(ins => GetMinCountButtonPressesForJoltagesZ3(ins.joltages, ins.buttons))
|
.Select(ins => GetMinCountButtonPressesForJoltagesZ3(ins.joltages, ins.buttons))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
return minCounts.Sum();
|
return minCounts.Sum();
|
||||||
}
|
}
|
||||||
|
|
||||||
private long GetMinCountButtonPressesForLamps(uint lamps, uint[] buttons)
|
private long GetMinCountButtonPressesForLamps(bool[] lamps, int[][] buttons)
|
||||||
{
|
{
|
||||||
var presses = 0L;
|
var lampMask = AsMask(lamps, x => x);
|
||||||
HashSet<uint> lampStates = [lamps];
|
var pattern = GetPatternCosts(lamps.Length, buttons)
|
||||||
while (lampStates.All(state => state > 0) && presses < 100)
|
.First(pattern => pattern.mask == lampMask);
|
||||||
{
|
return pattern.data.cost;
|
||||||
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<int> 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private long GetMinCountButtonPressesForJoltageLevels(int[] joltages, int[][] buttons)
|
private long GetMinCountButtonPressesForJoltageLevels(int[] joltages, int[][] buttons)
|
||||||
{
|
{
|
||||||
var patternCosts = GetPatternCosts(joltages.Length, buttons)
|
var patternCosts = GetPatternCosts(joltages.Length, buttons)
|
||||||
.GroupBy(x => x.Item1)
|
.GroupBy(x => x.mask)
|
||||||
.ToDictionary(x => x.Key, x => x.Select(y => y.Item2).ToArray());
|
.ToDictionary(x => x.Key, x => x.Select(y => y.data).ToArray());
|
||||||
var res = GetMinCountInternal(joltages, patternCosts, new Dictionary<int[], long>(new IntArrayEqualityComparer()));
|
var res = GetMinCountInternal(joltages, patternCosts, new Dictionary<int[], long>(new IntArrayEqualityComparer()));
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
static long GetMinCountInternal(int[] targetJoltages, Dictionary<uint, (int[] joltages, int cots)[]> patternCosts, Dictionary<int[], long> memory)
|
static long GetMinCountInternal(int[] targetJoltages, Dictionary<uint, (int[] joltages, int cots)[]> patternCosts, Dictionary<int[], long> 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 (targetJoltages.All(j => j == 0)) return 0;
|
||||||
if (memory.TryGetValue(targetJoltages, out var knownResult)) return knownResult;
|
if (memory.TryGetValue(targetJoltages, out var knownResult)) return knownResult;
|
||||||
|
|
||||||
var minCount = 1_000_000_000L;
|
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, []);
|
var patternsToCheck = patternCosts.GetValueOrDefault(joltageMask, []);
|
||||||
foreach (var (joltageLevels, cost) in patternsToCheck)
|
foreach (var (joltageLevels, cost) in patternsToCheck)
|
||||||
{
|
{
|
||||||
var nextPattern = targetJoltages.Zip(joltageLevels, (jolts, level) => (jolts - level) / 2).ToArray();
|
var nextTarget = targetJoltages.Zip(joltageLevels, (jolts, level) => (jolts - level) / 2).ToArray();
|
||||||
var resultNext = 2 * GetMinCountInternal(nextPattern, patternCosts, memory) + cost;
|
var nextCount = 2 * GetMinCountInternal(nextTarget, patternCosts, memory) + cost;
|
||||||
minCount = Math.Min(minCount, resultNext);
|
minCount = Math.Min(minCount, nextCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
memory[targetJoltages] = minCount;
|
memory[targetJoltages] = minCount;
|
||||||
|
|
@ -193,7 +114,7 @@ public class Day10 : IPuzzleSolver<long>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
yield return (0,([..Enumerable.Repeat(0, patternLength)], 0));
|
||||||
for (var i = 1; i <= buttons.Length; i++)
|
for (var i = 1; i <= buttons.Length; i++)
|
||||||
|
|
@ -201,7 +122,7 @@ public class Day10 : IPuzzleSolver<long>
|
||||||
foreach (var buttonPattern in buttons.Combinations(i))
|
foreach (var buttonPattern in buttons.Combinations(i))
|
||||||
{
|
{
|
||||||
var joltageLevels = GetJoltageLevelsForButtonPattern(patternLength, buttonPattern);
|
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));
|
yield return (mask,(joltageLevels, buttonPattern.Length));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -223,8 +144,10 @@ public class Day10 : IPuzzleSolver<long>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static uint GetMask(int[] toBeMasked, Func<int, bool> maskPredicate) =>
|
private static uint AsMask<TValue>(TValue[] toBeMasked, Func<TValue, bool> maskPredicate) =>
|
||||||
toBeMasked.Aggregate((uint)0L, (acc, val) => (acc << 1) | (uint)(maskPredicate(val) ? 1 : 0));
|
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)
|
private long GetMinCountButtonPressesForJoltagesZ3(int[] joltages, int[][] buttons)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue