Skip to content

Commit

Permalink
Day 24 2024 (#288)
Browse files Browse the repository at this point in the history
* manually solved

* Initial programatic solution

* Cleanup

* udate readmes
  • Loading branch information
peckb1 authored Dec 25, 2024
1 parent 453db1c commit 431aa4f
Show file tree
Hide file tree
Showing 5 changed files with 321 additions and 1 deletion.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ as the value which passed the given day/phase combination
* Day 03
* Regex shining today! Sorting by the match location made the single list of all matches easy to scan through.
* Day 12
* Not a day where I had the best solution; but a solution nonetheless. Slow scan of each field's border; ain't much, but it's honest work.
* Not a day where I had the best solution; but a solution nonetheless. Slow scan of each field's border; ain't much, but it's honest work.
* Day 24
* I had to pen + paper the finding of the wires; while renaming my output to be what they actually are (i.e. `x01 XOR y01 -> sum1`). But then coming up with the actual rules to mimic my manual process was fun. And in the process learned a lot about binary addition at the logic gate level.

### Interesting approaches:

Expand Down
283 changes: 283 additions & 0 deletions src/main/kotlin/me/peckb/aoc/_2024/calendar/day24/Day24.kt
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)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
## [Day 24: Crossed Wires](https://adventofcode.com/2024/day/24)
2 changes: 2 additions & 0 deletions src/test/kotlin/me/peckb/aoc/_2024/TestDayComponent.kt
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import me.peckb.aoc._2024.calendar.day20.Day20Test
import me.peckb.aoc._2024.calendar.day21.Day21Test
import me.peckb.aoc._2024.calendar.day22.Day22Test
import me.peckb.aoc._2024.calendar.day23.Day23Test
import me.peckb.aoc._2024.calendar.day24.Day24Test
import javax.inject.Singleton
import me.peckb.aoc.DayComponent
import me.peckb.aoc.InputModule
Expand Down Expand Up @@ -54,4 +55,5 @@ internal interface TestDayComponent : DayComponent {
fun inject(day21Test: Day21Test)
fun inject(day22Test: Day22Test)
fun inject(day23Test: Day23Test)
fun inject(day24Test: Day24Test)
}
32 changes: 32 additions & 0 deletions src/test/kotlin/me/peckb/aoc/_2024/calendar/day24/Day24Test.kt
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"
}
}

0 comments on commit 431aa4f

Please sign in to comment.