Skip to content

Commit 431aa4f

Browse files
authored
Day 24 2024 (#288)
* manually solved * Initial programatic solution * Cleanup * udate readmes
1 parent 453db1c commit 431aa4f

File tree

5 files changed

+321
-1
lines changed

5 files changed

+321
-1
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,9 @@ as the value which passed the given day/phase combination
104104
* Day 03
105105
* Regex shining today! Sorting by the match location made the single list of all matches easy to scan through.
106106
* Day 12
107-
* 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.
107+
* 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.
108+
* Day 24
109+
* 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.
108110

109111
### Interesting approaches:
110112

Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
package me.peckb.aoc._2024.calendar.day24
2+
3+
import javax.inject.Inject
4+
import me.peckb.aoc.generators.InputGenerator.InputGeneratorFactory
5+
import java.util.LinkedList
6+
7+
class Day24 @Inject constructor(
8+
private val generatorFactory: InputGeneratorFactory,
9+
) {
10+
fun partOne(filename: String) = generatorFactory.forFile(filename).read { input ->
11+
var readingBase = true
12+
13+
val data = mutableMapOf<String, Lazy<Int>>()
14+
val zValues = mutableListOf<String>()
15+
16+
input.forEach input@{ line ->
17+
if (line.isEmpty()) {
18+
readingBase = false
19+
return@input
20+
}
21+
if (readingBase) {
22+
line.split(": ").let { (key, value) -> data[key] = lazy { value.toInt() } }
23+
} else {
24+
line.split(" ").let { (first, operations, second, _, result) ->
25+
if (result.startsWith('z')) { zValues.add(result) }
26+
27+
val op = findOperation(operations)
28+
data[result] = lazy { op(data[first]!!.value, data[second]!!.value) }
29+
}
30+
}
31+
}
32+
33+
zValues.sortedDescending()
34+
.map { data[it]!!.value }
35+
.joinToString("")
36+
.toLong(2)
37+
}
38+
39+
fun partTwo(filename: String) = generatorFactory.forFile(filename).read { input ->
40+
var readingBase = true
41+
val data = mutableMapOf<String, () -> Int>()
42+
val xValues = mutableListOf<String>()
43+
val yValues = mutableListOf<String>()
44+
val zValues = mutableListOf<String>()
45+
val wirings = mutableMapOf<String, Wiring>()
46+
47+
input.forEach input@{ line ->
48+
if (line.isEmpty()) {
49+
readingBase = false
50+
return@input
51+
}
52+
if (readingBase) {
53+
line.split(": ").let { (key, value) ->
54+
if (key.startsWith('x')) { xValues.add(key) }
55+
if (key.startsWith('y')) { yValues.add(key) }
56+
data[key] = { value.toInt() }
57+
}
58+
} else {
59+
line.split(" ").let { (a, operation, b, _, result) ->
60+
if (result.startsWith('z')) { zValues.add(result) }
61+
62+
val op = findOperation(operation)
63+
64+
wirings[result] = Wiring(a, operation, b, result)
65+
66+
data[result] = { op(data[a]!!(), data[b]!!()) }
67+
}
68+
}
69+
}
70+
71+
val swaps = mutableMapOf<String, String>()
72+
73+
fun swap(swap: Pair<String, String>) {
74+
val oldA = wirings[swap.first]!!
75+
val oldB = wirings[swap.second]!!
76+
77+
val newB = oldB.copy(result = oldA.result)
78+
val newA = oldA.copy(result = oldB.result)
79+
80+
wirings[swap.first] = newB
81+
wirings[swap.second] = newA
82+
83+
val opA = findOperation(newA.op)
84+
val opB = findOperation(newB.op)
85+
86+
data[newA.result] = { opA(data[newA.a]!!(), data[newA.b]!!()) }
87+
data[newB.result] = { opB(data[newB.a]!!(), data[newB.b]!!()) }
88+
89+
swaps[swap.first] = swap.second
90+
swaps[swap.second] = swap.first
91+
}
92+
93+
val correctWirings = mutableMapOf<String, List<Wiring>>()
94+
95+
(0 .. 45).forEach loop@{ n ->
96+
val previousPreviousKey = "z${(n-2).toString().padStart(2, '0')}"
97+
val previousKey = "z${(n-1).toString().padStart(2, '0')}"
98+
val key = "z${n.toString().padStart(2, '0')}"
99+
when (n) {
100+
0 -> checkZero(wirings)?.also { swap(it) }
101+
1 -> checkOne(wirings)?.also { swap(it) }
102+
in (2..44) -> checkDefault(
103+
wirings = wirings,
104+
key = n to key,
105+
previousWirings = n - 1 to previousKey,
106+
previousPreviousWirings = n - 2 to previousPreviousKey,
107+
correctWirings = correctWirings
108+
)?.also { swap(it) }
109+
// don't need to check the last - the error won't be there.
110+
}
111+
correctWirings[key] = getMyWirings(key, wirings)
112+
}
113+
114+
if (swaps.size != 8) { throw IllegalStateException("Did not find all the swaps") }
115+
116+
val binaryX = xValues.sortedDescending().map { data[it]!!() }.joinToString("")
117+
val binaryY = yValues.sortedDescending().map { data[it]!!() }.joinToString("")
118+
val expectedResult = (binaryX.toLong(2) + binaryY.toLong(2)).toString(2)
119+
val actualResult = zValues.sortedDescending().map { data[it]!!() }.joinToString("")
120+
121+
if (actualResult != expectedResult) { throw IllegalStateException("We found eight swaps, but didn't get the right result") }
122+
123+
swaps.keys.sorted().joinToString(",")
124+
}
125+
126+
private fun findOperation(op: String) : (Int, Int) -> Int {
127+
return when (op) {
128+
"AND" -> Int::and
129+
"OR" -> Int::or
130+
"XOR" -> Int::xor
131+
else -> throw IllegalArgumentException("Unknown operation $op")
132+
}
133+
}
134+
135+
private fun checkZero(wirings: MutableMap<String, Wiring>) : Pair<String, String>? {
136+
val z00Wiring = wirings["z00"]!!
137+
val expectedInput = setOf("x00", "y00")
138+
return if (z00Wiring.op != "XOR" || expectedInput != z00Wiring.input()) {
139+
// find `x00 XOR y00 = ???`
140+
val itemToSwapTo = wirings.entries.first { (_, w) ->
141+
val (a, op, b, _) = w
142+
op == "XOR" && expectedInput == setOf(a, b)
143+
}
144+
return "z00" to itemToSwapTo.key
145+
} else {
146+
null
147+
}
148+
}
149+
150+
private fun checkOne(wirings: MutableMap<String, Wiring>): Pair<String, String>? {
151+
val z01Wiring = wirings["z01"]!!
152+
153+
// aaa XOR bbb = z01
154+
// y01 XOR x01 = bbb
155+
// x00 AND y00 = aaa
156+
val aaaInput = setOf("x00", "y00")
157+
val bbbInput = setOf("x01", "y01")
158+
159+
val aaa = wirings.entries.first { (_, w) -> w.op == "AND" && aaaInput == setOf(w.a, w.b) }
160+
val bbb = wirings.entries.first { (_, w) -> w.op == "XOR" && bbbInput == setOf(w.a, w.b) }
161+
162+
val correctInput = setOf(aaa.key, bbb.key)
163+
if (z01Wiring.input() == correctInput) {
164+
return null
165+
} else {
166+
// is there a `aaa.key AND bbb.key` which we need to swap to z01?
167+
val maybeZSwap = wirings.entries.find { (_, w) -> w.op == "XOR" && setOf(w.a, w.b) == setOf(aaa.key, bbb.key) }
168+
if (maybeZSwap != null) {
169+
return "z01" to maybeZSwap.key
170+
} else {
171+
// our "z01" was correct - so the input needs swapping
172+
return if (!z01Wiring.input().contains(aaa.key) && !z01Wiring.input().contains(bbb.key)) {
173+
// full disjointSet - this should not happen in the input
174+
throw IllegalStateException("Input has full disjoint set!")
175+
} else {
176+
// one of the items is missing
177+
if (aaa.key in z01Wiring.input()) {
178+
// bbb needs swap
179+
bbb.key to z01Wiring.input().minus(aaa.key).first()
180+
} else {
181+
// aaa needs swap
182+
aaa.key to z01Wiring.input().minus(bbb.key).first()
183+
}
184+
}
185+
}
186+
}
187+
}
188+
189+
fun checkDefault(
190+
wirings: MutableMap<String, Wiring>,
191+
key: Pair<Int, String>,
192+
previousWirings: Pair<Int, String>,
193+
previousPreviousWirings: Pair<Int, String>,
194+
correctWirings: Map<String, List<Wiring>>,
195+
): Pair<String, String>? {
196+
val myWirings = getMyWirings(key.second, wirings)
197+
198+
val currentWirings = myWirings.minus(correctWirings[previousWirings.second]!!)
199+
val previousNewWirings = correctWirings[previousWirings.second]!!.minus(correctWirings[previousPreviousWirings.second]!!)
200+
201+
// the items we need ...
202+
// for zN
203+
// sum(N-1) AND carryChain(N-1) = carryAfter(N-1)
204+
// y(N-1) AND x(N-1) = carry(N-1)
205+
// carryAfter(N-1) OR carry(N-1) = carryChainN
206+
// yN XOR yN = sumN
207+
// sumN XOR carryChainN = zN
208+
209+
// sum(N-1)
210+
val x1 = previousWirings.first.let { "x${it.toString().padStart(2, '0')}" }
211+
val y1 = previousWirings.first.let { "y${it.toString().padStart(2, '0')}" }
212+
val sumN1 = previousNewWirings.first { w -> w.op == "XOR" && setOf(w.a, w.b) == setOf(x1, y1) }
213+
214+
// carry(N-1)
215+
val carryN1 = wirings.entries.first { (_, w) -> w.op == "AND" && setOf(w.a, w.b) == setOf(x1, y1) }.value
216+
217+
// sumN
218+
val x = key.first.let { "x${it.toString().padStart(2, '0')}" }
219+
val y = key.first.let { "y${it.toString().padStart(2, '0')}" }
220+
val sumN = wirings.entries.first { (_, w) -> w.op == "XOR" && setOf(w.a, w.b) == setOf(x, y) }.value
221+
222+
// carryChain(N-1)
223+
val carryChainN1 = previousNewWirings.find { w -> w.op == "OR" } ?: previousNewWirings.first { w -> w.op == "AND" }
224+
// carryAfter(N-1)
225+
val carryAfterN1 = wirings.entries.find { (_, w) -> w.op == "AND" && setOf(w.a, w.b) == setOf(sumN1.result, carryChainN1.result) }?.value
226+
if (carryAfterN1 == null) {
227+
// doesn't happen on input
228+
throw IllegalArgumentException("Something Wrong with $carryChainN1 or $sumN1")
229+
}
230+
231+
// carryChainN
232+
val carryChainN = wirings.entries.find { (_, w) -> w.op == "OR" && setOf(w.a, w.b) == setOf(carryAfterN1.result, carryN1.result) }?.value
233+
if (carryChainN == null) {
234+
// doesn't happen on input
235+
throw IllegalArgumentException("Something Wrong with $carryAfterN1 or $carryN1")
236+
}
237+
238+
// zN
239+
val zN = wirings.entries.find { (_, w) -> w.op == "XOR" && setOf(w.a, w.b) == setOf(sumN.result, carryChainN.result) }?.value
240+
if (zN == null) {
241+
val correctZN = wirings[key.second]!!
242+
return if (carryChainN.result in correctZN.input()) {
243+
// something bad with sumN
244+
sumN.result to correctZN.input().minus(carryChainN.result).first()
245+
} else {
246+
// something bad with carryChainN
247+
carryChainN.result to correctZN.input().minus(sumN.result).first()
248+
}
249+
}
250+
251+
if (setOf(carryN1, sumN, carryAfterN1, carryChainN, zN) != currentWirings.toSet()) {
252+
// if we got this far - we need to swap out zN values
253+
val toSwapWith = currentWirings.first { it.result == key.second }
254+
return toSwapWith.result to zN.result
255+
}
256+
257+
return null
258+
}
259+
260+
private fun getMyWirings(key: String, wirings: MutableMap<String, Wiring>): MutableList<Wiring> {
261+
val myWirings = mutableListOf<Wiring>()
262+
val toCheck = LinkedList<String>()
263+
toCheck.add(key)
264+
while(toCheck.isNotEmpty()) {
265+
val wiring = wirings[toCheck.poll()]!!
266+
val (a, op, b, r) = wiring
267+
268+
myWirings.add(wiring)
269+
270+
val aIsInput = a.startsWith('x') || a.startsWith('y')
271+
val bIsInput = b.startsWith('x') || b.startsWith('y')
272+
273+
if (!aIsInput) { toCheck.add(a) }
274+
if (!bIsInput) { toCheck.add(b) }
275+
}
276+
277+
return myWirings
278+
}
279+
}
280+
281+
data class Wiring(val a: String, val op: String, val b: String, val result: String) {
282+
fun input() = setOf(a, b)
283+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
## [Day 24: Crossed Wires](https://adventofcode.com/2024/day/24)

src/test/kotlin/me/peckb/aoc/_2024/TestDayComponent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import me.peckb.aoc._2024.calendar.day20.Day20Test
2323
import me.peckb.aoc._2024.calendar.day21.Day21Test
2424
import me.peckb.aoc._2024.calendar.day22.Day22Test
2525
import me.peckb.aoc._2024.calendar.day23.Day23Test
26+
import me.peckb.aoc._2024.calendar.day24.Day24Test
2627
import javax.inject.Singleton
2728
import me.peckb.aoc.DayComponent
2829
import me.peckb.aoc.InputModule
@@ -54,4 +55,5 @@ internal interface TestDayComponent : DayComponent {
5455
fun inject(day21Test: Day21Test)
5556
fun inject(day22Test: Day22Test)
5657
fun inject(day23Test: Day23Test)
58+
fun inject(day24Test: Day24Test)
5759
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package me.peckb.aoc._2024.calendar.day24
2+
3+
import javax.inject.Inject
4+
5+
import me.peckb.aoc._2024.DaggerTestDayComponent
6+
import org.junit.jupiter.api.Assertions.assertEquals
7+
import org.junit.jupiter.api.BeforeEach
8+
import org.junit.jupiter.api.Test
9+
10+
internal class Day24Test {
11+
@Inject
12+
lateinit var day24: Day24
13+
14+
@BeforeEach
15+
fun setup() {
16+
DaggerTestDayComponent.create().inject(this)
17+
}
18+
19+
@Test
20+
fun testDay24PartOne() {
21+
assertEquals(55544677167336, day24.partOne(DAY_24))
22+
}
23+
24+
@Test
25+
fun testDay24PartTwo() {
26+
assertEquals("gsd,kth,qnf,tbt,vpm,z12,z26,z32", day24.partTwo(DAY_24))
27+
}
28+
29+
companion object {
30+
private const val DAY_24: String = "advent-of-code-input/2024/day24.input"
31+
}
32+
}

0 commit comments

Comments
 (0)