@@ -3,92 +3,146 @@ package com.willmolloy.adventofcode._2025
33import com.willmolloy.adventofcode.common.Day
44import com.willmolloy.adventofcode.common.Input
55import com.willmolloy.adventofcode.common.extensions.debug
6+ import java.util.BitSet
67import java.util.concurrent.atomic.AtomicInteger
78
89/* * https://adventofcode.com/2025/day/10 */
910object Day10 : Day(2025 , 10 ) {
1011
11- // lights = BITSET
12- private class Machine (val lights : Int , val buttons : Array <IntArray >, val joltage : IntArray ){
12+ private class Machine (
13+ val lights : BooleanArray ,
14+ val buttons : Array <IntArray >,
15+ val joltage : IntArray ,
16+ ) {
1317 override fun toString (): String {
14- return " Machine(lights=${lights} , buttons=${buttons.contentDeepToString()} , joltage=${joltage.contentToString()} )"
18+ return " Machine(lights=${lights.contentToString() } , buttons=${buttons.contentDeepToString()} , joltage=${joltage.contentToString()} )"
1519 }
1620 }
1721
18- fun Int.toggleBit (bit : Int ): Int = this xor (1 shl bit)
22+ private fun parse (input : Input ): List <Machine > =
23+ input.readLines().map { line ->
24+ val match = Regex (" \\ [(.*)] \\ ((.*)\\ ) \\ {(.*)}" ).matchEntire(line)!!
25+ val (lights, buttons, joltage) = match.destructured
26+
27+ val lightsTyped = lights.toCharArray().map { it != ' .' }.toBooleanArray()
28+ val buttonsTyped =
29+ buttons.split(" ) (" ).map { it.split(" ," ).map { it.toInt() }.toIntArray() }.toTypedArray()
30+ val joltageTyped = joltage.split(" ," ).map { it.toInt() }.toIntArray()
31+ Machine (lightsTyped, buttonsTyped, joltageTyped)
32+ }
33+
34+ fun BooleanArray.toBitSet (): BitSet {
35+ val bitSet = BitSet ()
36+ for ((i, b) in this .withIndex()) {
37+ if (b) {
38+ bitSet.flip(i)
39+ }
40+ }
41+ return bitSet
42+ }
1943
2044 override fun part1 (input : Input ): Any {
2145 val machines = parse(input)
22-
23- val solve = AtomicInteger (0 )
46+ val solveCount = AtomicInteger ()
2447
2548 fun solve (machine : Machine ): Long {
26- " Solving ${solve.incrementAndGet()} /${machines.size} : $machine " .debug()
49+ " Solving ${solveCount.incrementAndGet()} /${machines.size} : $machine " .debug()
50+
51+ // represent the graph node as a bitset; otherwise get OOM
52+ val lightsBitSet = machine.lights.toBitSet()
2753
2854 // BFS for shorted path
29- val queue = ArrayDeque <Int >()
55+ val queue = ArrayDeque <BitSet >()
3056
31- // initially all off
32- queue.add(0 )
57+ // initially all lights off
58+ val root = BitSet ()
59+ queue.add(root)
3360
34- val visited = mutableSetOf<Int >()
61+ val visited = mutableSetOf<BitSet >()
3562
3663 var depth = 0L
3764 while (! queue.isEmpty()) {
65+ " Depth: $depth , breadth: ${queue.size} " .debug()
3866
39- val levelSize = queue.size
40- for (i in 0 until levelSize) {
67+ (0 .. < queue.size).forEach { _ ->
4168 val node = queue.removeFirst()
4269
43- if (node == machine.lights ) {
70+ if (node == lightsBitSet ) {
4471 return depth
4572 }
4673
47- for (button in machine.buttons.withIndex() ) {
48- var adjNode = node
49- for (b in button.value) {
50- adjNode = adjNode.toggleBit(b)
51- }
74+ for (button in machine.buttons) {
75+ val adjNode = node.clone() as BitSet
76+
77+ // TODO possible to represent button as BitSet then flip all at once?
78+ button.forEach { adjNode.flip(it) }
5279
5380 if (visited.add(adjNode)) {
5481 queue.add(adjNode)
5582 }
5683 }
57-
5884 }
5985 depth++
6086 }
6187
62- error(" Unsolved " )
88+ error(" Failed to solve: $machine " )
6389 }
6490
6591 return machines.sumOf { solve(it) }
6692 }
6793
6894 override fun part2 (input : Input ): Any {
69- return 2222222222222222
70- }
95+ val machines = parse(input)
96+ val solveCount = AtomicInteger ()
7197
72- private fun parse (input : Input ): List <Machine > = input.readLines()
73- .mapNotNull { line ->
74- val match = Regex (" \\ [(.*)](.*)\\ {(.*)}" ).matchEntire(line) ? : return @mapNotNull null
98+ fun solve (machine : Machine ): Long {
99+ " Solving ${solveCount.incrementAndGet()} /${machines.size} : $machine " .debug()
75100
76- val (lights, buttons, joltage) = match.destructured
101+ // TODO possible to represent the joltage as bitset too?
102+ // TODO or is there a more efficient way to traverse the graph?
103+ // TODO the subreddit mentions Z3...
77104
78- val lightsTyped = lights.toCharArray().map { it != ' .' }.toBooleanArray()
105+ // BFS for shorted path
106+ val queue = ArrayDeque <IntArray >()
79107
80- val buttonsTyped = buttons.trim().removePrefix(" (" ).removeSuffix(" )" ).split(" ) (" )
81- .map { it.split(" ," ).map { it.toInt() }.toIntArray() }.toTypedArray()
108+ // initially all joltage zero
109+ val root = IntArray (machine.joltage.size) { 0 }
110+ queue.add(root)
82111
83- val joltageTyped = joltage.split(" ," ).map { it.toInt() }.toIntArray()
112+ val visited = mutableSetOf<String >()
113+
114+ var depth = 0L
115+ while (! queue.isEmpty()) {
116+ " Depth: $depth , breadth: ${queue.size} " .debug()
117+
118+ (0 .. < queue.size).forEach { _ ->
119+ val node = queue.removeFirst()
120+
121+ if (node.contentEquals(machine.joltage)) {
122+ return depth
123+ }
124+
125+ for (button in machine.buttons) {
126+ val adjNode = node.clone()
84127
85- var lightsBits = 0
86- for ((i, b) in lightsTyped.withIndex()) {
87- if (b) {
88- lightsBits = lightsBits.toggleBit(i)
128+ button.forEach { adjNode[it]++ }
129+
130+ // prune nodes which exceed the joltage
131+ if (adjNode.withIndex().any { adjNode[it.index] > machine.joltage[it.index] }) {
132+ continue
133+ }
134+
135+ if (visited.add(adjNode.contentToString())) {
136+ queue.add(adjNode)
137+ }
138+ }
89139 }
140+ depth++
90141 }
91142
92- Machine (lightsBits, buttonsTyped, joltageTyped )
143+ error( " Failed to solve: $machine " )
93144 }
145+
146+ return machines.sumOf { solve(it) }
147+ }
94148}
0 commit comments