Skip to content

Commit 2be8efe

Browse files
authored
Day 11 2025 (#306)
* Part One * Part two * remove extraneous default * readme * remove debug * associateBy * oneline
1 parent 871b9e1 commit 2be8efe

File tree

4 files changed

+124
-0
lines changed

4 files changed

+124
-0
lines changed
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package me.peckb.aoc._2025.calendar.day11
2+
3+
import javax.inject.Inject
4+
import me.peckb.aoc.generators.InputGenerator.InputGeneratorFactory
5+
6+
class Day11 @Inject constructor(
7+
private val generatorFactory: InputGeneratorFactory,
8+
) {
9+
fun partOne(filename: String) = generatorFactory.forFile(filename).readAs(::server) { input ->
10+
val serverMap = input.plus(Server("out")).associateBy { it.id }
11+
12+
countAllPaths(serverMap, serverMap["you"]!!)
13+
}
14+
15+
fun partTwo(filename: String) = generatorFactory.forFile(filename).readAs(::server) { input ->
16+
val serverMap = input.plus(Server("out")).associateBy { it.id }
17+
18+
countAllPaths(
19+
serverMap = serverMap,
20+
start = serverMap["svr"]!!,
21+
mandatoryStops = setOf(serverMap["dac"]!!, serverMap["fft"]!!)
22+
)
23+
}
24+
25+
fun countAllPaths(
26+
serverMap: Map<String, Server>,
27+
start: Server,
28+
end: Server = serverMap["out"]!!,
29+
mandatoryStops: Set<Server> = emptySet(),
30+
): Long {
31+
// Cache needs to include which mandatory stops have been visited
32+
val stepsCount = mutableMapOf<Pair<Server, Set<Server>>, Long>()
33+
// mutable state of our current path
34+
val currentPath = mutableSetOf<Server>()
35+
36+
fun dfs(current: Server, stopsHit: Set<Server> = emptySet()): Long {
37+
// AoC is nice, and we don't have loops but uh ... just in case someone on reddit gets squirrelly with their data
38+
if (current in currentPath) return 0
39+
40+
// Cache check needs to consider which mandatory stops were visited
41+
val cacheKey = current to stopsHit
42+
43+
// if we have already found the steps for when we hit this node after already touching
44+
// some / all of the mandatory stops we need to hit then we can bail out
45+
// since we only update the cache after we have bottomed out
46+
stepsCount[cacheKey]?.let { return it }
47+
48+
// If this is stop is a mandatory stop, we don't want to update the
49+
// steps passed in, so keep it in a new variable
50+
val updatedStopsHit = if (current in mandatoryStops) { stopsHit + current } else { stopsHit }
51+
52+
val steps = when (current) {
53+
// Only count if we're at the end AND we've visited all required nodes!
54+
end if mandatoryStops.size == updatedStopsHit.size -> 1
55+
// At end but missing required nodes
56+
end -> 0
57+
// not at the end yet, so keep going...
58+
else -> {
59+
// update our current path so our kids know where we have been
60+
currentPath.add(current)
61+
62+
// find out how many steps we have
63+
val steps = serverMap[current.id]!!.nextServers.sumOf { dfs(serverMap[it]!!, updatedStopsHit) }
64+
65+
// current path is mutable so don't forget to pop ourselves back off the stack!
66+
currentPath.remove(current)
67+
68+
steps
69+
}
70+
}
71+
72+
// Cache result with the visited required nodes
73+
stepsCount[cacheKey] = steps
74+
75+
return steps
76+
}
77+
78+
return dfs(start)
79+
}
80+
81+
private fun server(line: String): Server {
82+
val (id, serverList) = line.split(": ")
83+
return Server(id, (serverList.split(" ")))
84+
}
85+
}
86+
87+
data class Server(val id: String, val nextServers: List<String> = emptyList()) {
88+
override fun toString(): String = id
89+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
## [Day 11: Reactor](https://adventofcode.com/2025/day/11)

src/test/kotlin/me/peckb/aoc/_2025/TestDayComponent.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import me.peckb.aoc._2025.calendar.day07.Day07Test
1010
import me.peckb.aoc._2025.calendar.day08.Day08Test
1111
import me.peckb.aoc._2025.calendar.day09.Day09Test
1212
import me.peckb.aoc._2025.calendar.day10.Day10Test
13+
import me.peckb.aoc._2025.calendar.day11.Day11Test
1314
import javax.inject.Singleton
1415
import me.peckb.aoc.DayComponent
1516
import me.peckb.aoc.InputModule
@@ -28,4 +29,5 @@ internal interface TestDayComponent : DayComponent {
2829
fun inject(day08Test: Day08Test)
2930
fun inject(day09Test: Day09Test)
3031
fun inject(day10Test: Day10Test)
32+
fun inject(day11Test: Day11Test)
3133
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package me.peckb.aoc._2025.calendar.day11
2+
3+
import javax.inject.Inject
4+
5+
import me.peckb.aoc._2025.DaggerTestDayComponent
6+
import org.junit.jupiter.api.Assertions.assertEquals
7+
import org.junit.jupiter.api.BeforeEach
8+
import org.junit.jupiter.api.Test
9+
10+
internal class Day11Test {
11+
@Inject
12+
lateinit var day11: Day11
13+
14+
@BeforeEach
15+
fun setup() {
16+
DaggerTestDayComponent.create().inject(this)
17+
}
18+
19+
@Test
20+
fun testDay11PartOne() {
21+
assertEquals(423, day11.partOne(DAY_11))
22+
}
23+
24+
@Test
25+
fun testDay11PartTwo() {
26+
assertEquals(333657640517376, day11.partTwo(DAY_11))
27+
}
28+
29+
companion object {
30+
private const val DAY_11: String = "advent-of-code-input/2025/day11.input"
31+
}
32+
}

0 commit comments

Comments
 (0)