Files
AdventOfCode/Days/Day24.cs
2025-01-24 10:19:13 +01:00

370 lines
12 KiB
C#

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<string>();
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<string> 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<string>? 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<string, int> Signals, List<LogicGate> Gates, HashSet<string> ZGates) ParseInput()
{
var readingSignals = true;
var signals = new Dictionary<string, int>();
var gates = new List<LogicGate>();
var zGates = new HashSet<string>();
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<Operator>(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
/// <summary>
/// Heap's algorithm to find all permutations. Non recursive, more efficient.
/// </summary>
/// <param name="items">Items to permute in each possible ways</param>
/// <param name="funcExecuteAndTellIfShouldStop"></param>
/// <returns>Return true if cancelled</returns>
private static bool ForAllPermutation<T>(T[] items, Func<T[], bool> 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;
}
/// <summary>
/// Swap 2 elements of same type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="a"></param>
/// <param name="b"></param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static void Swap<T>(ref T a, ref T b)
{
T temp = a;
a = b;
b = temp;
}
}