-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* manually solved * Initial programatic solution * Cleanup * udate readmes
- Loading branch information
Showing
5 changed files
with
321 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
283 changes: 283 additions & 0 deletions
283
src/main/kotlin/me/peckb/aoc/_2024/calendar/day24/Day24.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,283 @@ | ||
package me.peckb.aoc._2024.calendar.day24 | ||
|
||
import javax.inject.Inject | ||
import me.peckb.aoc.generators.InputGenerator.InputGeneratorFactory | ||
import java.util.LinkedList | ||
|
||
class Day24 @Inject constructor( | ||
private val generatorFactory: InputGeneratorFactory, | ||
) { | ||
fun partOne(filename: String) = generatorFactory.forFile(filename).read { input -> | ||
var readingBase = true | ||
|
||
val data = mutableMapOf<String, Lazy<Int>>() | ||
val zValues = mutableListOf<String>() | ||
|
||
input.forEach input@{ line -> | ||
if (line.isEmpty()) { | ||
readingBase = false | ||
return@input | ||
} | ||
if (readingBase) { | ||
line.split(": ").let { (key, value) -> data[key] = lazy { value.toInt() } } | ||
} else { | ||
line.split(" ").let { (first, operations, second, _, result) -> | ||
if (result.startsWith('z')) { zValues.add(result) } | ||
|
||
val op = findOperation(operations) | ||
data[result] = lazy { op(data[first]!!.value, data[second]!!.value) } | ||
} | ||
} | ||
} | ||
|
||
zValues.sortedDescending() | ||
.map { data[it]!!.value } | ||
.joinToString("") | ||
.toLong(2) | ||
} | ||
|
||
fun partTwo(filename: String) = generatorFactory.forFile(filename).read { input -> | ||
var readingBase = true | ||
val data = mutableMapOf<String, () -> Int>() | ||
val xValues = mutableListOf<String>() | ||
val yValues = mutableListOf<String>() | ||
val zValues = mutableListOf<String>() | ||
val wirings = mutableMapOf<String, Wiring>() | ||
|
||
input.forEach input@{ line -> | ||
if (line.isEmpty()) { | ||
readingBase = false | ||
return@input | ||
} | ||
if (readingBase) { | ||
line.split(": ").let { (key, value) -> | ||
if (key.startsWith('x')) { xValues.add(key) } | ||
if (key.startsWith('y')) { yValues.add(key) } | ||
data[key] = { value.toInt() } | ||
} | ||
} else { | ||
line.split(" ").let { (a, operation, b, _, result) -> | ||
if (result.startsWith('z')) { zValues.add(result) } | ||
|
||
val op = findOperation(operation) | ||
|
||
wirings[result] = Wiring(a, operation, b, result) | ||
|
||
data[result] = { op(data[a]!!(), data[b]!!()) } | ||
} | ||
} | ||
} | ||
|
||
val swaps = mutableMapOf<String, String>() | ||
|
||
fun swap(swap: Pair<String, String>) { | ||
val oldA = wirings[swap.first]!! | ||
val oldB = wirings[swap.second]!! | ||
|
||
val newB = oldB.copy(result = oldA.result) | ||
val newA = oldA.copy(result = oldB.result) | ||
|
||
wirings[swap.first] = newB | ||
wirings[swap.second] = newA | ||
|
||
val opA = findOperation(newA.op) | ||
val opB = findOperation(newB.op) | ||
|
||
data[newA.result] = { opA(data[newA.a]!!(), data[newA.b]!!()) } | ||
data[newB.result] = { opB(data[newB.a]!!(), data[newB.b]!!()) } | ||
|
||
swaps[swap.first] = swap.second | ||
swaps[swap.second] = swap.first | ||
} | ||
|
||
val correctWirings = mutableMapOf<String, List<Wiring>>() | ||
|
||
(0 .. 45).forEach loop@{ n -> | ||
val previousPreviousKey = "z${(n-2).toString().padStart(2, '0')}" | ||
val previousKey = "z${(n-1).toString().padStart(2, '0')}" | ||
val key = "z${n.toString().padStart(2, '0')}" | ||
when (n) { | ||
0 -> checkZero(wirings)?.also { swap(it) } | ||
1 -> checkOne(wirings)?.also { swap(it) } | ||
in (2..44) -> checkDefault( | ||
wirings = wirings, | ||
key = n to key, | ||
previousWirings = n - 1 to previousKey, | ||
previousPreviousWirings = n - 2 to previousPreviousKey, | ||
correctWirings = correctWirings | ||
)?.also { swap(it) } | ||
// don't need to check the last - the error won't be there. | ||
} | ||
correctWirings[key] = getMyWirings(key, wirings) | ||
} | ||
|
||
if (swaps.size != 8) { throw IllegalStateException("Did not find all the swaps") } | ||
|
||
val binaryX = xValues.sortedDescending().map { data[it]!!() }.joinToString("") | ||
val binaryY = yValues.sortedDescending().map { data[it]!!() }.joinToString("") | ||
val expectedResult = (binaryX.toLong(2) + binaryY.toLong(2)).toString(2) | ||
val actualResult = zValues.sortedDescending().map { data[it]!!() }.joinToString("") | ||
|
||
if (actualResult != expectedResult) { throw IllegalStateException("We found eight swaps, but didn't get the right result") } | ||
|
||
swaps.keys.sorted().joinToString(",") | ||
} | ||
|
||
private fun findOperation(op: String) : (Int, Int) -> Int { | ||
return when (op) { | ||
"AND" -> Int::and | ||
"OR" -> Int::or | ||
"XOR" -> Int::xor | ||
else -> throw IllegalArgumentException("Unknown operation $op") | ||
} | ||
} | ||
|
||
private fun checkZero(wirings: MutableMap<String, Wiring>) : Pair<String, String>? { | ||
val z00Wiring = wirings["z00"]!! | ||
val expectedInput = setOf("x00", "y00") | ||
return if (z00Wiring.op != "XOR" || expectedInput != z00Wiring.input()) { | ||
// find `x00 XOR y00 = ???` | ||
val itemToSwapTo = wirings.entries.first { (_, w) -> | ||
val (a, op, b, _) = w | ||
op == "XOR" && expectedInput == setOf(a, b) | ||
} | ||
return "z00" to itemToSwapTo.key | ||
} else { | ||
null | ||
} | ||
} | ||
|
||
private fun checkOne(wirings: MutableMap<String, Wiring>): Pair<String, String>? { | ||
val z01Wiring = wirings["z01"]!! | ||
|
||
// aaa XOR bbb = z01 | ||
// y01 XOR x01 = bbb | ||
// x00 AND y00 = aaa | ||
val aaaInput = setOf("x00", "y00") | ||
val bbbInput = setOf("x01", "y01") | ||
|
||
val aaa = wirings.entries.first { (_, w) -> w.op == "AND" && aaaInput == setOf(w.a, w.b) } | ||
val bbb = wirings.entries.first { (_, w) -> w.op == "XOR" && bbbInput == setOf(w.a, w.b) } | ||
|
||
val correctInput = setOf(aaa.key, bbb.key) | ||
if (z01Wiring.input() == correctInput) { | ||
return null | ||
} else { | ||
// is there a `aaa.key AND bbb.key` which we need to swap to z01? | ||
val maybeZSwap = wirings.entries.find { (_, w) -> w.op == "XOR" && setOf(w.a, w.b) == setOf(aaa.key, bbb.key) } | ||
if (maybeZSwap != null) { | ||
return "z01" to maybeZSwap.key | ||
} else { | ||
// our "z01" was correct - so the input needs swapping | ||
return if (!z01Wiring.input().contains(aaa.key) && !z01Wiring.input().contains(bbb.key)) { | ||
// full disjointSet - this should not happen in the input | ||
throw IllegalStateException("Input has full disjoint set!") | ||
} else { | ||
// one of the items is missing | ||
if (aaa.key in z01Wiring.input()) { | ||
// bbb needs swap | ||
bbb.key to z01Wiring.input().minus(aaa.key).first() | ||
} else { | ||
// aaa needs swap | ||
aaa.key to z01Wiring.input().minus(bbb.key).first() | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
fun checkDefault( | ||
wirings: MutableMap<String, Wiring>, | ||
key: Pair<Int, String>, | ||
previousWirings: Pair<Int, String>, | ||
previousPreviousWirings: Pair<Int, String>, | ||
correctWirings: Map<String, List<Wiring>>, | ||
): Pair<String, String>? { | ||
val myWirings = getMyWirings(key.second, wirings) | ||
|
||
val currentWirings = myWirings.minus(correctWirings[previousWirings.second]!!) | ||
val previousNewWirings = correctWirings[previousWirings.second]!!.minus(correctWirings[previousPreviousWirings.second]!!) | ||
|
||
// the items we need ... | ||
// for zN | ||
// sum(N-1) AND carryChain(N-1) = carryAfter(N-1) | ||
// y(N-1) AND x(N-1) = carry(N-1) | ||
// carryAfter(N-1) OR carry(N-1) = carryChainN | ||
// yN XOR yN = sumN | ||
// sumN XOR carryChainN = zN | ||
|
||
// sum(N-1) | ||
val x1 = previousWirings.first.let { "x${it.toString().padStart(2, '0')}" } | ||
val y1 = previousWirings.first.let { "y${it.toString().padStart(2, '0')}" } | ||
val sumN1 = previousNewWirings.first { w -> w.op == "XOR" && setOf(w.a, w.b) == setOf(x1, y1) } | ||
|
||
// carry(N-1) | ||
val carryN1 = wirings.entries.first { (_, w) -> w.op == "AND" && setOf(w.a, w.b) == setOf(x1, y1) }.value | ||
|
||
// sumN | ||
val x = key.first.let { "x${it.toString().padStart(2, '0')}" } | ||
val y = key.first.let { "y${it.toString().padStart(2, '0')}" } | ||
val sumN = wirings.entries.first { (_, w) -> w.op == "XOR" && setOf(w.a, w.b) == setOf(x, y) }.value | ||
|
||
// carryChain(N-1) | ||
val carryChainN1 = previousNewWirings.find { w -> w.op == "OR" } ?: previousNewWirings.first { w -> w.op == "AND" } | ||
// carryAfter(N-1) | ||
val carryAfterN1 = wirings.entries.find { (_, w) -> w.op == "AND" && setOf(w.a, w.b) == setOf(sumN1.result, carryChainN1.result) }?.value | ||
if (carryAfterN1 == null) { | ||
// doesn't happen on input | ||
throw IllegalArgumentException("Something Wrong with $carryChainN1 or $sumN1") | ||
} | ||
|
||
// carryChainN | ||
val carryChainN = wirings.entries.find { (_, w) -> w.op == "OR" && setOf(w.a, w.b) == setOf(carryAfterN1.result, carryN1.result) }?.value | ||
if (carryChainN == null) { | ||
// doesn't happen on input | ||
throw IllegalArgumentException("Something Wrong with $carryAfterN1 or $carryN1") | ||
} | ||
|
||
// zN | ||
val zN = wirings.entries.find { (_, w) -> w.op == "XOR" && setOf(w.a, w.b) == setOf(sumN.result, carryChainN.result) }?.value | ||
if (zN == null) { | ||
val correctZN = wirings[key.second]!! | ||
return if (carryChainN.result in correctZN.input()) { | ||
// something bad with sumN | ||
sumN.result to correctZN.input().minus(carryChainN.result).first() | ||
} else { | ||
// something bad with carryChainN | ||
carryChainN.result to correctZN.input().minus(sumN.result).first() | ||
} | ||
} | ||
|
||
if (setOf(carryN1, sumN, carryAfterN1, carryChainN, zN) != currentWirings.toSet()) { | ||
// if we got this far - we need to swap out zN values | ||
val toSwapWith = currentWirings.first { it.result == key.second } | ||
return toSwapWith.result to zN.result | ||
} | ||
|
||
return null | ||
} | ||
|
||
private fun getMyWirings(key: String, wirings: MutableMap<String, Wiring>): MutableList<Wiring> { | ||
val myWirings = mutableListOf<Wiring>() | ||
val toCheck = LinkedList<String>() | ||
toCheck.add(key) | ||
while(toCheck.isNotEmpty()) { | ||
val wiring = wirings[toCheck.poll()]!! | ||
val (a, op, b, r) = wiring | ||
|
||
myWirings.add(wiring) | ||
|
||
val aIsInput = a.startsWith('x') || a.startsWith('y') | ||
val bIsInput = b.startsWith('x') || b.startsWith('y') | ||
|
||
if (!aIsInput) { toCheck.add(a) } | ||
if (!bIsInput) { toCheck.add(b) } | ||
} | ||
|
||
return myWirings | ||
} | ||
} | ||
|
||
data class Wiring(val a: String, val op: String, val b: String, val result: String) { | ||
fun input() = setOf(a, b) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
## [Day 24: Crossed Wires](https://adventofcode.com/2024/day/24) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 32 additions & 0 deletions
32
src/test/kotlin/me/peckb/aoc/_2024/calendar/day24/Day24Test.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package me.peckb.aoc._2024.calendar.day24 | ||
|
||
import javax.inject.Inject | ||
|
||
import me.peckb.aoc._2024.DaggerTestDayComponent | ||
import org.junit.jupiter.api.Assertions.assertEquals | ||
import org.junit.jupiter.api.BeforeEach | ||
import org.junit.jupiter.api.Test | ||
|
||
internal class Day24Test { | ||
@Inject | ||
lateinit var day24: Day24 | ||
|
||
@BeforeEach | ||
fun setup() { | ||
DaggerTestDayComponent.create().inject(this) | ||
} | ||
|
||
@Test | ||
fun testDay24PartOne() { | ||
assertEquals(55544677167336, day24.partOne(DAY_24)) | ||
} | ||
|
||
@Test | ||
fun testDay24PartTwo() { | ||
assertEquals("gsd,kth,qnf,tbt,vpm,z12,z26,z32", day24.partTwo(DAY_24)) | ||
} | ||
|
||
companion object { | ||
private const val DAY_24: String = "advent-of-code-input/2024/day24.input" | ||
} | ||
} |