diff --git a/src/aoc2024/Day24.kt b/src/aoc2024/Day24.kt new file mode 100644 index 0000000..ecdc5ba --- /dev/null +++ b/src/aoc2024/Day24.kt @@ -0,0 +1,345 @@ +package aoc2024 + +import println +import readInput +import java.util.* +import kotlin.collections.ArrayDeque + +/* +--- Day 24: Crossed Wires --- +https://adventofcode.com/2024/day/24 +*/ +fun main() { + + val inlineTestInput = """ +x00: 1 +x01: 1 +x02: 1 +y00: 0 +y01: 1 +y02: 0 + +x00 AND y00 -> z00 +x01 XOR y01 -> z01 +x02 OR y02 -> z02 +""" + + val inlineTestInput2 = """ +x00: 1 +x01: 0 +x02: 1 +x03: 1 +x04: 0 +y00: 1 +y01: 1 +y02: 1 +y03: 1 +y04: 1 + +ntg XOR fgs -> mjb +y02 OR x01 -> tnw +kwq OR kpj -> z05 +x00 OR x03 -> fst +tgd XOR rvg -> z01 +vdt OR tnw -> bfw +bfw AND frj -> z10 +ffh OR nrd -> bqk +y00 AND y03 -> djm +y03 OR y00 -> psh +bqk OR frj -> z08 +tnw OR fst -> frj +gnj AND tgd -> z11 +bfw XOR mjb -> z00 +x03 OR x00 -> vdt +gnj AND wpb -> z02 +x04 AND y00 -> kjc +djm OR pbm -> qhw +nrd AND vdt -> hwm +kjc AND fst -> rvg +y04 OR y02 -> fgs +y01 AND x02 -> pbm +ntg OR kjc -> kwq +psh XOR fgs -> tgd +qhw XOR tgd -> z09 +pbm OR djm -> kpj +x03 XOR y03 -> ffh +x00 XOR y04 -> ntg +bfw OR bqk -> z06 +nrd XOR fgs -> wpb +frj XOR qhw -> z04 +bqk OR frj -> z07 +y03 OR x01 -> nrd +hwm AND bqk -> z03 +tgd XOR rvg -> z12 +tnw OR pbm -> gnj +""" + + data class Node(val n1: String, val n2: String, val op: Int, var target: String, var val1: Boolean? = null, var val2: Boolean? = null) { + fun output() = when (op) { + 0 -> val1!! || val2!! + 1 -> val1!! && val2!! + 2 -> val1!! != val2!! + else -> false + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Node + + return target == other.target + } + + override fun hashCode(): Int { + return target.hashCode() + } + } + + val nodes = ArrayList() + val nodesWaiting = HashMap>() + var satisfiedNodes = ArrayList() + val zTargets = TreeSet() + val zState = HashMap() + val targetParent = HashMap() + val initialStates = HashMap() + val zInvolvedNodes = HashMap>() + val random = kotlin.random.Random(1337) + + fun readCircuit(input: List) { + nodes.clear() + nodesWaiting.clear() + satisfiedNodes.clear() + zTargets.clear() + zState.clear() + targetParent.clear() + initialStates.clear() + var i = 0 + while (input[i++].isNotEmpty()) { + val (n, st) = input[i - 1].split(": ") + initialStates[n] = (st == "1") + } + while (i < input.size) { + val (n1, op, n2, _, target) = input[i++].split(" ") + val opCode = when (op) { + "OR" -> 0 + "AND" -> 1 + "XOR" -> 2 + else -> throw IllegalStateException() + } + if (target.startsWith("z")) { + zTargets.add(target) + } + val node = Node(n1, n2, opCode, target) + nodes.add(node) + targetParent[target] = node + } + } + + fun outputState(target: String, output: Boolean, newSatisfiedNodes: MutableCollection) { + if (target.startsWith("z")) { + zState[target] = output + } + val targetList = nodesWaiting[target] ?: return + while (targetList.isNotEmpty()) { + val tNode = targetList.removeFirst() + if (tNode.n1 == target) { + tNode.val1 = output + if (tNode.val2 != null) { + newSatisfiedNodes.add(tNode) + } + } else { + tNode.val2 = output + if (tNode.val1 != null) { + newSatisfiedNodes.add(tNode) + } + } + } + } + + fun runCircuit(): Boolean { + while (zState.size != zTargets.size) { + if (satisfiedNodes.isEmpty()) { + return false + } + val newSatisfiedNodes = ArrayList() + for (node in satisfiedNodes) { + val output = node.output() + node.val1 = null + node.val2 = null + nodesWaiting.getOrPut(node.n1) { ArrayList() }.add(node) + nodesWaiting.getOrPut(node.n2) { ArrayList() }.add(node) + + outputState(node.target, output, newSatisfiedNodes) + } + satisfiedNodes = newSatisfiedNodes + } + return true + } + + fun reset() { + satisfiedNodes.clear() + nodesWaiting.clear() + zState.clear() + for (node in nodes) { + node.val1 = null + node.val2 = null + nodesWaiting.getOrPut(node.n1) { ArrayDeque() }.add(node) + nodesWaiting.getOrPut(node.n2) { ArrayDeque() }.add(node) + } + } + + fun setXY(n: String, value: Long) { + var v = value + for (i in 0..zTargets.size - 2) { + outputState("%s%02d".format(n, i), (v and 1L) != 0L, satisfiedNodes) + v = v shr 1 + } + } + + fun readZ(): Long { + var result = 0L + for (i in zTargets.reversed()) { + result *= 2 + if (zState[i] == true) result++ + } + + return result + } + + fun calcInvolvedNodes() { + zInvolvedNodes.clear() + for (target in zTargets) { + val set = HashSet() + val queue = ArrayDeque() + queue.add(targetParent[target]!!) + while (queue.isNotEmpty()) { + val node = queue.removeFirst() + set.add(node) + val t1 = targetParent[node.n1] + val t2 = targetParent[node.n2] + if (t1 != null && !set.contains(t1)) queue.add(t1) + if (t2 != null && !set.contains(t2)) queue.add(t2) + } + zInvolvedNodes[target] = set + } + } + + fun swapOutputs(t1: String, t2: String) { + val n1 = targetParent[t1]!! + val n2 = targetParent[t2]!! + n1.target = t2 + n2.target = t1 + targetParent[t1] = n2 + targetParent[t2] = n1 + } + + fun part1(input: List): Long { + readCircuit(input) + reset() + initialStates.forEach { outputState(it.key, it.value, satisfiedNodes) } + runCircuit() + + return readZ() + } + + fun checkIt(x: Long, y: Long, sum: Long, mask: Long = -1L): Boolean { + reset() + setXY("x", x) + setXY("y", y) + return runCircuit() && (readZ() and mask) == sum + } + + fun countGoodBits(startbit: Int): Int { + val inv = (1L shl (zTargets.size - 1)) - 1 + for (b in startbit..zTargets.size - 2) { + val tv = 1L shl b + val tvi = (1L shl (b + 1)) - 1L + if (!(checkIt(tv, 0L, tv) && + checkIt(tvi, 0L, tvi) && + checkIt(0L, tv, tv) && + checkIt(0L, tvi, tvi) && + checkIt(tv, tv, 2 * tv) && + checkIt(tvi, tvi, 2 * tvi) && + checkIt(inv - tvi, 0L, 0L, tvi) && + checkIt(0L, inv - tvi, 0L, tvi) + ) + ) { + return b + } + for (i in 0..(1L shl ((b - 8).coerceIn(1..6)))) { + val rx = random.nextLong() and tvi + val ry = random.nextLong() and tvi + if (!checkIt(rx, ry, rx + ry, mask = tvi * 2 + 1)) return b + } + } + return zTargets.size + } + + fun part2(input: List): String { + readCircuit(input) + calcInvolvedNodes() + + val goodGates = HashSet() + + val swaps = ArrayList>() + + for (b in 0..zTargets.size - 3) { + val zb0 = zInvolvedNodes["z%02d".format(b)]!! + val zb1 = zInvolvedNodes["z%02d".format(b + 1)]!! + val zb2 = zInvolvedNodes["z%02d".format(b + 2)]!! + + val tv = 1L shl b + val tvi = (1L shl (b + 1)) - 1L + val good = checkIt(tv, 0L, tv) && + checkIt(tvi, 0L, tvi) && + checkIt(0L, tv, tv) && + checkIt(0L, tvi, tvi) && + checkIt(tv, tv, 2 * tv) && + checkIt(tvi, tvi, 2 * tvi) + if (good) { + goodGates.addAll(zb0) + //goodGates.addAll(zb1) + } else { + println("Bit error at $b") + val badList = zb0.union(zb1).union(zb2).minus(goodGates).map { it.target }.toList() + var swapped = false + out@ for (p1 in badList.indices) { + for (p2 in p1 + 1..badList.lastIndex) { + swapOutputs(badList[p1], badList[p2]) + val goodBits = countGoodBits(b) + if (goodBits > b + 1) { + println("**** Successfully $goodBits swapped ${badList[p1]} with ${badList[p2]}") + swapped = true + swaps.add(badList[p1] to badList[p2]) + break@out + } else { + swapOutputs(badList[p1], badList[p2]) + } + } + } + if (!swapped) throw IllegalStateException("Sob!") + calcInvolvedNodes() + } + } + return swaps.flatMap { listOf(it.first, it.second) }.sorted().joinToString(",") + } + + // test if implementation meets criteria from the description, like: + val testInput = inlineTestInput.trim().reader().readLines() + val testInput2 = inlineTestInput2.trim().reader().readLines() + //val testInput = readInput("aoc2024/Day24_test") + val testInputPart1Result = part1(testInput) + println("Part 1 Test: $testInputPart1Result") + val testInputPart1Result2 = part1(testInput2) + println("Part 1 Test 2: $testInputPart1Result2") +// val testInputPart2Result = part2(testInput2) +// println("Part 2 Test: $testInputPart2Result") + check(testInputPart1Result == 4L) + check(testInputPart1Result2 == 2024L) + //check(testInputPart2Result == "z00,z01,z02,z05") + + val input = readInput("aoc2024/Day24") + part1(input).println() + part2(input).println() +}