using System.Collections.Frozen; using System.Numerics; using System.Runtime.CompilerServices; using System.Runtime.Intrinsics.X86; using System.Text; using Microsoft.CodeAnalysis; using Microsoft.VisualBasic; using Spectre.Console; namespace AdventOfCode.Days; public class Day24 : Day { public override int Number => 24; public override string Name => "Crossed Wires"; public override void RunPart1(bool display = true) { var (signals, gates, zGates) = ParseInput(); while (zGates.Count > 0) { foreach (var (inputLeft, inputRight, output, logicOperator) in gates) { if (!signals.TryGetValue(inputLeft, out var inputLeftValue) || !signals.TryGetValue(inputRight, out var inputRightValue)) { continue; } signals[output] = Compute(inputLeftValue, inputRightValue, logicOperator); if (output.StartsWith('z')) { zGates.Remove(output); } } } var zOutputValue = signals .Where(s => s.Key.StartsWith('z')) .Select(s => new { Index = int.Parse(s.Key[1..]), Value = (long)s.Value }) .Sum(s => s.Value << s.Index); if (display) { AnsiConsole.MarkupLine($"[green]Value output on z signal: [yellow]{zOutputValue}[/][/]"); } } public override void RunPart2(bool display = true) { var (initialSignals, initialGates, initialZGates) = ParseInput(); var xInputValue = initialSignals .Where(s => s.Key.StartsWith('x')) .Select(s => new { Index = int.Parse(s.Key[1..]), Value = (long)s.Value }) .Sum(s => s.Value << s.Index); var yInputValue = initialSignals .Where(s => s.Key.StartsWith('y')) .Select(s => new { Index = int.Parse(s.Key[1..]), Value = (long)s.Value }) .Sum(s => s.Value << s.Index); // Find wires that need to be changed const long initialZValue = 58740594706150L; var targetZValue = xInputValue + yInputValue; var difference = targetZValue ^ initialZValue; var possibleChange = new HashSet(); for (var i = 0; i < sizeof(long) * 8; i++) { if (((1 << i) & difference) != 0) { possibleChange.Add($"z{i:D2}"); } } // Find all dependencies int initialSize; do { initialSize = possibleChange.Count; foreach (var gate in initialGates) { if (possibleChange.Contains(gate.Output)) { possibleChange.Add(gate.InputLeft); possibleChange.Add(gate.InputRight); } } } while (possibleChange.Count != initialSize); var gatesToSwapIndices = initialGates .Index() .ToList() .Where(t => possibleChange.Contains(t.Item.Output)) .Select(t => t.Index) .ToList(); // Try out all possible permutations of concerned outputs List changedOutputs = []; for (var a = 0; a < gatesToSwapIndices.Count - 7; a++) { for (var b = a + 1; b < gatesToSwapIndices.Count - 6; b++) { for (var c = b + 1; c < gatesToSwapIndices.Count - 5; c++) { for (var d = c + 1; d < gatesToSwapIndices.Count - 4; d++) { for (var e = d + 1; e < gatesToSwapIndices.Count - 3; e++) { for (var f = e + 1; f < gatesToSwapIndices.Count - 2; f++) { for (var g = f + 1; g < gatesToSwapIndices.Count - 1; g++) { for (var h = g + 1; h < gatesToSwapIndices.Count; h++) { Console.WriteLine($"h: {h}"); ForAllPermutation([ gatesToSwapIndices[a], gatesToSwapIndices[b], gatesToSwapIndices[c], gatesToSwapIndices[d], gatesToSwapIndices[e], gatesToSwapIndices[f], gatesToSwapIndices[g], gatesToSwapIndices[h]], permutations => { if (CheckPermutations(permutations) is { } modifiedOutputs) { changedOutputs = modifiedOutputs; return true; } return false; }); } } } } } } } } var swappedOutputs = string.Join(',', changedOutputs.Order()); if (display) { AnsiConsole.MarkupLine($"[green]Swapped outputs: [yellow]{swappedOutputs}[/][/]"); } return; List? CheckPermutations(int[] permutations) { var gates = initialGates.ToArray(); var zGates = initialZGates.ToHashSet(); var signals = initialSignals.ToDictionary(); SwapOutput(ref gates[permutations[0]], ref gates[permutations[1]]); SwapOutput(ref gates[permutations[2]], ref gates[permutations[3]]); SwapOutput(ref gates[permutations[4]], ref gates[permutations[5]]); SwapOutput(ref gates[permutations[6]], ref gates[permutations[7]]); var zOutputValue = 0L; while (zGates.Count > 0) { // Avoid locks when result is not reachable (infinite loop) var resultsCount = signals.Count; foreach (var (inputLeft, inputRight, output, logicOperator) in gates) { if (signals.ContainsKey(output)) { continue; } if (!signals.TryGetValue(inputLeft, out var inputLeftValue) || !signals.TryGetValue(inputRight, out var inputRightValue)) { continue; } var computed = Compute(inputLeftValue, inputRightValue, logicOperator); signals[output] = computed; if (output.StartsWith('z')) { zGates.Remove(output); zOutputValue += (long)computed << int.Parse(output[1..]); } } // No new signal value computed, it's an infinite loop if (signals.Count == resultsCount) { return null; } } if (xInputValue + yInputValue == zOutputValue) { return permutations.Select(i => gates[i].Output).ToList(); } return null; } void SwapOutput(ref LogicGate first, ref LogicGate second) { var temp = first.Output; first = first with { Output = second.Output }; second = second with { Output = temp }; } } private int Compute(int inputLeftValue, int inputRightValue, Operator logicOperator) => logicOperator switch { Operator.And => inputLeftValue & inputRightValue, Operator.Or => inputLeftValue | inputRightValue, Operator.Xor => inputLeftValue ^ inputRightValue, _ => throw new ArgumentOutOfRangeException(nameof(logicOperator), logicOperator, null) }; private (Dictionary Signals, List Gates, HashSet ZGates) ParseInput() { var readingSignals = true; var signals = new Dictionary(); var gates = new List(); var zGates = new HashSet(); foreach (var line in Input.AsSpan().EnumerateLines()) { if (line.IsWhiteSpace()) { readingSignals = false; continue; } if (readingSignals) { var signalName = line[..line.IndexOf(':')]; var signalValue = int.Parse(line[(line.IndexOf(':') + 2)..]); signals[signalName.ToString()] = signalValue; } else { var split = line.Split(' '); split.MoveNext(); var inputLeft = line[split.Current]; split.MoveNext(); var logicOperator = Enum.Parse(line[split.Current], true); split.MoveNext(); var inputRight = line[split.Current]; split.MoveNext(); split.MoveNext(); var output = line[split.Current]; gates.Add(new LogicGate(inputLeft.ToString(), inputRight.ToString(), output.ToString(), logicOperator)); if (output.StartsWith('z')) { zGates.Add(output.ToString()); } } } return (signals, gates, zGates); } private record LogicGate(string InputLeft, string InputRight, string Output, Operator LogicOperator); private enum Operator { And, Or, Xor } // Source: https://stackoverflow.com/a/36634935 /// /// Heap's algorithm to find all permutations. Non recursive, more efficient. /// /// Items to permute in each possible ways /// /// Return true if cancelled private static bool ForAllPermutation(T[] items, Func funcExecuteAndTellIfShouldStop) { int countOfItem = items.Length; if (countOfItem <= 1) { return funcExecuteAndTellIfShouldStop(items); } var indexes = new int[countOfItem]; // Unnecessary. Thanks to NetManage for the advise // for (int i = 0; i < countOfItem; i++) // { // indexes[i] = 0; // } if (funcExecuteAndTellIfShouldStop(items)) { return true; } for (int i = 1; i < countOfItem;) { if (indexes[i] < i) { // On the web there is an implementation with a multiplication which should be less efficient. if ((i & 1) == 1) // if (i % 2 == 1) ... more efficient ??? At least the same. { Swap(ref items[i], ref items[indexes[i]]); } else { Swap(ref items[i], ref items[0]); } if (funcExecuteAndTellIfShouldStop(items)) { return true; } indexes[i]++; i = 1; } else { indexes[i++] = 0; } } return false; } /// /// Swap 2 elements of same type /// /// /// /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private static void Swap(ref T a, ref T b) { T temp = a; a = b; b = temp; } }