using System; using System.Collections; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.IO; using System.Linq; using AdvenOfCode.Contracts; using Microsoft.Z3; namespace AoC_2025; public class Day10 : IPuzzleSolver { private (uint lamps, uint[] 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 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; } 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)); 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 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; } } } private long GetMinCountButtonPressesForJoltageLevels(int[] joltages, int[][] buttons) { var patternCosts = GetPatternCosts(joltages.Length, buttons).ToArray(); var res = GetMinCountInternal(joltages, patternCosts, new Dictionary(new IntArrayEqualityComparer())); return res; static long GetMinCountInternal(int[] targetJoltages, (int[] joltages, int cots)[] patternCosts, Dictionary 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 (joltageLevel, buttonPresses) in patternsToCheck) { var nextPattern = targetJoltages.Zip(joltageLevel, (jolts, level) => (jolts - level) / 2).ToArray(); var resultNext = 2 * GetMinCountInternal(nextPattern, patternCosts, memory) + buttonPresses; 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 Combinations(buttons, 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; } private IEnumerable Combinations(IEnumerable values, int count) { var pool = values.ToArray(); var poolLength = pool.Length; if (count > poolLength) yield break; var indices = Range(0, count).ToArray(); yield return GetCombination(indices, pool); while (true) { var validIndex = false; var currentIndex = 0; foreach (var i in Range(0, count).Reverse()) { currentIndex = i; if (indices[i] != i + poolLength - count) { validIndex = true; break; } } if(!validIndex) yield break; indices[currentIndex] += 1; foreach (var j in Range(currentIndex + 1, count)) { Index ix = j - 1; indices[j] = indices[ix] + 1; } yield return GetCombination(indices, pool); } static TValue[] GetCombination(int[] innerIndices, TValue[] innerPool) => innerIndices.Select(i => innerPool[i]).ToArray(); static IEnumerable Range(int start, int end) { for (var i = start; i < end; i++) yield return i; } } } public class IntArrayEqualityComparer : IEqualityComparer { public bool Equals(int[]? x, int[]? y) { if (x is null || y is null || x.Length != y.Length) return false; return !x.Where((t, i) => t != y[i]).Any(); } public int GetHashCode(int[] obj) { var result = 17; foreach (var t in obj) { unchecked { result = result * 23 + t; } } return result; } }