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+ }
0 commit comments