Day 24. Solution earlier than working algorithm that does not require manual steps.

This commit is contained in:
Chris Hodges 2024-12-24 14:23:31 +01:00
parent e00e002cd6
commit e7d733846f

345
src/aoc2024/Day24.kt Normal file
View File

@ -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<Node>()
val nodesWaiting = HashMap<String, MutableList<Node>>()
var satisfiedNodes = ArrayList<Node>()
val zTargets = TreeSet<String>()
val zState = HashMap<String, Boolean>()
val targetParent = HashMap<String, Node>()
val initialStates = HashMap<String, Boolean>()
val zInvolvedNodes = HashMap<String, MutableSet<Node>>()
val random = kotlin.random.Random(1337)
fun readCircuit(input: List<String>) {
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<Node>) {
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<Node>()
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<Node>()
val queue = ArrayDeque<Node>()
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<String>): 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>): String {
readCircuit(input)
calcInvolvedNodes()
val goodGates = HashSet<Node>()
val swaps = ArrayList<Pair<String, String>>()
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()
}