11package me.peckb.aoc._2025.calendar.day10
22
3- import com.microsoft.z3.BitVecNum
4- import com.microsoft.z3.BitVecSort
3+ import com.microsoft.z3.ArithExpr
54import com.microsoft.z3.Context
6- import com.microsoft.z3.Expr
7- import com.microsoft.z3.Optimize
5+ import com.microsoft.z3.IntExpr
6+ import com.microsoft.z3.IntNum
7+ import com.microsoft.z3.IntSort
88import com.microsoft.z3.Status
99import javax.inject.Inject
1010import me.peckb.aoc.generators.InputGenerator.InputGeneratorFactory
11- import org.apache.commons.math3.stat.inference.TestUtils.g
11+ import kotlin.collections.component1
12+ import kotlin.collections.component2
1213import kotlin.math.min
13- import kotlin.use
1414
1515class Day10 @Inject constructor(
1616 private val generatorFactory : InputGeneratorFactory ,
1717) {
1818 fun partOne (filename : String ) = generatorFactory.forFile(filename).readAs(::day10) { input ->
19- input.sumOf { machine ->
20- thing(" ." .repeat(machine.indicatorLights.length), machine.indicatorLights, machine.buttonSchematics)
21- }
19+ input.sumOf { machine -> flipLights(machine.indicatorLights, machine.buttonSchematics) }
2220 }
2321
2422 fun partTwo (filename : String ) = generatorFactory.forFile(filename).readAs(::day10) { input ->
25- input.sumOf { machine ->
26-
27- Context ().use { ctx ->
28- var bestFound = Int .MAX_VALUE
29-
30- val bits = 64 // the answer will fit inside a long
31-
32- fun valueOf (value : Int ) = ctx.mkBV(value, bits)
33-
34- fun variableOf (name : String ) = ctx.mkBVConst(name, bits)
35-
36- infix fun Expr<BitVecSort>.equalTo (t : Expr <BitVecSort >) = ctx.mkEq(this , t)
37-
38- infix fun Expr<BitVecSort>.notEqual (t : Expr <BitVecSort >) = ctx.mkNot(this equalTo t)
39-
40- operator fun Expr<BitVecSort>.plus (t : Expr <BitVecSort >) = ctx.mkBVAdd(this , t)
41-
42- operator fun Expr<BitVecSort>.times (t : Expr <BitVecSort >) = ctx.mkBVMul(this , t)
43-
44- infix fun Expr<BitVecSort>.lessThan (t : Expr <BitVecSort >) = ctx.mkBVSLT(this , t)
45-
46- infix fun Expr<BitVecSort>.gte (t : Expr <BitVecSort >) = ctx.mkBVSGE(this , t)
47-
48- val a = variableOf(" a" )
49- val b = variableOf(" b" )
50- val c = variableOf(" c" )
51- val d = variableOf(" d" )
52- val e = variableOf(" e" )
53- val f = variableOf(" f" )
54-
55- val optimizer = ctx.mkOptimize()
56-
57- optimizer.Add (e + f equalTo valueOf(3 ))
58- optimizer.Add (b + f equalTo valueOf(5 ))
59- optimizer.Add (c + d + e equalTo valueOf(4 ))
60- optimizer.Add (a + b + d equalTo valueOf(7 ))
61- // optimizer.Add(a lessThan valueOf(10000))
62- // optimizer.Add(b lessThan valueOf(10000))
63- // optimizer.Add(c lessThan valueOf(10000))
64- // optimizer.Add(d lessThan valueOf(10000))
65- // optimizer.Add(e lessThan valueOf(10000))
66- // optimizer.Add(f lessThan valueOf(10000))
67- optimizer.Add (a gte valueOf(0 ))
68- optimizer.Add (b gte valueOf(0 ))
69- optimizer.Add (c gte valueOf(0 ))
70- optimizer.Add (d gte valueOf(0 ))
71- optimizer.Add (e gte valueOf(0 ))
72- optimizer.Add (f gte valueOf(0 ))
73-
74- optimizer.MkMinimize (a + b + c + d + e + f)
75-
76- optimizer.Check ()
77- // while(optimizer.Check() == Status.SATISFIABLE) {
78- val model = optimizer.model
79-
80- val aEval = (model.evaluate(a, true ) as BitVecNum )
81- val bEval = (model.evaluate(b, true ) as BitVecNum )
82- val cEval = (model.evaluate(c, true ) as BitVecNum )
83- val dEval = (model.evaluate(d, true ) as BitVecNum )
84- val eEval = (model.evaluate(e, true ) as BitVecNum )
85- val fEval = (model.evaluate(f, true ) as BitVecNum )
86-
87- bestFound = min(bestFound, aEval.int + bEval.int + cEval.int + dEval.int + eEval.int + fEval.int)
88-
89- // optimizer.Add(
90- // ctx.mkOr(
91- // a notEqual aEval,
92- // b notEqual bEval,
93- // c notEqual cEval,
94- // d notEqual dEval,
95- // e notEqual eEval,
96- // f notEqual fEval,
97- // )
98- // )
99- // }
100- // println(bestFound)
101- bestFound
23+ input.sumOf { machine -> enableJoltages(machine) }
24+ }
25+
26+ private fun enableJoltages (machine : Machine ) : Int {
27+ return Context ().use { ctx ->
28+ val opt = ctx.mkOptimize()
29+
30+ fun intVariableOf (name : String ) = ctx.mkIntConst(name)
31+
32+ fun intValueOf (value : Int ) = ctx.mkInt(value)
33+
34+ infix fun IntExpr.gte (t : IntExpr ) = ctx.mkGe(this , t)
35+
36+ operator fun ArithExpr<IntSort>.plus (t : IntExpr ) = ctx.mkAdd(this , t)
37+
38+ infix fun ArithExpr<IntSort>.equalTo (t : IntExpr ) = ctx.mkEq(this , t)
39+
40+ fun List<IntExpr>.sum () = ctx.mkAdd(* this .toTypedArray())
41+
42+ val ZERO = intValueOf(0 )
43+
44+ val affectedJoltages = mutableMapOf<Int , MutableList <IntExpr >>()
45+ val buttonVariables = machine.buttonSchematics.mapIndexed { index, schematic ->
46+ // create button variable
47+ intVariableOf(index.toString()).also { buttonVariable ->
48+ // track the joltages if affects
49+ schematic.affectedIndices.forEach { affectedIndex ->
50+ affectedJoltages.computeIfAbsent(affectedIndex) { _ -> mutableListOf () }.add(buttonVariable)
51+ }
52+ // add it to the optimizer
53+ opt.Add (buttonVariable gte ZERO )
54+ }
55+ }
56+
57+ affectedJoltages.forEach { (joltageIndex, buttonsAffecting) ->
58+ val targetValue = intValueOf(machine.joltages[joltageIndex])
59+ val sumOfButtonPresses = buttonsAffecting.sum()
60+ opt.Add (sumOfButtonPresses equalTo targetValue)
61+ }
62+
63+ opt.MkMinimize (buttonVariables.sum())
64+
65+ val status = opt.Check ()
66+
67+ if (status == Status .SATISFIABLE ) {
68+ val model = opt.getModel()
69+ ((model.evaluate(buttonVariables.sum(), true ) as IntNum ).int)
70+ } else {
71+ throw IllegalStateException (" No Solution Found" )
10272 }
10373 }
10474 }
10575
106-
107- fun thing (current : String , goal : String , buttonSchematics : List <ButtonSchematic >): Long {
108- val minPressesForState = mutableMapOf (current to 0L )
76+ fun flipLights (goal : String , buttonSchematics : List <ButtonSchematic >): Long {
77+ val minPressesForState = mutableMapOf (" ." .repeat(goal.length) to 0L )
10978 while (! minPressesForState.containsKey(goal)) {
11079 minPressesForState.entries.toList().forEach { (light, cost) ->
11180 val nextStates = possibleLights(light, buttonSchematics)
@@ -128,7 +97,7 @@ class Day10 @Inject constructor(
12897
12998 private fun applyLightPress (current : String , buttonSchematic : ButtonSchematic ): String {
13099 return current.mapIndexed { index, c ->
131- if (buttonSchematic.lightsAffected .contains(index)) {
100+ if (buttonSchematic.affectedIndices .contains(index)) {
132101 if (c == ' #' ) { ' .' } else { ' #' }
133102 } else { c }
134103 }.joinToString(" " )
@@ -153,6 +122,4 @@ data class Machine(
153122 val joltages : List <Int >,
154123)
155124
156- /* * zero indexed */
157- // TODO rename
158- data class ButtonSchematic (val lightsAffected : List <Int >)
125+ data class ButtonSchematic (val affectedIndices : List <Int >)
0 commit comments