1+ import com.microsoft.z3.Context
2+ import com.microsoft.z3.IntNum
3+ import com.microsoft.z3.Status.SATISFIABLE
4+ import java.util.PriorityQueue
5+ import kotlin.io.path.readLines
6+
7+ class Day10 : Day {
8+ override fun partOne (filename : String , verbose : Boolean ): Int {
9+ val machines = filename.asPath().readLines().map { it.parseMachine() }
10+
11+ if (verbose) {
12+ machines.forEach { machine ->
13+ print (machine.indicatorLights.joinToString(" " , " [" , " ]" ) { if (it) " #" else " ." })
14+ print (" " )
15+ print (machine.buttons.joinToString(" " ) { it.joinToString(" ," , " (" , " )" ) })
16+ print (" " )
17+ print (machine.joltages.joinToString(" ," , " {" , " }" ))
18+ println ()
19+ }
20+ }
21+
22+ return machines.sumOf { machine ->
23+ search(
24+ List (machine.indicatorLights.size) { false },
25+ { state ->
26+ machine.buttons.map { buttons ->
27+ state.mapIndexed { index, on ->
28+ if (index in buttons) ! on else on
29+ } to 1
30+ }
31+ },
32+ { state -> state == machine.indicatorLights}
33+ )[machine.indicatorLights]!!
34+ }
35+ }
36+
37+ override fun partTwo (filename : String , verbose : Boolean ): Int {
38+ val machines = filename.asPath().readLines().map { it.parseMachine() }
39+
40+ return machines.sumOf { machine ->
41+ Context ().use { ctx ->
42+ val solver = ctx.mkOptimize()
43+ val zero = ctx.mkInt(0 )
44+
45+ val buttons = machine.buttons.indices
46+ .map { ctx.mkIntConst(" button#$it " ) }
47+ .onEach { button -> solver.Add (ctx.mkGe(button, zero)) }
48+ .toTypedArray()
49+
50+ machine.joltages.forEachIndexed { counter, targetValue ->
51+ val buttonsThatIncrement = machine.buttons
52+ .withIndex()
53+ .filter { (_, counters) -> counter in counters }
54+ .mapToArray { (index) -> buttons[index] }
55+ val target = ctx.mkInt(targetValue)
56+ val sumOfPresses = ctx.mkAdd(* buttonsThatIncrement)
57+ solver.Add (ctx.mkEq(sumOfPresses, target))
58+ }
59+
60+ val presses = ctx.mkIntConst(" presses" )
61+ solver.Add (ctx.mkEq(presses, ctx.mkAdd(* buttons)))
62+ solver.MkMinimize (presses)
63+
64+ if (solver.Check () != SATISFIABLE ) error(" No solution found for machine: $machine ." )
65+ solver.model.evaluate(presses, false ).let { it as IntNum }.int
66+ }
67+ }
68+ }
69+
70+ private fun <T : Any > search (
71+ start : T ,
72+ neighbours : (T ) -> Iterable <Pair <T , Int >>,
73+ goalFunction : (T ) -> Boolean ,
74+ maximumCost : Int = Int .MAX_VALUE ,
75+ ): Map <T , Int > {
76+ val distance = mutableMapOf (start to 0 )
77+ val visited = mutableSetOf<Pair <T , Int >>()
78+
79+ val queue = PriorityQueue (compareBy<Triple <T , Int , Int >> { (_, _, priority) -> priority })
80+ queue.add(Triple (start, 0 , 0 ))
81+
82+ while (queue.isNotEmpty()) {
83+ val (node, costSoFar, _) = queue.poll()
84+ if (! visited.add(node to costSoFar)) continue
85+ if (goalFunction(node)) return distance
86+
87+ for ((nextNode, nextCost) in neighbours(node)) {
88+ if (maximumCost - nextCost < costSoFar) continue
89+
90+ val totalCost = costSoFar + nextCost
91+
92+ if (totalCost > (distance[nextNode] ? : Int .MAX_VALUE )) continue
93+
94+ distance[nextNode] = totalCost
95+
96+ queue.add(Triple (nextNode, totalCost, totalCost))
97+ }
98+ }
99+
100+ return emptyMap()
101+ }
102+
103+ private data class Machine (
104+ val indicatorLights : List <Boolean >,
105+ val buttons : List <Set <Int >>,
106+ val joltages : List <Int >,
107+ )
108+
109+ private val machinePattern = Regex (""" \[(.*)] (.*) \{(.*)}""" )
110+
111+ private fun String.parseMachine () =
112+ machinePattern.matchEntire(this )!! .destructured.let { (indicatorLights, buttons, joltages) ->
113+ Machine (
114+ indicatorLights.map { it == ' #' },
115+ buttons.split(" " ).map { button ->
116+ button.removeSurrounding(" (" , " )" ).split(' ,' ).mapToSet { it.toInt() }
117+ },
118+ joltages.split(" ," ).map { it.toInt() }
119+ )
120+ }
121+
122+ companion object : Day .Main (" Day10.txt" ) {
123+ @JvmStatic
124+ fun main (args : Array <String >) = main()
125+ }
126+ }
0 commit comments