370 lines
12 KiB
C#
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;
|
|
}
|
|
} |