The penultimate puzzle. I'm assuming this is going to be difficult, tricky, or sneaky somehow.
The write-up seems a bit complicated, but in essense is just a very basic circuit diagram, limited only to a few types of logic gates. Doesn't seem too difficult.
private Dictionary<string, bool> wires = new Dictionary<string, bool>();
private List<Gate> gates = new List<Gate>();
public object PartOne()
{
var toProcess = new Queue<Gate>(gates);
while (toProcess.TryDequeue(out var gate))
{
if (wires.ContainsKey(gate.left) && wires.ContainsKey(gate.right))
{
wires[gate.output] = gate.type switch
{
"AND" => wires[gate.left] && wires[gate.right],
"XOR" => wires[gate.left] ^ wires[gate.right],
_ => wires[gate.left] || wires[gate.right]
};
}
else
{
toProcess.Enqueue(gate);
}
}
var wireVals = wires.Where(k => k.Key[0] == 'z')
.ToList()
.OrderByDescending(k => k.Key)
.Select(k => k.Value ? '1' : '0');
return Convert.ToInt64(string.Join("", wireVals), 2);
}
Easy enough
Not easy enough. I'm not even sure how to approach this one properly. While running the simulation of the output is pretty quick (10ms), the sheer number of possible swaps alone is enormous.
Maybe I should try just solving for one gate instead - and keep note of any swap that appears closer to the answer by one bit? But if it's an unusual kind of connection then it may require multiple swaps to be more correct. If I tried something that would "score" correctness to be more... well... correct, then it can help, but it would still take ages to run.
I should think more logically about what this is. It's intended to be an adder, and they explicitly say that z00
is the least significant bit. Which would mean that z00 = x00 + y00
, z01 = x01 + y01 + c00
, where c00
is the carry for z00
. If I know this structure, I can start finding ones that are incorrect.
private int GetFirstIncorrect(List<Gate> gates)
{
for (int i = 0; i <= 44; i++)
{
var ancestors = GetAncestors(gates, $"z{i:00}");
for (int j = 0; j <= i; j++)
{
if (!ancestors.Remove($"x{j:00}")) return i;
if (!ancestors.Remove($"y{j:00}")) return i;
}
if (ancestors.Count != 0) return i;
}
return -1;
}
private List<string> GetAncestors(List<Gate> gates, string toFind)
{
var found = new HashSet<string>();
var queue = new Queue<string>();
queue.Enqueue(toFind);
while (queue.TryDequeue(out var wire))
{
var gate = gates.FirstOrDefault(g => g.output == wire, null);
if (gate == null) found.Add(wire);
else
{
queue.Enqueue(gate.left);
queue.Enqueue(gate.right);
}
}
return found.ToList();
}
That jives with what I get looking at it manually! Now I can try doing swaps until that number starts going up!
Spoilers: It didn't jive. It turns out I had a lot of other ideas that didn't quite work out; in the end I ended up just manually analyzing the output, using GraphViz for some help to vizualize it. From there, I more statically analyzed it, occasionally testing stuff out with different input values for X and Y to help spot check things. I will admit, I manually answered this, though only after a lot of attempts to automate it.
I suppose this is a lesson - sometimes, it's easier to eyeball it than get a machine to crunch away at a problem.