Skip to content
This repository has been archived by the owner on Jan 3, 2025. It is now read-only.

feat: day 24 #29

Merged
merged 4 commits into from
Dec 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions day24/deno.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"name": "@scope/day24",
"version": "0.1.0",
"exports": {
".": "./mod.ts"
}
}
10 changes: 10 additions & 0 deletions day24/example.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
x00: 1
x01: 1
x02: 1
y00: 0
y01: 1
y02: 0

x00 AND y00 -> z00
x01 XOR y01 -> z01
x02 OR y02 -> z02
346 changes: 346 additions & 0 deletions day24/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,346 @@
class Device {
input: [string, number][];
gates: [string, string, string, string][];

constructor(
input: [string, number][],
gates: [string, string, string, string][],
) {
this.input = input;
this.gates = gates;
}

compute(): bigint {
const values = new Map<string, number>();
const outputs = new Map<string, [string, string, string]>();

for (const [wire, value] of this.input) {
values.set(wire, value);
}

for (const [a, op, b, wire] of this.gates) {
outputs.set(wire, [a, op, b]);
}

const computeWire = (wire: string): number => {
if (!values.has(wire)) {
const [a, op, b] = outputs.get(wire)!;

const a_value = computeWire(a);
const b_value = computeWire(b);

switch (op) {
case "AND":
values.set(wire, a_value & b_value);
break;
case "OR":
values.set(wire, a_value | b_value);
break;
case "XOR":
values.set(wire, a_value ^ b_value);
break;
default:
break;
}
}
return values.get(wire)!;
};

return this.gates.map(([, , , wire]) => wire).filter((wire) =>
wire.startsWith("z")
).sort().reverse().map((wire) => computeWire(wire)).reduce(
(acc, value) => (acc << 1n) | BigInt(value),
0n,
);
}

swap(a: string, b: string): void {
for (let i = 0; i < this.gates.length; i++) {
if (this.gates[i][3] === a) {
this.gates[i][3] = b;
} else if (this.gates[i][3] === b) {
this.gates[i][3] = a;
}
}
}

maxZVar(): number {
return Number(
this.gates.map(([, , , wire]) => wire).filter((wire) =>
wire.startsWith("z")
).sort().reverse()[0].slice(1),
);
}

isValid(): boolean {
const visited = new Set<string>();

const dfs = (wire: string): boolean => {
if (visited.has(wire)) {
return false;
}

visited.add(wire);

const [a, , b] = this.gates.find(([, , , w]) => w === wire)!;

if (!dfs(a) || !dfs(b)) return false;

visited.delete(wire);

return true;
};

return dfs(`z${this.maxZVar()}`);
}

getAllDependencies(): Map<string, Set<string>> {
const dependencies = new Map<string, Set<string>>();

for (const [a, , b, wire] of this.gates) {
if (!dependencies.has(a)) {
dependencies.set(a, new Set());
}
if (!dependencies.has(b)) {
dependencies.set(b, new Set());
}
if (!dependencies.has(wire)) {
dependencies.set(wire, new Set());
}

dependencies.get(wire)!.add(a);
dependencies.get(wire)!.add(b);
dependencies.get(wire)!.add(wire);
}

// find the closure of the dependencies
let changed = true;
while (changed) {
changed = false;
for (const [, deps] of dependencies) {
for (const dep of deps) {
for (const depDep of dependencies.get(dep)!) {
if (!deps.has(depDep)) {
deps.add(depDep);
changed = true;
}
}
}
}
}

return dependencies;
}

getIncrementalDependencies(): Map<number, Set<string>> {
const allDependencies = this.getAllDependencies();

const incrementalDependencies = new Map<number, Set<string>>();

let till = new Set();

const maxZ = this.maxZVar();

for (let zn = 0; zn <= maxZ; zn++) {
const z = `z${zn.toString().padStart(2, "0")}`;
const current = allDependencies.get(z)!;
incrementalDependencies.set(
zn,
new Set(current.values().filter((dep) => !till.has(dep))),
);
till = new Set([...till, ...current]);
}

return incrementalDependencies;
}

getGatesAtEachLevel(): Map<number, [string, string, string, string][]> {
const levels = new Map<number, [string, string, string, string][]>();

const incrementalDependencies = this.getIncrementalDependencies();

for (const [a, op, b, w] of this.gates) {
const [level] = incrementalDependencies.entries().find(([, wires]) =>
wires.has(w)
)!;
if (!levels.has(level)) {
levels.set(level, []);
}
levels.get(level)!.push([a, op, b, w]);
}

return levels;
}

expectedGates(
level: number,
maxLevel: number,
): { and: number; or: number; xor: number } {
switch (level) {
case 0:
return { and: 0, or: 0, xor: 1 };
case 1:
return { and: 1, or: 0, xor: 2 };
case maxLevel:
return { and: 2, or: 1, xor: 0 };
default:
return { and: 2, or: 1, xor: 2 };
}
}

validLevel(
level: number,
maxLevel: number,
gates: [string, string, string, string][],
): boolean {
const expected = this.expectedGates(level, maxLevel);
const { and, or, xor } = gates.reduce(
(acc, [, op]) => {
switch (op) {
case "AND":
acc.and++;
break;
case "OR":
acc.or++;
break;
case "XOR":
acc.xor++;
break;
default:
break;
}
return acc;
},
{ and: 0, or: 0, xor: 0 },
);

// there must be one x${level-1} AND x${level-1} gate.
if (expected.and > 0 && expected.and === and) {
const levelKey = (level - 1).toString().padStart(2, "0");
if (
gates.filter(([a, op, b]) =>
op === "AND" &&
((a === `x${levelKey}` && b === `y${levelKey}`) ||
(b === `x${levelKey}` && a === `y${levelKey}`))
).length === 0
) {
return false;
}
}

// there must be one x${level} XOR x${level} gate.
if (expected.xor > 0 && expected.xor === xor) {
const levelKey = level.toString().padStart(2, "0");
if (
gates.filter(([a, op, b]) =>
op === "XOR" &&
((a === `x${levelKey}` && b === `y${levelKey}`) ||
(b === `x${levelKey}` && a === `y${levelKey}`))
).length === 0
) {
return false;
}
}

// there must be one XOR with z${level} gate.
if (expected.xor > 0 && expected.xor === xor) {
const levelKey = level.toString().padStart(2, "0");
if (
gates.filter(([, op, , w]) => op === "XOR" && w === `z${levelKey}`)
.length === 0
) {
return false;
}
}

return and === expected.and && or === expected.or && xor === expected.xor;
}

findBuggyLevels(): number[] {
const levels = this.getGatesAtEachLevel();
const buggyLevels = [];

const maxZ = this.maxZVar();

for (const [level, gates] of levels) {
if (!this.validLevel(level, maxZ, gates)) {
buggyLevels.push(level);
}
}

return buggyLevels.sort();
}

findSwapsAtLevel(levels: number[]): [string, string][] {
const gatesAtlevels = this.getGatesAtEachLevel();
const swaps = [];
const swappedLevels = new Set<number>();

let nBuggyLevels = levels.length;

for (let i = 0; i < levels.length; i++) {
if (swappedLevels.has(levels[i])) continue;
const gatesI = gatesAtlevels.get(levels[i])!;
rnbguy marked this conversation as resolved.
Show resolved Hide resolved
for (let j = i + 1; j < levels.length; j++) {
if (swappedLevels.has(levels[j])) continue;
const gatesJ = gatesAtlevels.get(levels[j])!;
for (
const [gateI, gateJ] of gatesI.flatMap((a1) =>
gatesJ.map((a2) => [a1, a2])
)
) {
this.swap(gateI[3], gateJ[3]);
const currentBuggyLevels = this.findBuggyLevels();
if (nBuggyLevels == currentBuggyLevels.length + 2) {
nBuggyLevels = currentBuggyLevels.length;
swaps.push([gateI[3], gateJ[3]] as [string, string]);
swappedLevels.add(levels[i]);
swappedLevels.add(levels[j]);
break;
} else {
this.swap(gateI[3], gateJ[3]);
}
}
}
}

return swaps;
}

findSwaps(): [string, string][] {
const buggyLevels = this.findBuggyLevels();
return this.findSwapsAtLevel(buggyLevels);
}
}

export function parse(
data: string,
): Device {
const [wires, gates] = data.trim().split("\n\n") as [string, string];
return new Device(
wires.split("\n").map((line) => {
const [wire, value] = line.split(": ");
return [wire, Number(value)];
}),
gates.split("\n").map((line) => {
const [lhs, rhs] = line.split(" -> ");
const [a, op, b] = lhs.split(" ");
return [a, op, b, rhs];
}),
);
}

export function solve1(data: Device): bigint {
return data.compute();
}

export function solve2(data: Device): string {
return data.findSwaps().flat().sort().join(",");
}

if (import.meta.main) {
const dataPath = new URL("input.txt", import.meta.url).pathname;
const data = parse(await Deno.readTextFile(dataPath));
console.log(solve1(data));
console.log(solve2(data));
}
9 changes: 9 additions & 0 deletions day24/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { assertEquals } from "@std/assert";
import { parse, solve1, solve2 } from "./mod.ts";

Deno.test(async function testExample() {
const dataPath = new URL("example.txt", import.meta.url).pathname;
const data = parse(await Deno.readTextFile(dataPath));
assertEquals(solve1(data), 4n);
assertEquals(solve2(data), "");
});
Loading