262 lines
9.6 KiB
C#
262 lines
9.6 KiB
C#
using System.Data;
|
|
using AdvenOfCode.Contracts;
|
|
using AdventOfCode.Extensions;
|
|
using AdventOfCode.HelperClasses;
|
|
using Microsoft.Z3;
|
|
|
|
namespace AoC_2025;
|
|
|
|
public class Day10 : IPuzzleSolver<long>
|
|
{
|
|
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)
|
|
{
|
|
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.Select(ixlamp => ixlamp.Item == '#').ToArray())
|
|
.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())
|
|
.ToArray())
|
|
.ToArray();
|
|
var joltages = puzzleInput
|
|
.Select(input => input.joltages)
|
|
.Select(joltage => joltage
|
|
.Split(',', StringSplitOptions.RemoveEmptyEntries)
|
|
.Select(jolt => int.Parse(jolt))
|
|
.ToArray())
|
|
.ToArray();
|
|
var res = lamps
|
|
.Zip(buttons, (lamp, button) => (lamp, button))
|
|
.Zip(joltages, (bl, joltage) => (bl.lamp, bl.button, joltage))
|
|
.ToArray();
|
|
return res;
|
|
}
|
|
|
|
public long SolvePart1(string pathToPuzzleInput)
|
|
{
|
|
var instructions = ParsePuzzleInput(pathToPuzzleInput);
|
|
var minCounts = instructions
|
|
.Select(ins => GetMinCountButtonPressesForLamps(ins.lamps, ins.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 minCounts = instructions
|
|
.Select(instruction => GetMinCountButtonPressesForJoltageLevels(instruction.joltages, instruction.buttons))
|
|
.ToArray();
|
|
return minCounts.Sum();
|
|
}
|
|
|
|
// not proud of you
|
|
public long SolvePart2Z3(string pathToPuzzleInput)
|
|
{
|
|
var instructions = ParsePuzzleInputUnoptimized(pathToPuzzleInput);
|
|
var minCounts = instructions
|
|
.Select(ins => GetMinCountButtonPressesForJoltagesZ3(ins.joltages, ins.buttons))
|
|
.ToArray();
|
|
return minCounts.Sum();
|
|
}
|
|
|
|
private long GetMinCountButtonPressesForLamps(uint lamps, uint[] buttons)
|
|
{
|
|
var presses = 0L;
|
|
HashSet<uint> 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<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)
|
|
{
|
|
var patternCosts = GetPatternCosts(joltages.Length, buttons).ToArray();
|
|
var res = GetMinCountInternal(joltages, patternCosts, new Dictionary<int[], long>(new IntArrayEqualityComparer()));
|
|
return res;
|
|
|
|
static long GetMinCountInternal(int[] targetJoltages, (int[] joltages, int cots)[] patternCosts, Dictionary<int[], long> memory)
|
|
{
|
|
if (targetJoltages.All(j => j == 0)) return 0;
|
|
if (memory.TryGetValue(targetJoltages, out var knownResult)) return knownResult;
|
|
|
|
var minCount = 1_000_000_000L;
|
|
var patternsToCheck = patternCosts
|
|
.Where(pc =>
|
|
pc.joltages
|
|
.Zip(targetJoltages, (pJolt, tJolt) => (pJolt % 2) == (tJolt % 2))
|
|
.All(isEqual => isEqual))
|
|
.ToArray();
|
|
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;
|
|
if (resultNext < minCount)
|
|
{
|
|
minCount = resultNext;
|
|
}
|
|
}
|
|
|
|
memory[targetJoltages] = minCount;
|
|
return minCount;
|
|
}
|
|
}
|
|
|
|
private IEnumerable<(int[], int)> GetPatternCosts(int patternLength, int[][] buttons)
|
|
{
|
|
yield return ([..Enumerable.Repeat(0, patternLength)], 0);
|
|
for (var i = 1; i <= buttons.Length; i++)
|
|
{
|
|
foreach (var buttonPattern in buttons.Combinations(i))
|
|
{
|
|
var joltageLevels = GetJoltageLevelsForButtonPattern(patternLength, buttonPattern);
|
|
yield return (joltageLevels, buttonPattern.Length);
|
|
}
|
|
}
|
|
|
|
yield break;
|
|
|
|
static int[] GetJoltageLevelsForButtonPattern(int countLamps, int[][] buttonPattern)
|
|
{
|
|
var joltagesProduced = Enumerable.Repeat(0, countLamps).ToArray();
|
|
foreach (var button in buttonPattern)
|
|
{
|
|
foreach (var index in button)
|
|
{
|
|
joltagesProduced[index]++;
|
|
}
|
|
}
|
|
|
|
return joltagesProduced;
|
|
}
|
|
}
|
|
|
|
private long GetMinCountButtonPressesForJoltagesZ3(int[] joltages, int[][] buttons)
|
|
{
|
|
using var context = new Context();
|
|
var optimize = context.MkOptimize();
|
|
var variables = Enumerable.Range(0, buttons.Length)
|
|
.Select(ix => context.MkIntConst($"n{ix}"))
|
|
.ToArray();
|
|
foreach (var variable in variables)
|
|
{
|
|
optimize.Add(variable >= 0);
|
|
}
|
|
|
|
var zero = context.MkInt(0);
|
|
foreach (var (i, joltage) in joltages.Index())
|
|
{
|
|
var equation = zero + zero;
|
|
|
|
foreach (var (b, button) in buttons.Index())
|
|
{
|
|
if(button.Contains(i))
|
|
equation += variables[b];
|
|
}
|
|
|
|
optimize.Add(context.MkEq(equation, context.MkInt(joltage)));
|
|
}
|
|
var sumVariables = variables.Aggregate(zero + zero, (current, variable) => current + variable);
|
|
|
|
optimize.MkMinimize(sumVariables);
|
|
optimize.Check();
|
|
var answerExpression = optimize.Model.Eval(sumVariables);
|
|
var answer = answerExpression is IntNum number ? number.Int64 : 0;
|
|
return answer;
|
|
}
|
|
} |