Skip to content

Commit e261d87

Browse files
committed
Solve 2024 day 17 part 2 using reverse engineering and Z3
1 parent 398cf0b commit e261d87

File tree

2 files changed

+122
-8
lines changed

2 files changed

+122
-8
lines changed

src/main/scala/eu/sim642/adventofcode2024/Day17.scala

Lines changed: 87 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
package eu.sim642.adventofcode2024
22

3+
import com.microsoft.z3.{Context, Status}
4+
5+
import scala.jdk.CollectionConverters.*
6+
37
object Day17 {
48

59
case class Registers(a: Int, b: Int, c: Int)
@@ -8,12 +12,12 @@ object Day17 {
812

913
case class Input(registers: Registers, program: Program)
1014

11-
def runOutput(input: Input): String = {
15+
def runOutput0(input: Input): Seq[Int] = {
1216
val Input(registers, program) = input
1317

1418
def helper(ip: Int, registers: Registers): LazyList[Int] = {
15-
println(ip)
16-
println(registers)
19+
//println(ip)
20+
//println(registers)
1721

1822
def combo(operand: Int): Int = operand match {
1923
case 0 | 1 | 2 | 3 => operand
@@ -32,7 +36,8 @@ object Day17 {
3236

3337
program(ip) match {
3438
case 0 => // adv
35-
helper(ip + 2, registers.copy(a = registers.a / (1 << comboOperand)))
39+
//helper(ip + 2, registers.copy(a = registers.a / (1 << comboOperand)))
40+
helper(ip + 2, registers.copy(a = registers.a >> comboOperand))
3641
case 1 => // bxl
3742
helper(ip + 2, registers.copy(b = registers.b ^ literalOperand))
3843
case 2 => // bst
@@ -44,17 +49,73 @@ object Day17 {
4449
case 5 => // out
4550
(comboOperand & 0b111) +: helper(ip + 2, registers)
4651
case 6 => // bdv
47-
helper(ip + 2, registers.copy(b = registers.a / (1 << comboOperand)))
52+
//helper(ip + 2, registers.copy(b = registers.a / (1 << comboOperand)))
53+
helper(ip + 2, registers.copy(b = registers.a >> comboOperand))
4854
case 7 => // cdv
49-
helper(ip + 2, registers.copy(c = registers.a / (1 << comboOperand)))
55+
//helper(ip + 2, registers.copy(c = registers.a / (1 << comboOperand)))
56+
helper(ip + 2, registers.copy(c = registers.a >> comboOperand))
5057
case _ => throw new IllegalArgumentException("illegal instruction")
5158
}
5259
}
5360
else
5461
LazyList.empty
5562
}
5663

57-
helper(0, registers).mkString(",")
64+
helper(0, registers)
65+
}
66+
67+
def runOutput(input: Input): String = runOutput0(input).mkString(",")
68+
69+
def findQuineA(input: Input): Int = {
70+
Iterator.from(0)
71+
.find(newA => runOutput0(Input(input.registers.copy(a = newA), input.program)) == input.program)
72+
.get
73+
}
74+
75+
def myProg(initialA: Int, expectedOutputs: Iterator[Int]): Boolean = {
76+
var a: Int = initialA
77+
var b: Int = 0
78+
var c: Int = 0
79+
while (a != 0) {
80+
b = a & 0b111
81+
b = b ^ 1
82+
c = a >> b
83+
b = b ^ 5
84+
b = b ^ c
85+
a = a >> 3
86+
if ((b & 0b111) != expectedOutputs.next())
87+
return false
88+
}
89+
!expectedOutputs.hasNext
90+
}
91+
92+
def findQuineA2(input: Input): Int = {
93+
Iterator.from(0)
94+
.find(newA => myProg(newA, input.program.iterator))
95+
.get
96+
}
97+
98+
def findQuineA3(input: Input): Long = {
99+
val ctx = new Context(Map("model" -> "true").asJava)
100+
import ctx._
101+
val s = mkSolver()
102+
103+
val bits = input.program.size * 3
104+
val initialA = mkBVConst("initialA", bits)
105+
106+
for ((instruction, i) <- input.program.zipWithIndex) {
107+
val a = mkBVLSHR(initialA, mkBV(i * 3, bits))
108+
var b = mkBVAND(a, mkBV(7, bits))
109+
b = mkBVXOR(b, mkBV(1, bits))
110+
val c = mkBVLSHR(a, b)
111+
b = mkBVXOR(b, mkBV(5, bits))
112+
b = mkBVXOR(b, c)
113+
val out = mkBVAND(b, mkBV(7, bits))
114+
s.add(mkEq(out, mkBV(instruction, bits)))
115+
}
116+
117+
assert(s.check() == Status.SATISFIABLE)
118+
s.getModel.evaluate(initialA, false).toString.toLong
58119
}
59120

60121
def parseInput(input: String): Input = input match {
@@ -67,7 +128,25 @@ object Day17 {
67128
lazy val input: String = scala.io.Source.fromInputStream(getClass.getResourceAsStream("day17.txt")).mkString.trim
68129

69130
def main(args: Array[String]): Unit = {
70-
println(runOutput(parseInput(input)))
131+
/*var a: Int = 30344604
132+
var b: Int = 0
133+
var c: Int = 0
134+
while (a != 0) {
135+
b = a & 0b111
136+
b = b ^ 1
137+
c = a >> b
138+
b = b ^ 5
139+
b = b ^ c
140+
a = a >> 3
141+
print(b & 0b111)
142+
print(',')
143+
}*/
144+
145+
146+
//println(runOutput(parseInput(input)))
147+
println(findQuineA3(parseInput(input)))
148+
149+
// part 2: 164540892147389 - correct
71150

72151
// part 1: 4,5,0,4,7,4,3,0,0 - wrong (bst used literal not combo operand)
73152
}

src/test/scala/eu/sim642/adventofcode2024/Day17Test.scala

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,13 @@ class Day17Test extends AnyFunSuite {
2828
|
2929
|Program: 0,1,5,4,3,0""".stripMargin
3030

31+
val exampleInput3 =
32+
"""Register A: 2024
33+
|Register B: 0
34+
|Register C: 0
35+
|
36+
|Program: 0,3,5,4,3,0""".stripMargin
37+
3138
test("Part 1 examples") {
3239
assert(runOutput(parseInput(exampleInput)) == "4,6,3,5,6,3,5,2,1,0")
3340
assert(runOutput(parseInput(exampleInput1)) == "0,1,2")
@@ -37,4 +44,32 @@ class Day17Test extends AnyFunSuite {
3744
test("Part 1 input answer") {
3845
assert(runOutput(parseInput(input)) == "4,3,2,6,4,5,3,2,4")
3946
}
47+
48+
test("Part 2 examples") {
49+
assert(findQuineA(parseInput(exampleInput3)) == 117440)
50+
}
51+
52+
/*
53+
do {
54+
b = a & 0b111
55+
b = b ^ 1
56+
c = a / (1 << b)
57+
b = b ^ 5
58+
b = b ^ c
59+
a = a / (1 << 3)
60+
output(b & 0b111)
61+
} while (a != 0)
62+
*/
63+
64+
/*
65+
do {
66+
b = a & 0b111
67+
b = b ^ 1
68+
c = a >> b
69+
b = b ^ 5
70+
b = b ^ c
71+
a = a >> 3
72+
output(b & 0b111)
73+
} while (a != 0)
74+
*/
4075
}

0 commit comments

Comments
 (0)