11package de.ronny_h.aoc.year24.day17
22
33import de.ronny_h.aoc.AdventOfCode
4- import kotlin.math.pow
5- import kotlin.properties.Delegates
4+ import de.ronny_h.aoc.extensions.memoize
65
7- fun main () = ChronospatialComputer ().run (" 4,1,7,6,4,1,0,2,7" , " TODO " )
6+ fun main () = ChronospatialComputer ().run (" 4,1,7,6,4,1,0,2,7" , " 164279024971453 " )
87
98class ChronospatialComputer : AdventOfCode <String >(2024 , 17 ) {
109 override fun part1 (input : List <String >): String {
11- return ThreeBitComputer ().runProgram(input )
10+ return ThreeBitComputer (input ).runProgram().joinToString( " , " )
1211 }
1312
14- override fun part2 (input : List <String >): String = " TODO"
13+ fun part2BruteForce (input : List <String >): String {
14+ val computer = ThreeBitComputer (input)
15+ val state = computer.save()
16+ var registerA = 0L
17+ while (computer.runProgram() != computer.program) {
18+ registerA++
19+ computer.restore(state, registerA)
20+ }
21+ return " $registerA "
22+ }
23+
24+ override fun part2 (input : List <String >): String {
25+ val computer = ThreeBitComputer (input)
26+ val program = computer.program
27+ val binarySolution = findSmallestRegisterAValueToOutputTheProgram(program)
28+ val solution = binarySolution.toLong(2 )
29+
30+ // validate the solution using the emulated 3-bit computer
31+ computer.restore(computer.save(), solution)
32+ val output = computer.runProgram()
33+ check(output == program)
34+
35+ println (" binary solution: $binarySolution " )
36+ println (" as Long: $solution " )
37+ return " $solution "
38+ }
39+ }
40+
41+ fun findSmallestRegisterAValueToOutputTheProgram (program : List <Int >): String {
42+ val programValues = program.toSet() // the numbers to generate
43+
44+ // there are 0..1111111 possibilities to generate one number
45+ // (C is shifted right max 7 digits, only lowest 3 digits of remaining C count for output)
46+ val max = " 1111111111" .toInt(2 )
47+ val resultByInput = mutableMapOf<Int , MutableList <Int >>()
48+ (0 .. max).map {
49+ it to runTheProgramOneIteration(it.toLong())
50+ }.forEach { (a, out ) ->
51+ if (out in programValues) {
52+ resultByInput.getOrPut(out ) { mutableListOf () }.add(a)
53+ }
54+ }
55+ val outProducing = programValues.sorted().associateWith { out ->
56+ val generatedBy = resultByInput.getValue(out )
57+ println (" $out can be generated by $generatedBy ${generatedBy.size} " )
58+ generatedBy.map { it.toString(2 ).padStart(3 , ' 0' ) }
59+ }
60+ val outProducingSortedByLowest3Bits = outProducing.mapValues { (_, producedBy) ->
61+ // sort by lowest 3 bits because of (*)
62+ producedBy.sortedBy { it.substring(it.length - 3 ) }
63+ }
64+
65+ fun checkSolution (candidate : String ): Boolean {
66+ val registerA = candidate.toLong(2 )
67+ val result = runTheProgramsActualImplementation(registerA)
68+ return result == program
69+ }
70+
71+ fun findSolution (
72+ remainingProgram : List <Int >,
73+ solutionSoFar : String ,
74+ ): String {
75+
76+ data class RecursionState (
77+ val remainingProgram : List <Int >,
78+ val solutionSoFar : String ,
79+ )
80+
81+ lateinit var findSolutionRec: (RecursionState ) -> Pair <String , Boolean >
82+
83+ findSolutionRec = { state: RecursionState ->
84+ if (state.remainingProgram.isEmpty()) {
85+ // due to larger overlaps than the chosen ones there might be higher digits leading to a different output
86+ // -> check if the solution indeed is one
87+ state.solutionSoFar to checkSolution(state.solutionSoFar)
88+ } else {
89+ val p = state.remainingProgram.first()
90+ // (*) from outProducing[p] take that number whose digits higher than 3 matches the last bits of binarySolution and has minimal lowest 3 bits
91+ val possibleChoices = outProducingSortedByLowest3Bits.getValue(p)
92+
93+ var result: Pair <String , Boolean > = state.solutionSoFar to false
94+ for (c in possibleChoices) {
95+ if (c.length == 3 ) {
96+ // an exactly 3 bit long minimal value found -> take it
97+ val (solution, found) = findSolutionRec(
98+ RecursionState (
99+ state.remainingProgram.drop(1 ),
100+ state.solutionSoFar + c
101+ )
102+ )
103+ if (found) {
104+ result = solution to true
105+ break
106+ } else {
107+ continue
108+ }
109+ } else {
110+ // search a longer value with minimal lowest 3 bits (see (*)) and matching overlapping bits
111+ val higherBitsOfC = c.substring(0 , c.length - 3 )
112+ val startIndex = state.solutionSoFar.length - higherBitsOfC.length
113+ if (startIndex < 0 ) {
114+ // overlap exceeds the current number of bits in binarySolution
115+ continue
116+ }
117+ val overlappingBits = state.solutionSoFar.substring(startIndex)
118+ if (higherBitsOfC == overlappingBits) {
119+ // take the lowest 3 bits; the higher ones are matching
120+ val (solution, found) = findSolutionRec(
121+ RecursionState (
122+ state.remainingProgram.drop(1 ),
123+ state.solutionSoFar + c.substring(c.length - 3 )
124+ )
125+ )
126+ if (found) {
127+ result = solution to true
128+ break
129+ } else {
130+ continue
131+ }
132+ }
133+ }
134+ }
135+ result
136+ }
137+ }.memoize()
138+
139+ return findSolutionRec(RecursionState (remainingProgram, solutionSoFar)).first
140+ }
141+
142+ println (" program: $program " )
143+ // result can only be minimal if the minimal solution is used for the highest bit -> start with that
144+ val highestThreeBits = outProducing.getValue(program.last()).first()
145+ check(highestThreeBits.length == 3 )
146+ val binarySolution = findSolution(program.dropLast(1 ).reversed(), highestThreeBits)
147+ return binarySolution
148+ }
149+
150+ fun runTheProgramsActualImplementation (registerA : Long = 64854237L): List <Int > {
151+ var A = registerA
152+ val out = mutableListOf<Int >()
153+ do {
154+ out .add(runTheProgramOneIteration(A ))
155+ A = A shr 3 // 6: 0,3 A = A shr 3
156+ } while (A != 0L ) // 7: 3,0 A==0 ? end : goto 0
157+ return out
158+ }
159+
160+ fun runTheProgramOneIteration (A : Long ): Int {
161+ var B = A % 8 // 0: 2,4 B = A % 8 -> B = lowest 3 bits of A
162+ B = B xor 1 // 1: 1,1 B = B xor 1 (001) -> invert lowest bit of B
163+ val C = A shr B .toIntChecked() // 2: 7,5 C = A shr B
164+ B = B xor 5 // 3: 1,5 B = B xor 5 (101) -> invert bit 0 and 2 of B
165+ B = B xor C // 4: 4,0 B = B xor C
166+ return (B % 8 ).toInt() // 5: 5,5 out B % 8 -> print lowest 3 bits of B
15167}
16168
17169/* *
@@ -38,50 +190,51 @@ class ChronospatialComputer : AdventOfCode<String>(2024, 17) {
38190 * 6 bdv division of A and 2^<combo operand>, truncated result in B
39191 * 7 cdv division of A and 2^<combo operand>, truncated result in C
40192 */
41- private class ThreeBitComputer {
193+ class ThreeBitComputer ( input : List < String >) {
42194
43195 private val instructionStep = 2
44196
45- private lateinit var program: List < Int >
46- private var registerA by Delegates .notNull< Int >( )
47- private var registerB by Delegates .notNull< Int >( )
48- private var registerC by Delegates .notNull< Int >( )
197+ val program = input.readProgram()
198+ private var registerA = input.readRegister( ' A ' )
199+ private var registerB = input.readRegister( ' B ' )
200+ private var registerC = input.readRegister( ' C ' )
49201 private var instructionPointer = 0
50202
51203 private val output = mutableListOf<Int >()
52204
53- fun init (input : List <String >) {
54- program = input.readProgram()
55- registerA = input.readRegister(' A' )
56- registerB = input.readRegister(' B' )
57- registerC = input.readRegister(' C' )
58- instructionPointer = 0
59- output.clear()
60- }
61-
62205 private val instructions: List < (Int ) -> Unit > = listOf (
63- { op -> registerA = (registerA / 2.0 .pow( combo(op))).toInt() ; next() }, // 0 adv
64- { op -> registerB = (registerB xor op) ; next() }, // 1 bxl
65- { op -> registerB = (combo(op) % 8 ) and 7 ; next() }, // 2 bst; 7=111 -> take only lowest 3 bits
66- { op -> if (registerA == 0 ) next() else instructionPointer = op }, // 3 jnz
206+ { op -> registerA = (registerA shr combo(op).toIntChecked()) ; next() }, // 0 adv; A/2^x = A shr x
207+ { op -> registerB = (registerB xor op.toLong()) ; next() }, // 1 bxl
208+ { op -> registerB = (combo(op) % 8 ) ; next() }, // 2 bst; 7=111 -> take only lowest 3 bits
209+ { op -> if (registerA == 0L ) next() else instructionPointer = op }, // 3 jnz
67210 { _ -> registerB = (registerB xor registerC) ; next() }, // 4 bxc
68- { op -> output.add((combo(op) % 8 ) and 7 ) ; next() }, // 5 out
69- { op -> registerB = (registerA / 2.0 .pow( combo(op))).toInt( ); next() }, // 6 bdv
70- { op -> registerC = (registerA / 2.0 .pow( combo(op))).toInt( ); next() }, // 7 cdv
211+ { op -> output.add((combo(op) % 8 ).toInt()) ; next() }, // 5 out
212+ { op -> registerB = (registerA shr combo(op).toIntChecked() ); next() }, // 6 bdv
213+ { op -> registerC = (registerA shr combo(op).toIntChecked() ); next() }, // 7 cdv
71214 )
72215
73- fun runProgram (input : List <String >): String {
74- init (input)
216+ fun runProgram (): List <Int > {
75217 while (instructionPointer < program.size) {
76218 val instruction = program[instructionPointer]
77219 val op = program[instructionPointer + 1 ]
78220 instructions[instruction].invoke(op)
79221 }
80- return output.joinToString(" ," )
222+ return output
223+ }
224+
225+ fun save () = State (registerA, registerB, registerC, instructionPointer)
226+ fun restore (state : State , registerAOverride : Long ) {
227+ this .registerA = registerAOverride
228+ this .registerB = state.registerB
229+ this .registerC = state.registerC
230+ this .instructionPointer = state.instructionPointer
231+ this .output.clear()
81232 }
82233
83234 private fun combo (op : Int ) = when (op) {
84- in 1 .. 3 -> op
235+ 1 -> 1L
236+ 2 -> 2L
237+ 3 -> 3L
85238 4 -> registerA
86239 5 -> registerB
87240 6 -> registerC
@@ -95,11 +248,25 @@ private class ThreeBitComputer {
95248 private fun List<String>.readRegister (register : Char ) =
96249 first { it.startsWith(" Register $register : " ) }
97250 .substringAfter(" Register $register : " )
98- .toInt ()
251+ .toLong ()
99252
100253 private fun List<String>.readProgram () =
101254 first { it.startsWith(" Program: " ) }
102255 .substringAfter(" Program: " )
103256 .split(" ," )
104257 .map(String ::toInt)
258+
259+ data class State (
260+ val registerA : Long ,
261+ val registerB : Long ,
262+ val registerC : Long ,
263+ val instructionPointer : Int ,
264+ )
265+ }
266+
267+ private fun Long.toIntChecked (): Int {
268+ if (this > Int .MAX_VALUE ) {
269+ throw IllegalArgumentException (" $this exceeds Int range" )
270+ }
271+ return this .toInt()
105272}
0 commit comments